From d598c6d38d5135651b1abf7337513406b303cbd9 Mon Sep 17 00:00:00 2001 From: paunofu Date: Thu, 14 Sep 2023 08:20:09 +0000 Subject: [PATCH 001/133] Translated using Weblate (Spanish) Currently translated at 45.7% (1414 of 3089 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/es/ --- langs/layers/es.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/langs/layers/es.json b/langs/layers/es.json index 31d2d941b..31857c562 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -1939,10 +1939,10 @@ "then": "Este cruce no tiene superficie podotáctil" }, "2": { - "then": "Este cruce tiene superficie podotáctil, pero no es correcto" + "then": "Este cruce tiene superficie podotáctil, pero no correctamente" } }, - "question": "¿Tiene pavimento táctil este cruce?" + "question": "¿Este cruce tiene pavimento podotáctil?" }, "crossing-type": { "mappings": { @@ -1956,7 +1956,7 @@ "then": "Paso de cebra" }, "3": { - "then": "Cruce sin marcas de cruce" + "then": "Cruce sin señalizar" } }, "question": "¿Qué tipo de cruce es este?" From e16ee28f8856a3ef176c2cee691a5a7a02a13098 Mon Sep 17 00:00:00 2001 From: paunofu Date: Fri, 15 Sep 2023 06:57:42 +0000 Subject: [PATCH 002/133] Translated using Weblate (Catalan) Currently translated at 63.3% (1957 of 3089 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 250 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 240 insertions(+), 10 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 139ea1daa..a612bbe4b 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -1642,7 +1642,8 @@ "title": "un observatori d'ocells" }, "1": { - "description": "Una pantalla o paret amb obertures per a observar ocells" + "description": "Una pantalla o paret amb obertures per a observar ocells", + "title": "un observatori d'ocells" } }, "tagRenderings": { @@ -1819,7 +1820,7 @@ "question": "Té un connector
endoll de paret Europeu amb un pin de terra (CEE7/4 tipus F)
" }, "3": { - "question": "Té un connector
Chademo " + "question": "Té un connector
Chademo
" }, "4": { "question": "Té un connector
Tipus 1 amb cable (J1772)
" @@ -1984,7 +1985,7 @@ "then": "Supercarregador Tesla (destí) (Un tipus 2 amb un cable marca tesla)" }, "25": { - "then": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla)" + "then": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla)" }, "26": { "then": "USB per a carregar mòbils i dispositius petits" @@ -2658,14 +2659,25 @@ "2": { "then": "Este creuament té superfície podotàctil, però no correctament" } - } + }, + "question": "Aquest creuament té superfície podotàctil?" }, "crossing-type": { "mappings": { + "0": { + "then": "Creuament, sense semàfors" + }, + "1": { + "then": "Creuament amb senyals de trànsit" + }, "2": { "then": "Pas de zebra" + }, + "3": { + "then": "Creuament sense senyalitzar" } - } + }, + "question": "Quin tipus de creuament és aquest?" } }, "title": { @@ -2679,16 +2691,105 @@ }, "cycleways_and_roads": { "description": "Totes les infraestructures per les quals algú pot ciclar, acompanyades de preguntes sobre aquesta infraestructura", + "name": "Vies ciclistes i carreteres", "tagRenderings": { "Cycleway type for a road": { "mappings": { + "0": { + "then": "Hi ha un carril compartit" + }, "1": { "then": "Hi ha un carril al costat de la carretera (separat amb pintura)" + }, + "2": { + "then": "Hi ha una pista, però no hi ha una via ciclista dibuixada separada d'aquesta carretera al mapa." + }, + "3": { + "then": "Hi ha una via ciclista dibuixada per separat" + }, + "4": { + "then": "No hi ha via ciclista" + }, + "5": { + "then": "No hi ha via ciclista" + } + }, + "question": "Quin tipus de via ciclista hi ha aquí?" + }, + "Cycleway:smoothness": { + "mappings": { + "0": { + "then": "Utilitzable per a rodets prims: patins, monopatí" + }, + "1": { + "then": "Utilitzable per a rodes primes: bicicleta de carreres" + }, + "2": { + "then": "Utilitzable per a rodes normals: bici de ciutat, cadira de rodes, patinets" + }, + "3": { + "then": "Utilitzable per a rodes robustes: bicicleta de senderisme, cotxe, rickshaw" + }, + "4": { + "then": "Utilitzable per a vehicles amb gran espai lliure: vehicle tot terreny lleuger" + }, + "5": { + "then": "Utilitzable per a vehicles tot terreny: vehicle tot terreny resistent" + }, + "6": { + "then": "Utilitzable per a vehicles tot terreny especialitzats: tractor, ATV" } } }, + "Cycleway:surface": { + "mappings": { + "0": { + "then": "Aquesta via ciclista no està pavimentada" + }, + "1": { + "then": "Aquesta via ciclista està pavimentada" + }, + "2": { + "then": "Aquesta via ciclista està feta d'asfalt" + }, + "4": { + "then": "Aquesta via ciclista està feta de formigó" + }, + "8": { + "then": "Aquesta via ciclista està feta de fusta" + } + }, + "question": "De quina superfície està fet aquesta via ciclista?", + "render": "Aquesta via ciclista està feta de {cycleway:surface}" + }, + "Is this a cyclestreet? (For a road)": { + "mappings": { + "0": { + "then": "Açò és un ciclocarrer, i una zona de 30km/h." + }, + "1": { + "then": "Açò és un ciclocarrer" + }, + "2": { + "then": "Açò no és un ciclocarrer." + } + }, + "question": "Açò és un ciclocarrer?" + }, "Maxspeed (for road)": { "mappings": { + "0": { + "then": "La velocitat màxima és de 20km/h" + }, + "1": { + "then": "La velocitat màxima és de 30km/h" + }, + "2": { + "then": "La velocitat màxima és de 50km/h" + }, + "3": { + "then": "La velocitat màxima és de 70km/h" + }, "4": { "then": "La velocitat màxima és de 90km/h" } @@ -2730,24 +2831,68 @@ } } }, + "cyclelan-segregation": { + "mappings": { + "0": { + "then": "Aquesta via ciclista està separada per una línia discontínua" + }, + "1": { + "then": "Aquesta via ciclista està separat per una línia contínua" + }, + "2": { + "then": "Aquesta via ciclista està separat per una línia d'aparcament" + }, + "3": { + "then": "Aquesta via ciclista està separada per una vorada" + } + }, + "question": "Com està separada aquesta via ciclista de la carretera?" + }, "cycleway-lane-track-traffic-signs": { "mappings": { "0": { "then": "Via ciclista obligatòria" }, + "1": { + "then": "Ciclovia obligatòria (amb senyal suplementari)
" + }, "2": { "then": "Via segregada a peu/ciclista" }, "3": { "then": "Via no segregada a peu/ciclista" + }, + "4": { + "then": "Sense senyal de trànsit" } - } + }, + "question": "Quina senyal de trànsit té aquesta via ciclista?" + }, + "cycleway-segregation": { + "mappings": { + "0": { + "then": "Aquesta via ciclista està separada per una línia discontinua" + }, + "1": { + "then": "Aquesta via ciclista està separada per una línia continua" + }, + "2": { + "then": "Aquesta via ciclista està separada per una línia d'aparcament" + }, + "3": { + "then": "Aquesta via ciclista està separada per una vorada" + } + }, + "question": "Com està separat aquesta via ciclista?" }, "cycleway-traffic-signs": { "mappings": { "0": { "then": "Via ciclista obligatòria" }, + "1": { + "then": "Ciclovia obligatòria (amb senyal suplementari)
" + }, "2": { "then": "Via segregada a peu/ciclista" }, @@ -2756,13 +2901,29 @@ }, "4": { "then": "Via ciclista obligatòria" + }, + "5": { + "then": "Ciclovia (ciclomotor) obligatòria" + }, + "6": { + "then": "via ciclista no obligatòria" + }, + "7": { + "then": "No hi ha cap senyal de trànsit" } - } + }, + "question": "Quina senyal de trànsit té aquesta via ciclista?" }, "cycleway-traffic-signs-supplementary": { "mappings": { + "0": { + "then": "Els ciclomotors han d'utilitzar la via ciclista" + }, "3": { "then": "Els ciclomotors no estan permesos" + }, + "6": { + "then": "No hi ha cap senyal de trànsit addicional" } } }, @@ -2771,6 +2932,9 @@ }, "is lit?": { "mappings": { + "0": { + "then": "Aquest carrer està il·luminat" + }, "1": { "then": "Aquesta carretera no està il·luminada" }, @@ -2784,7 +2948,9 @@ "question": "Aquesta carretera està il·luminada?" }, "width:carriageway": { - "questionHint": "Això es mesura de vorera a vorera i, per tant, inclou l'amplada dels aparcaments en línia" + "question": "Quina és l'amplada dels carrils d'aquesta carretera (en metres)?", + "questionHint": "Això es mesura de vorera a vorera i, per tant, inclou l'amplada dels aparcaments en línia", + "render": "L'amplada dels carrils d'aquesta carretera és {width:carriageway}m" } }, "title": { @@ -2807,6 +2973,15 @@ "5": { "then": "Carril bici" }, + "6": { + "then": "Carretera amb via ciclista al costat de la carretera {name}" + }, + "7": { + "then": "Via ciclista al costat de la carretera" + }, + "8": { + "then": "Ciclocarrer {name}" + }, "9": { "then": "Carrer ciclista" } @@ -2950,16 +3125,46 @@ "name": "Direcció de la visualització" }, "doctors": { + "description": "Aquesta capa mostra els consultoris mèdics", "name": "Metges", + "presets": { + "0": { + "title": "un consultori mèdic" + } + }, "tagRenderings": { "name": { - "question": "Com es diu aquesta consulta mèdica?" + "question": "Com es diu aquesta consulta mèdica?", + "render": "Aquest consultori mèdic és diu {name}" + }, + "specialty": { + "mappings": { + "0": { + "then": "Açò és un metge generalista" + }, + "1": { + "then": "Açò és un ginecòleg" + }, + "2": { + "then": "Açò és un psiquiatra" + }, + "3": { + "then": "Açò és un pediatre" + } + }, + "question": "En què està especialitzat aquest metge?", + "render": "Aquest metge està especialitzat en {healthcare:speciality}" } + }, + "title": { + "render": "Consultori metge {name}" } }, "dogpark": { + "name": "parcs de gossos", "presets": { "0": { + "description": "Un lloc per a gossos, on poden correr sense corretja", "title": "un parc per a gossos" } }, @@ -2975,9 +3180,27 @@ "mappings": { "0": { "then": "Aquest parc per a gossos està tancat per tot arreu" + }, + "1": { + "then": "Aquest parc de gossos no està tancat per tot el voltant" } - } + }, + "question": "Aquest parc de gossos està tancat?" + }, + "smalldogs": { + "mappings": { + "0": { + "then": "Té un espai separat per a cadells i gossos petits" + }, + "1": { + "then": "No té un espai separat per a cadells i gossos petits" + } + }, + "question": "Aquest parc per a gossos té una zona tancada per a gossos petits i cadells?" } + }, + "title": { + "render": "parc per a gossos" } }, "drinking_water": { @@ -3136,6 +3359,13 @@ "question": "Obert ara" } } + }, + "7": { + "options": { + "2": { + "question": "Sense superfície podotàctil" + } + } } } }, From cfa35d0eedad6e8be34515d9b6968a4f72d7e63b Mon Sep 17 00:00:00 2001 From: kjon Date: Sat, 16 Sep 2023 20:26:52 +0000 Subject: [PATCH 003/133] Translated using Weblate (German) Currently translated at 99.9% (3095 of 3096 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/de/ --- langs/layers/de.json | 518 ++++++++++++++++++++++--------------------- 1 file changed, 269 insertions(+), 249 deletions(-) diff --git a/langs/layers/de.json b/langs/layers/de.json index a79b5d472..9008a2559 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -35,6 +35,16 @@ "1": { "title": "eine freistehende Posterbox" }, + "10": { + "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", + "title": "ein Schild" + }, + "11": { + "title": "eine Skulptur" + }, + "12": { + "title": "eine Wandmalerei" + }, "2": { "title": "eine wandmontierte Posterbox" }, @@ -61,16 +71,6 @@ }, "9": { "title": "ein Totem" - }, - "10": { - "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", - "title": "ein Schild" - }, - "11": { - "title": "eine Skulptur" - }, - "12": { - "title": "eine Wandmalerei" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Dies ist ein Brett" }, + "10": { + "then": "Dies ist eine Wandmalerei" + }, "2": { "then": "Dies ist eine Litfaßsäule" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Dies ist ein Totem" - }, - "10": { - "then": "Dies ist eine Wandmalerei" } }, "question": "Welche Art von Werbung ist das?", @@ -205,6 +205,9 @@ "1": { "then": "Brett" }, + "10": { + "then": "Wandmalerei" + }, "2": { "then": "Posterbox" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Totem" - }, - "10": { - "then": "Wandmalerei" } } } @@ -312,6 +312,15 @@ "1": { "then": "Wandbild" }, + "10": { + "then": "Azulejo (spanische dekorative Fliesenarbeit)" + }, + "11": { + "then": "Fliesenarbeit" + }, + "12": { + "then": "Holzschnitzerei" + }, "2": { "then": "Malerei" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relief" - }, - "10": { - "then": "Azulejo (spanische dekorative Fliesenarbeit)" - }, - "11": { - "then": "Fliesenarbeit" - }, - "12": { - "then": "Holzschnitzerei" } }, "question": "Um welche Art Kunstwerk handelt es sich?", @@ -1830,6 +1830,27 @@ "1": { "question": "Verfügt über einen
Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)
" }, + "10": { + "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" + }, + "11": { + "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" + }, + "12": { + "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" + }, + "13": { + "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" + }, + "14": { + "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" + }, + "15": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" + }, + "16": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" + }, "2": { "question": "Verfügt über einen
europäischen Netzstecker mit Erdungsstift (CEE7/4 Typ E)
Anschluss" }, @@ -1853,27 +1874,6 @@ }, "9": { "question": "Hat einen
Typ 2 CCS (Mennekes)
Anschluss" - }, - "10": { - "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" - }, - "11": { - "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" - }, - "12": { - "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" - }, - "13": { - "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" - }, - "14": { - "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" - }, - "15": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" - }, - "16": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" } } } @@ -1929,30 +1929,6 @@ "1": { "then": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)" }, - "2": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, - "3": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, - "4": { - "then": "Chademo-Anschluss" - }, - "5": { - "then": "Chademo-Anschluss" - }, - "6": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "7": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "8": { - "then": "Typ 1 ohne Kabel (J1772)" - }, - "9": { - "then": " Typ 1 ohne Kabel (J1772)" - }, "10": { "then": "Typ 1 CCS (Typ 1 Combo)" }, @@ -1983,6 +1959,9 @@ "19": { "then": "Typ 2 mit Kabel (mennekes)" }, + "2": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, "20": { "then": "Tesla Supercharger CCS (Typ 2 CSS von Tesla)" }, @@ -2013,11 +1992,32 @@ "29": { "then": " Bosch Active Connect mit 3 Pins und Kabel" }, + "3": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, "30": { "then": "Bosch Active Connect mit 5 Pins und Kabel" }, "31": { "then": " Bosch Active Connect mit 5 Pins und Kabel" + }, + "4": { + "then": "Chademo-Anschluss" + }, + "5": { + "then": "Chademo-Anschluss" + }, + "6": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "7": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "8": { + "then": "Typ 1 ohne Kabel (J1772)" + }, + "9": { + "then": " Typ 1 ohne Kabel (J1772)" } }, "question": "Welche Ladeanschlüsse gibt es hier?" @@ -3555,6 +3555,15 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" + }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3578,15 +3587,6 @@ }, "9": { "then": "Der Radweg ist aus Schotter" - }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieses Radwegs?", @@ -3635,6 +3635,15 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" + }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3658,15 +3667,6 @@ }, "9": { "then": "Der Radweg ist aus Schotter" - }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieser Straße?", @@ -4705,30 +4705,6 @@ "1": { "then": "Die Fitness-Station hat ein Schild mit Anweisungen für eine bestimmte Übung." }, - "2": { - "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." - }, - "3": { - "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." - }, - "4": { - "then": "Die Fitness-Station hat Stangen zum Dehnen." - }, - "5": { - "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." - }, - "6": { - "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." - }, - "7": { - "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." - }, - "8": { - "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." - }, - "9": { - "then": "Die Fitness-Station hat Pfosten für Slalomübungen." - }, "10": { "then": "Die Fitness-Station hat Trittsteine." }, @@ -4759,6 +4735,9 @@ "19": { "then": "Die Fitness-Station hat Kampfseile (battle ropes)." }, + "2": { + "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." + }, "20": { "then": "Die Fitness-Station hat ein Fahrradergometer." }, @@ -4773,6 +4752,27 @@ }, "24": { "then": "Die Fitness-Station hat eine Slackline." + }, + "3": { + "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." + }, + "4": { + "then": "Die Fitness-Station hat Stangen zum Dehnen." + }, + "5": { + "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." + }, + "6": { + "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." + }, + "7": { + "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." + }, + "8": { + "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." + }, + "9": { + "then": "Die Fitness-Station hat Pfosten für Slalomübungen." } }, "question": "Welche Übungsgeräte gibt es an dieser Fitness-Station?" @@ -4892,6 +4892,21 @@ "1": { "then": "Dies ist eine Pommesbude" }, + "10": { + "then": "Hier werden chinesische Gerichte serviert" + }, + "11": { + "then": "Hier werden griechische Gerichte serviert" + }, + "12": { + "then": "Hier werden indische Gerichte serviert" + }, + "13": { + "then": "Hier werden türkische Gerichte serviert" + }, + "14": { + "then": "Hier werden thailändische Gerichte serviert" + }, "2": { "then": "Bietet vorwiegend Pastagerichte an" }, @@ -4915,21 +4930,6 @@ }, "9": { "then": "Hier werden französische Gerichte serviert" - }, - "10": { - "then": "Hier werden chinesische Gerichte serviert" - }, - "11": { - "then": "Hier werden griechische Gerichte serviert" - }, - "12": { - "then": "Hier werden indische Gerichte serviert" - }, - "13": { - "then": "Hier werden türkische Gerichte serviert" - }, - "14": { - "then": "Hier werden thailändische Gerichte serviert" } }, "question": "Was für Essen gibt es hier?", @@ -6145,6 +6145,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Alle Notizen" + }, + "1": { + "question": "Importnotizen ausblenden" + }, + "2": { + "question": "Nur Importnotizen anzeigen" + } + } + }, "2": { "options": { "0": { @@ -6200,19 +6213,6 @@ "question": "Nur offene Notizen anzeigen" } } - }, - "10": { - "options": { - "0": { - "question": "Alle Notizen" - }, - "1": { - "question": "Importnotizen ausblenden" - }, - "2": { - "question": "Nur Importnotizen anzeigen" - } - } } }, "name": "OpenStreetMap-Hinweise", @@ -6541,6 +6541,21 @@ "1": { "then": "Dies ist ein normaler Stellplatz." }, + "10": { + "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." + }, + "11": { + "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." + }, + "12": { + "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." + }, + "13": { + "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." + }, + "14": { + "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." + }, "2": { "then": "Dies ist ein Behindertenstellplatz." }, @@ -6564,21 +6579,6 @@ }, "9": { "then": "Dies ist ein Stellplatz, der für Motorräder reserviert ist." - }, - "10": { - "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." - }, - "11": { - "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." - }, - "12": { - "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." - }, - "13": { - "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." - }, - "14": { - "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." } }, "question": "Welche Art von Stellplatz ist dies?" @@ -6883,6 +6883,20 @@ } }, "tagRenderings": { + "has_atm": { + "mappings": { + "0": { + "then": "Die Postfiliale verfügt über einen Geldautomat" + }, + "1": { + "then": "Die Postfiliale verfügt nicht über einen Geldautomat" + }, + "2": { + "then": "Die Postfiliale verfügt über einen Geldautomat, der aber bereits separat kartiert ist" + } + }, + "question": "Verfügt die Postfiliale über einen Geldautomat?" + }, "letter-from": { "mappings": { "0": { @@ -7573,30 +7587,6 @@ "1": { "question": "Recycling von Batterien" }, - "2": { - "question": "Recycling von Getränkekartons" - }, - "3": { - "question": "Recycling von Dosen" - }, - "4": { - "question": "Recycling von Kleidung" - }, - "5": { - "question": "Recycling von Speiseöl" - }, - "6": { - "question": "Recycling von Motoröl" - }, - "7": { - "question": "Recycling von Leuchtstoffröhren" - }, - "8": { - "question": "Recycling von Grünabfällen" - }, - "9": { - "question": "Recycling von Glasflaschen" - }, "10": { "question": "Recycling von Glas" }, @@ -7627,11 +7617,35 @@ "19": { "question": "Recycling von Restabfällen" }, + "2": { + "question": "Recycling von Getränkekartons" + }, "20": { "question": "Recycling von Druckerpatronen" }, "21": { "question": "Recycling von Fahrrädern" + }, + "3": { + "question": "Recycling von Dosen" + }, + "4": { + "question": "Recycling von Kleidung" + }, + "5": { + "question": "Recycling von Speiseöl" + }, + "6": { + "question": "Recycling von Motoröl" + }, + "7": { + "question": "Recycling von Leuchtstoffröhren" + }, + "8": { + "question": "Recycling von Grünabfällen" + }, + "9": { + "question": "Recycling von Glasflaschen" } } }, @@ -7699,30 +7713,6 @@ "1": { "then": "Getränkekartons können hier recycelt werden" }, - "2": { - "then": "Dosen können hier recycelt werden" - }, - "3": { - "then": "Kleidung kann hier recycelt werden" - }, - "4": { - "then": "Speiseöl kann hier recycelt werden" - }, - "5": { - "then": "Motoröl kann hier recycelt werden" - }, - "6": { - "then": "Hier können Leuchtstoffröhren recycelt werden" - }, - "7": { - "then": "Grünabfälle können hier recycelt werden" - }, - "8": { - "then": "Bio-Abfall kann hier recycelt werden" - }, - "9": { - "then": "Glasflaschen können hier recycelt werden" - }, "10": { "then": "Glas kann hier recycelt werden" }, @@ -7753,6 +7743,9 @@ "19": { "then": "Schuhe können hier recycelt werden" }, + "2": { + "then": "Dosen können hier recycelt werden" + }, "20": { "then": "Elektrokleingeräte können hier recycelt werden" }, @@ -7767,6 +7760,27 @@ }, "24": { "then": "Fahrräder können hier recycelt werden" + }, + "3": { + "then": "Kleidung kann hier recycelt werden" + }, + "4": { + "then": "Speiseöl kann hier recycelt werden" + }, + "5": { + "then": "Motoröl kann hier recycelt werden" + }, + "6": { + "then": "Hier können Leuchtstoffröhren recycelt werden" + }, + "7": { + "then": "Grünabfälle können hier recycelt werden" + }, + "8": { + "then": "Bio-Abfall kann hier recycelt werden" + }, + "9": { + "then": "Glasflaschen können hier recycelt werden" } }, "question": "Was kann hier recycelt werden?" @@ -8570,6 +8584,12 @@ "1": { "then": "Diese Straßenlaterne verwendet LEDs" }, + "10": { + "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" + }, + "11": { + "then": "Diese Straßenlaterne wird mit Gas beleuchtet" + }, "2": { "then": "Diese Straßenlaterne verwendet Glühlampenlicht" }, @@ -8593,12 +8613,6 @@ }, "9": { "then": "Diese Straßenlaterne verwendet Niederdruck-Natriumdampflampen (einfarbig orange)" - }, - "10": { - "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" - }, - "11": { - "then": "Diese Straßenlaterne wird mit Gas beleuchtet" } }, "question": "Mit welcher Art von Beleuchtung arbeitet diese Straßenlaterne?" @@ -9687,6 +9701,27 @@ "1": { "question": "Verkauf von Getränken" }, + "10": { + "question": "Verkauf von Milch" + }, + "11": { + "question": "Verkauf von Brot" + }, + "12": { + "question": "Verkauf von Eiern" + }, + "13": { + "question": "Verkauf von Käse" + }, + "14": { + "question": "Verkauf von Honig" + }, + "15": { + "question": "Verkauf von Kartoffeln" + }, + "16": { + "question": "Verkauf von Blumen" + }, "2": { "question": "Verkauf von Süßigkeiten" }, @@ -9710,27 +9745,6 @@ }, "9": { "question": "Verkauf von Fahrradschläuchen" - }, - "10": { - "question": "Verkauf von Milch" - }, - "11": { - "question": "Verkauf von Brot" - }, - "12": { - "question": "Verkauf von Eiern" - }, - "13": { - "question": "Verkauf von Käse" - }, - "14": { - "question": "Verkauf von Honig" - }, - "15": { - "question": "Verkauf von Kartoffeln" - }, - "16": { - "question": "Verkauf von Blumen" } } } @@ -9771,6 +9785,30 @@ "1": { "then": "Süßigkeiten werden verkauft" }, + "10": { + "then": "Brot wird verkauft" + }, + "11": { + "then": "Eier werden verkauft" + }, + "12": { + "then": "Käse wird verkauft" + }, + "13": { + "then": "Honig wird verkauft" + }, + "14": { + "then": "Kartoffeln werden verkauft" + }, + "15": { + "then": "Blumen werden verkauft" + }, + "16": { + "then": "Parkscheine werden verkauft" + }, + "18": { + "then": "Fahrscheine werden verkauft" + }, "2": { "then": "Lebensmittel werden verkauft" }, @@ -9794,24 +9832,6 @@ }, "9": { "then": "Milch wird verkauft" - }, - "10": { - "then": "Brot wird verkauft" - }, - "11": { - "then": "Eier werden verkauft" - }, - "12": { - "then": "Käse wird verkauft" - }, - "13": { - "then": "Honig wird verkauft" - }, - "14": { - "then": "Kartoffeln werden verkauft" - }, - "15": { - "then": "Blumen werden verkauft" } }, "question": "Was wird in diesem Automaten verkauft?", @@ -10148,4 +10168,4 @@ } } } -} \ No newline at end of file +} From 3c5edaf8af5f8125cced4f0125268cbdad94ade3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 14:05:43 +0200 Subject: [PATCH 004/133] Chore: sync translations --- assets/layers/birdhide/birdhide.json | 3 +- .../charging_station/charging_station.json | 4 +- assets/layers/crossings/crossings.json | 21 +- .../cycleways_and_roads.json | 165 ++++-- assets/layers/doctors/doctors.json | 30 +- assets/layers/dogpark/dogpark.json | 24 +- assets/layers/filters/filters.json | 3 +- assets/layers/postoffices/postoffices.json | 12 +- .../vending_machine/vending_machine.json | 6 +- .../mapcomplete-changes.json | 112 +++- langs/layers/de.json | 510 +++++++++--------- 11 files changed, 518 insertions(+), 372 deletions(-) diff --git a/assets/layers/birdhide/birdhide.json b/assets/layers/birdhide/birdhide.json index 4c2618e92..a1a1b9e77 100644 --- a/assets/layers/birdhide/birdhide.json +++ b/assets/layers/birdhide/birdhide.json @@ -326,7 +326,8 @@ "nl": "een vogelkijkwand", "de": "einen Sichtschutz zur Vogelbeobachtung", "fr": "un camouflage d’observation ornithologique", - "da": "et fugletårn" + "da": "et fugletårn", + "ca": "un observatori d'ocells" }, "description": { "en": "A screen or wall with openings to watch birds", diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index ef1ec1059..d79783ff5 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -1070,7 +1070,7 @@ "then": { "en": "Tesla supercharger (destination) (A Type 2 with cable branded as tesla)", "nl": "Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)", - "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla)", + "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla)", "de": "Tesla supercharger (Destination) (Typ 2 mit Kabel von Tesla)", "es": "Supercargador Tesla (destino) (Un Tipo 2 con un cable de marca tesla)" }, @@ -4879,7 +4879,7 @@ "question": { "en": "Has a
Chademo
connector", "nl": "Heeft een
Chademo
", - "ca": "Té un connector
Chademo ", + "ca": "Té un connector
Chademo
", "da": "Har et
Chademo
stik", "de": "Verfügt über einen
Chademo
Stecker", "es": "Tiene un conector
Chademo
" diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index 12989edad..caf1fbbb2 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -125,7 +125,8 @@ "nl": "Wat voor oversteekplaats is dit?", "de": "Was ist das für eine Kreuzung?", "es": "¿Qué tipo de cruce es este?", - "fr": "Quel type de passage piéton est-ce ?" + "fr": "Quel type de passage piéton est-ce ?", + "ca": "Quin tipus de creuament és aquest?" }, "condition": "highway=crossing", "mappings": [ @@ -136,7 +137,8 @@ "nl": "Oversteekplaats, zonder verkeerslichten", "de": "Kreuzungen ohne Ampeln", "es": "Cruce, sin semáforos", - "fr": "Passage piéton, sans feux de signalisation" + "fr": "Passage piéton, sans feux de signalisation", + "ca": "Creuament, sense semàfors" } }, { @@ -146,7 +148,8 @@ "nl": "Oversteekplaats met verkeerslichten", "de": "Kreuzungen mit Ampeln", "es": "Cruce con señales de tráfico", - "fr": "Passage piéton avec des feux de signalisation" + "fr": "Passage piéton avec des feux de signalisation", + "ca": "Creuament amb senyals de trànsit" } }, { @@ -167,8 +170,9 @@ "en": "Crossing without crossing markings", "nl": "Oversteekplaats zonder kruispuntmarkeringen", "de": "Kreuzung ohne Kreuzungsmarkierungen", - "es": "Cruce sin marcas de cruce", - "fr": "Passage piéton sans marquages" + "es": "Cruce sin señalizar", + "fr": "Passage piéton sans marquages", + "ca": "Creuament sense senyalitzar" } } ] @@ -291,8 +295,9 @@ "en": "Does this crossing have tactile paving?", "nl": "Heeft deze oversteekplaats een geleidelijn?", "de": "Gibt es an dieser Kreuzung ein Blindenleitsystem?", - "es": "¿Tiene pavimento táctil este cruce?", - "fr": "Est-ce que ce passage piéton a une surface podotactile ?" + "es": "¿Este cruce tiene pavimento podotáctil?", + "fr": "Est-ce que ce passage piéton a une surface podotactile ?", + "ca": "Aquest creuament té superfície podotàctil?" }, "condition": "highway=crossing", "mappings": [ @@ -324,7 +329,7 @@ "en": "This crossing has tactile paving, but is not correct", "nl": "Deze oversteekplaats heeft een geleidelijn, die incorrect is.", "de": "Diese Kreuzung hat taktile Pflasterung, ist aber nicht korrekt", - "es": "Este cruce tiene superficie podotáctil, pero no es correcto", + "es": "Este cruce tiene superficie podotáctil, pero no correctamente", "fr": "Ce passage piéton a une surface podotactile, mais elle n'est pas adéquate", "ca": "Este creuament té superfície podotàctil, però no correctament" }, diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index 8c233be87..ba75935e0 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -5,7 +5,8 @@ "nl": "Fietspaden, straten en wegen", "de": "Radwege und Straßen", "fr": "Pistes cyclables et routes", - "es": "Carriles bici y carreteras" + "es": "Carriles bici y carreteras", + "ca": "Vies ciclistes i carreteres" }, "minzoom": 16, "source": { @@ -144,7 +145,8 @@ "nl": "Weg met fietspad naast de weg {name}", "en": "Road with cycleway next to the road {name}", "de": "Straße mit Radweg neben der Straße {name}", - "fr": "Route avec piste cyclable à côté de la route {name}" + "fr": "Route avec piste cyclable à côté de la route {name}", + "ca": "Carretera amb via ciclista al costat de la carretera {name}" } }, { @@ -155,7 +157,8 @@ "de": "Radweg neben der Straße", "fr": "Piste cyclable séparée de la route", "da": "Cykelsti ved siden af vejen", - "es": "Vía ciclista al lado de la carretera" + "es": "Vía ciclista al lado de la carretera", + "ca": "Via ciclista al costat de la carretera" } }, { @@ -169,7 +172,8 @@ "nl": "Fietsstraat {name}", "en": "Cyclestreet {name}", "de": "Fahrradstraße {name}", - "fr": "Rue cyclable {name}" + "fr": "Rue cyclable {name}", + "ca": "Ciclocarrer {name}" } }, { @@ -198,7 +202,8 @@ "nl": "Wat voor fietspad is hier?", "de": "Was für ein Radweg ist hier?", "es": "¿Qué tipo de carril bici hay aquí?", - "fr": "Quel type de piste cyclable il y a ici ?" + "fr": "Quel type de piste cyclable il y a ici ?", + "ca": "Quin tipus de via ciclista hi ha aquí?" }, "condition": { "and": [ @@ -215,7 +220,8 @@ "nl": "Er is een fietssuggestiestrook", "de": "Es gibt eine geteilte Fahrspur", "es": "Hay un carril compartido", - "fr": "Il y a une voie partagée" + "fr": "Il y a une voie partagée", + "ca": "Hi ha un carril compartit" } }, { @@ -236,7 +242,8 @@ "nl": "Er is een fietspad (los van de weg), maar geen fietspad afzonderlijk getekend naast deze weg.", "de": "Es gibt einen Weg, aber keinen Radweg, der auf der Karte getrennt von dieser Straße eingezeichnet ist.", "es": "Hay una pista, pero no hay un carril bici dibujado separado de esta carretera en el mapa.", - "fr": "Il y a une piste cyclable, mais elle n'est pas séparée de la route sur la carte." + "fr": "Il y a une piste cyclable, mais elle n'est pas séparée de la route sur la carte.", + "ca": "Hi ha una pista, però no hi ha una via ciclista dibuixada separada d'aquesta carretera al mapa." } }, { @@ -246,7 +253,8 @@ "nl": "Er is een apart getekend fietspad.", "de": "Hier ist ein getrennter Radweg vorhanden", "es": "Hay un carril bici dibujado por separado", - "fr": "Il y a une piste cyclable dessinée séparement" + "fr": "Il y a une piste cyclable dessinée séparement", + "ca": "Hi ha una via ciclista dibuixada per separat" } }, { @@ -256,7 +264,8 @@ "nl": "Er is geen fietspad aanwezig", "de": "Es gibt keinen Radweg", "es": "No hay carril bici", - "fr": "Il n'y a pas de piste cyclable" + "fr": "Il n'y a pas de piste cyclable", + "ca": "No hi ha via ciclista" }, "hideInAnswer": "cycleway=opposite" }, @@ -267,7 +276,8 @@ "nl": "Er is geen fietspad aanwezig", "de": "Es gibt keinen Radweg", "es": "No hay carril bici", - "fr": "Il n'y a pas de piste cyclable" + "fr": "Il n'y a pas de piste cyclable", + "ca": "No hi ha via ciclista" }, "hideInAnswer": "cycleway!=opposite", "addExtraTags": [ @@ -298,7 +308,8 @@ "de": "Diese Straße ist beleuchtet", "es": "La calle está iluminada", "fr": "Cette rue est éclairée", - "pl": "Ta ulica jest oświetlona" + "pl": "Ta ulica jest oświetlona", + "ca": "Aquest carrer està il·luminat" } }, { @@ -350,7 +361,8 @@ "nl": "Is dit een fietsstraat?", "de": "Ist das eine Fahrradstraße?", "es": "¿Esta es una ciclocalle?", - "fr": "Est-ce une route cyclable ?" + "fr": "Est-ce une route cyclable ?", + "ca": "Açò és un ciclocarrer?" }, "condition": { "and": [ @@ -367,7 +379,8 @@ "nl": "Dit is een fietsstraat, en dus een 30km/h zone", "de": "Dies ist eine Fahrradstraße in einer 30km/h Zone.", "es": "Esta es una ciclocalle, y una zona 30km/h.", - "fr": "C'est une rue cyclable, et une zone à 30 km/h." + "fr": "C'est une rue cyclable, et une zone à 30 km/h.", + "ca": "Açò és un ciclocarrer, i una zona de 30km/h." }, "addExtraTags": [ "overtaking:motor_vehicle=no", @@ -382,7 +395,8 @@ "nl": "Dit is een fietsstraat", "de": "Dies ist eine Fahrradstraße", "es": "Esta es una ciclocalle", - "fr": "Ceci est une route cyclable" + "fr": "Ceci est une route cyclable", + "ca": "Açò és un ciclocarrer" }, "hideInAnswer": "_country=be" }, @@ -393,7 +407,8 @@ "nl": "Dit is geen fietsstraat", "de": "Dies ist keine Fahrradstraße.", "es": "Esta no es una ciclocalle.", - "fr": "Ceci n'est pas une rue cyclable" + "fr": "Ceci n'est pas une rue cyclable", + "ca": "Açò no és un ciclocarrer." }, "addExtraTags": [ "overtaking:motor_vehicle=" @@ -433,7 +448,8 @@ "de": "Die Höchstgeschwindigkeit ist 20 km/h", "es": "La velocidad máxima es de 20km/h", "fr": "La vitesse maximum est de 20 km/h", - "pl": "Maksymalna prędkość tutaj to 20 km/h" + "pl": "Maksymalna prędkość tutaj to 20 km/h", + "ca": "La velocitat màxima és de 20km/h" } }, { @@ -444,7 +460,8 @@ "de": "Die Höchstgeschwindigkeit ist 30 km/h", "es": "La velocidad máxima es de 30km/h", "fr": "La vitesse maximum est de 30 km/h", - "pl": "Maksymalna prędkość tutaj to 30 km/h" + "pl": "Maksymalna prędkość tutaj to 30 km/h", + "ca": "La velocitat màxima és de 30km/h" } }, { @@ -455,7 +472,8 @@ "de": "Die Höchstgeschwindigkeit ist 50 km/h", "es": "La velocidad máxima es de 50km/h", "fr": "La vitesse maximum est de 50 km/h", - "pl": "Maksymalna prędkość tutaj to 50 km/h" + "pl": "Maksymalna prędkość tutaj to 50 km/h", + "ca": "La velocitat màxima és de 50km/h" } }, { @@ -467,7 +485,8 @@ "id": "Kecepatan maksimum 70 km/jam", "es": "La velocidad máxima es de 70km/h", "fr": "La vitesse maximum est de 70 km/h", - "pl": "Maksymalna prędkość tutaj to 70 km/h" + "pl": "Maksymalna prędkość tutaj to 70 km/h", + "ca": "La velocitat màxima és de 70km/h" } }, { @@ -502,7 +521,8 @@ "nl": "Dit fietspad is gemaakt van {cycleway:surface}", "de": "Der Radweg ist aus {cycleway:surface}", "es": "Este carril bici está hecho de {cycleway:surface}", - "fr": "Cette piste cyclable est faite de {cycleway:surface}" + "fr": "Cette piste cyclable est faite de {cycleway:surface}", + "ca": "Aquesta via ciclista està feta de {cycleway:surface}" }, "freeform": { "key": "cycleway:surface" @@ -522,7 +542,8 @@ "nl": "Dit fietspad is onverhard", "de": "Dieser Radweg hat keinen festen Belag", "es": "Este carril bici no está pavimentado", - "fr": "Cette piste cyclable n'est pas goudronnée" + "fr": "Cette piste cyclable n'est pas goudronnée", + "ca": "Aquesta via ciclista no està pavimentada" }, "hideInAnswer": true }, @@ -533,7 +554,8 @@ "nl": "Dit fietspad is geplaveid", "de": "Dieser Radweg hat einen festen Belag", "es": "Este carril bici está pavimentado", - "fr": "Cette piste cyclable est goudronée" + "fr": "Cette piste cyclable est goudronée", + "ca": "Aquesta via ciclista està pavimentada" }, "hideInAnswer": true }, @@ -544,7 +566,8 @@ "nl": "Dit fietspad is gemaakt van asfalt", "de": "Der Radweg ist aus Asphalt", "es": "Este carril bici está hecho de asfalto", - "fr": "Cette piste cyclable est asphaltée" + "fr": "Cette piste cyclable est asphaltée", + "ca": "Aquesta via ciclista està feta d'asfalt" } }, { @@ -564,7 +587,8 @@ "nl": "Dit fietspad is gemaakt van beton", "de": "Der Radweg ist aus Beton", "es": "Este carril bici está hecho de hormigón", - "fr": "Cette piste cyclable est bétonée" + "fr": "Cette piste cyclable est bétonée", + "ca": "Aquesta via ciclista està feta de formigó" } }, { @@ -602,7 +626,8 @@ "nl": "Dit fietspad is gemaakt van hout", "de": "Der Radweg ist aus Holz", "es": "Este carril bici está hecho de madera", - "fr": "Cette piste cyclable est faite en bois" + "fr": "Cette piste cyclable est faite en bois", + "ca": "Aquesta via ciclista està feta de fusta" } }, { @@ -650,7 +675,8 @@ "nl": "Waaruit is het oppervlak van het fietspad van gemaakt?", "de": "Was ist der Belag dieses Radwegs?", "es": "¿De qué superficie está hecho este carril bici?", - "fr": "De quoi est faite la surface de la piste cyclable ?" + "fr": "De quoi est faite la surface de la piste cyclable ?", + "ca": "De quina superfície està fet aquesta via ciclista?" }, "id": "Cycleway:surface" }, @@ -676,7 +702,8 @@ "en": "Usable for thin rollers: rollerblade, skateboard", "nl": "Geschikt voor fijne rollers: rollerblade, skateboard", "de": "Geeignet für dünne Rollen: Rollerblades, Skateboard", - "fr": "Utilisable pour les patins : patins à roulettes, skateboard" + "fr": "Utilisable pour les patins : patins à roulettes, skateboard", + "ca": "Utilitzable per a rodets prims: patins, monopatí" } }, { @@ -685,7 +712,8 @@ "en": "Usable for thin wheels: racing bike", "nl": "Geschikt voor fijne wielen: racefiets", "de": "Geeignet für dünne Reifen: Rennrad", - "fr": "Utilisable pour les roues fines : vélo de course" + "fr": "Utilisable pour les roues fines : vélo de course", + "ca": "Utilitzable per a rodes primes: bicicleta de carreres" } }, { @@ -695,7 +723,8 @@ "nl": "Geschikt voor normale wielen: stadsfiets, rolstoel, scooter", "de": "Geeignet für normale Reifen: Fahrrad, Rollstuhl, Scooter", "es": "Utilizable para ruedas normales: bici de ciudad, sillas de ruedas, scooter", - "fr": "Utilisable pour les roues traditionelles : vélo, chaise roulante, trotinettes" + "fr": "Utilisable pour les roues traditionelles : vélo, chaise roulante, trotinettes", + "ca": "Utilitzable per a rodes normals: bici de ciutat, cadira de rodes, patinets" } }, { @@ -704,7 +733,8 @@ "en": "Usable for robust wheels: trekking bike, car, rickshaw", "nl": "Geschikt voor brede wielen: trekfiets, auto, rickshaw", "de": "Geeignet für breite Reifen: Trekkingfahrrad, Auto, Rikscha", - "fr": "Utilisable pour les roues robustes : VTT, voitures, pousse-pousse" + "fr": "Utilisable pour les roues robustes : VTT, voitures, pousse-pousse", + "ca": "Utilitzable per a rodes robustes: bicicleta de senderisme, cotxe, rickshaw" } }, { @@ -713,7 +743,8 @@ "en": "Usable for vehicles with high clearance: light duty off-road vehicle", "nl": "Geschikt voor voertuigen met hoge banden: lichte terreinwagen", "de": "Geeignet für Fahrzeuge mit großer Bodenfreiheit: leichte Geländewagen", - "fr": "Utilisable pour les véhicules à dégagement élevé : véhicule tout-terrain léger" + "fr": "Utilisable pour les véhicules à dégagement élevé : véhicule tout-terrain léger", + "ca": "Utilitzable per a vehicles amb gran espai lliure: vehicle tot terreny lleuger" } }, { @@ -722,7 +753,8 @@ "en": "Usable for off-road vehicles: heavy duty off-road vehicle", "nl": "Geschikt voor terreinwagens: zware terreinwagen", "de": "Geeignet für Geländefahrzeuge: schwerer Geländewagen", - "fr": "Utilisable pour les véhicules tout-terrain : véhicule tout-terrain lourd" + "fr": "Utilisable pour les véhicules tout-terrain : véhicule tout-terrain lourd", + "ca": "Utilitzable per a vehicles tot terreny: vehicle tot terreny resistent" } }, { @@ -731,7 +763,8 @@ "en": "Usable for specialized off-road vehicles: tractor, ATV", "nl": "Geschikt voor gespecialiseerde terreinwagens: tractor, alleterreinwagen", "de": "Geeignet für Geländefahrzeuge: Traktor, ATV", - "fr": "Utilisable pour les véhicules hors route spécialisés : tracteur, véhicule 4x4" + "fr": "Utilisable pour les véhicules hors route spécialisés : tracteur, véhicule 4x4", + "ca": "Utilitzable per a vehicles tot terreny especialitzats: tractor, ATV" } }, { @@ -1025,7 +1058,8 @@ "en": "The carriage width of this road is {width:carriageway}m", "nl": "De breedte van deze rijbaan in deze straat is {width:carriageway}m", "de": "Die Fahrbahnbreite dieser Straße beträgt {width:carriageway}m", - "fr": "La largeur de cette chaussée est de {width:carriageway}m" + "fr": "La largeur de cette chaussée est de {width:carriageway}m", + "ca": "L'amplada dels carrils d'aquesta carretera és {width:carriageway}m" }, "freeform": { "key": "width:carriageway", @@ -1039,7 +1073,8 @@ "en": "What is the carriage width of this road (in meters)?", "nl": "Hoe breed is de rijbaan in deze straat (in meters)?", "de": "Wie groß ist die Fahrbahnbreite dieser Straße (in Metern)?", - "fr": "Quelle est la largeur de cette chaussée (en mètres) ?" + "fr": "Quelle est la largeur de cette chaussée (en mètres) ?", + "ca": "Quina és l'amplada dels carrils d'aquesta carretera (en metres)?" }, "id": "width:carriageway", "questionHint": { @@ -1058,7 +1093,8 @@ "de": "Welches Verkehrszeichen hat dieser Radweg?", "id": "Rambu lalu lintas apa yang dimiliki jalur sepeda ini?", "es": "¿Qué señal de tráfico tiene este carril bici?", - "fr": "Quel panneau de signalisation cette a cette piste cyclable ?" + "fr": "Quel panneau de signalisation cette a cette piste cyclable ?", + "ca": "Quina senyal de trànsit té aquesta via ciclista?" }, "condition": { "and": [ @@ -1099,7 +1135,8 @@ "nl": "Verplicht fietspad (met onderbord)
", "de": "Vorgeschriebener Radweg (mit Zusatzschild)
", "id": "Jalur sepeda wajib (dengan tanda tambahan)
", - "fr": "Piste cyclable obligatoire (avec panneau supplémentaire)" + "fr": "Piste cyclable obligatoire (avec panneau supplémentaire)", + "ca": "Ciclovia obligatòria (amb senyal suplementari)
" }, "hideInAnswer": true, "icon": { @@ -1155,7 +1192,8 @@ "de": "Kein Verkehrsschild vorhanden", "id": "Tidak ada rambu lalu lintas", "es": "Sin señal de tráfico", - "fr": "Aucun panneau de signalisation présent" + "fr": "Aucun panneau de signalisation présent", + "ca": "Sense senyal de trànsit" } } ] @@ -1167,7 +1205,8 @@ "nl": "Welk verkeersbord heeft dit fietspad?", "de": "Welches Verkehrszeichen hat dieser Radweg?", "es": "¿Qué seña de tráfico tiene este carril bici?", - "fr": "Quel panneau de signalisation cette a cette piste cyclable ?" + "fr": "Quel panneau de signalisation cette a cette piste cyclable ?", + "ca": "Quina senyal de trànsit té aquesta via ciclista?" }, "condition": { "and": [ @@ -1214,7 +1253,8 @@ "en": "Compulsory cycleway (with supplementary sign)
", "nl": "Verplicht fietspad (met onderbord)
", "de": "Vorgeschriebener Radweg (mit Zusatzschild)
", - "fr": "Piste cyclable obligatoire (avec panneau supplémentaire)" + "fr": "Piste cyclable obligatoire (avec panneau supplémentaire)", + "ca": "Ciclovia obligatòria (amb senyal suplementari)
" }, "hideInAnswer": true, "icon": { @@ -1293,7 +1333,8 @@ "en": "Compulsory (moped)cycleway", "nl": "Verplicht bromfietspad", "de": "Verpflichtender (Moped-)Radweg", - "fr": "Piste cyclable (cyclomoteur) obligatoire" + "fr": "Piste cyclable (cyclomoteur) obligatoire", + "ca": "Ciclovia (ciclomotor) obligatòria" }, "hideInAnswer": "_country!=nl", "addExtraTags": [ @@ -1311,7 +1352,8 @@ "en": "Non-compulsory cycleway", "nl": "Onverplicht fietspad", "de": "Radweg ohne Nutzungspflicht", - "fr": "Piste cyclable non obligatoire" + "fr": "Piste cyclable non obligatoire", + "ca": "via ciclista no obligatòria" }, "hideInAnswer": "_country!=nl", "addExtraTags": [ @@ -1329,7 +1371,8 @@ "en": "No traffic sign present", "nl": "Geen verkeersbord aanwezig", "de": "Kein Verkehrsschild vorhanden", - "fr": "Aucun panneau de signalisation présent" + "fr": "Aucun panneau de signalisation présent", + "ca": "No hi ha cap senyal de trànsit" } } ] @@ -1355,7 +1398,8 @@ "en": "Mopeds must use the cycleway", "nl": "Bromfiets Klass B verplicht op het fietspad", "de": "Mopeds müssen den Radweg benutzen", - "fr": "Les cyclomoteurs doivent utiliser la piste cyclable" + "fr": "Les cyclomoteurs doivent utiliser la piste cyclable", + "ca": "Els ciclomotors han d'utilitzar la via ciclista" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -1460,7 +1504,8 @@ "en": "No supplementary traffic sign present", "nl": "Geen onderbord aanwezig", "de": "Kein zusätzliches Verkehrszeichen vorhanden", - "fr": "Aucun panneau de signalisation supplémentaire présent" + "fr": "Aucun panneau de signalisation supplémentaire présent", + "ca": "No hi ha cap senyal de trànsit addicional" } } ] @@ -1503,7 +1548,8 @@ "de": "Wie ist der Radweg von der Straße abgegrenzt?", "id": "Bagaimana jalur sepeda ini terpisah dari jalan?", "es": "¿Cómo está separado este carril bici de la carretera?", - "fr": "Comment cette piste cyclable est-elle séparée de la route ?" + "fr": "Comment cette piste cyclable est-elle séparée de la route ?", + "ca": "Com està separada aquesta via ciclista de la carretera?" }, "condition": { "or": [ @@ -1520,7 +1566,8 @@ "de": "Der Radweg ist abgegrenzt durch eine gestrichelte Linie", "id": "Jalur sepeda ini dipisahkan oleh garis putus-putus", "es": "Este carril bici está separado por una línea discontinua", - "fr": "Cette piste cyclable est séparée par une ligne pointillée" + "fr": "Cette piste cyclable est séparée par une ligne pointillée", + "ca": "Aquesta via ciclista està separada per una línia discontínua" } }, { @@ -1531,7 +1578,8 @@ "de": "Der Radweg ist abgegrenzt durch eine durchgezogene Linie", "id": "Jalur sepeda ini dipisahkan oleh garis solid", "es": "Este carril bici está separado por una línea continua", - "fr": "Cette piste cyclable est séparée par une ligne continue" + "fr": "Cette piste cyclable est séparée par une ligne continue", + "ca": "Aquesta via ciclista està separat per una línia contínua" } }, { @@ -1542,7 +1590,8 @@ "de": "Der Radweg ist abgegrenzt durch eine Parkspur", "id": "Jalur sepeda ini dipisahkan oleh jalur parkir", "es": "Este carril bici está separado por una línea de aparcamiento", - "fr": "Cette piste cyclable est séparée par une voie de stationnement" + "fr": "Cette piste cyclable est séparée par une voie de stationnement", + "ca": "Aquesta via ciclista està separat per una línia d'aparcament" } }, { @@ -1552,7 +1601,8 @@ "nl": "Dit fietspad is gescheiden van de weg met een stoeprand", "de": "Dieser Radweg ist getrennt durch einen Bordstein", "id": "Jalur sepeda ini dipisahkan oleh kerb", - "fr": "Cette piste cyclable est séparée par une bordure" + "fr": "Cette piste cyclable est séparée par une bordure", + "ca": "Aquesta via ciclista està separada per una vorada" } } ] @@ -1565,7 +1615,8 @@ "de": "Wie ist der Radweg von der Straße abgegrenzt?", "id": "Bagaimana jalur sepeda ini dipisahkan dari jalan?", "es": "¿Cómo esta separado este carril bici de la carretera?", - "fr": "Comment cette piste cyclable est-elle séparée de la route ?" + "fr": "Comment cette piste cyclable est-elle séparée de la route ?", + "ca": "Com està separat aquesta via ciclista?" }, "condition": { "or": [ @@ -1582,7 +1633,8 @@ "de": "Der Radweg ist abgegrenzt durch eine gestrichelte Linie", "id": "Jalur sepeda ini dipisahkan oleh garis putus-putus", "es": "Este carril bici está separado por una línea discontinua", - "fr": "Cette piste cyclable est séparée par une ligne pointillée" + "fr": "Cette piste cyclable est séparée par une ligne pointillée", + "ca": "Aquesta via ciclista està separada per una línia discontinua" } }, { @@ -1593,7 +1645,8 @@ "de": "Der Radweg ist abgegrenzt durch eine durchgezogene Linie", "id": "Jalur sepeda ini dipisahkan oleh garis solid", "es": "Este carril bici está separado por una línea continua", - "fr": "Cette piste cyclable est séparée par une ligne continue" + "fr": "Cette piste cyclable est séparée par une ligne continue", + "ca": "Aquesta via ciclista està separada per una línia continua" } }, { @@ -1604,7 +1657,8 @@ "de": "Der Radweg ist abgegrenzt durch eine Parkspur", "id": "Jalur sepeda ini dipisahkan oleh jalur parkir", "es": "Este carril bici está separado por una línea de aparcamiento", - "fr": "Cette piste cyclable est séparée par une voie de stationnement" + "fr": "Cette piste cyclable est séparée par une voie de stationnement", + "ca": "Aquesta via ciclista està separada per una línia d'aparcament" } }, { @@ -1614,7 +1668,8 @@ "nl": "Dit fietspad is gescheiden van de weg met een stoeprand", "de": "Dieser Radweg ist getrennt durch einen Bordstein", "id": "Jalur sepeda ini dipisahkan oleh kerb", - "fr": "Cette piste cyclable est séparée par une bordure" + "fr": "Cette piste cyclable est séparée par une bordure", + "ca": "Aquesta via ciclista està separada per una vorada" } } ] diff --git a/assets/layers/doctors/doctors.json b/assets/layers/doctors/doctors.json index c2fbea24f..f0f955fac 100644 --- a/assets/layers/doctors/doctors.json +++ b/assets/layers/doctors/doctors.json @@ -13,7 +13,8 @@ "en": "This layer shows doctor offices", "de": "Diese Ebene zeigt Arztpraxen, Zahnärzte und andere Gesundheitseinrichtungen", "nl": "Deze laag toont dokterspraktijken", - "he": "שכבה זו מציגה מרפאות רופאים" + "he": "שכבה זו מציגה מרפאות רופאים", + "ca": "Aquesta capa mostra els consultoris mèdics" }, "source": { "osmTags": "amenity=doctors" @@ -23,7 +24,8 @@ "en": "Doctors Office {name}", "de": "Arztpraxis {name}", "nl": "Dokterspraktijk {name}", - "fr": "Cabinet medical {name}" + "fr": "Cabinet medical {name}", + "ca": "Consultori metge {name}" } }, "minzoom": 13, @@ -42,7 +44,8 @@ "en": "This doctors place is called {name}", "de": "Diese Arztpraxis heißt {name}", "nl": "Deze dokterspraktijk heet {name}", - "fr": "Ce cabinet médical s'appelle {name}" + "fr": "Ce cabinet médical s'appelle {name}", + "ca": "Aquest consultori mèdic és diu {name}" }, "freeform": { "key": "name" @@ -61,14 +64,16 @@ "nl": "Deze dokter is gespecialiseerd in {healthcare:speciality}", "fr": "Ce médecin est spécialisé dans {healthcare:speciality}", "he": "רופא זה מתמחה ב {healthcare:speciality}", - "pl": "Ten lekarz specjalizuje się w {healthcare:speciality}" + "pl": "Ten lekarz specjalizuje się w {healthcare:speciality}", + "ca": "Aquest metge està especialitzat en {healthcare:speciality}" }, "question": { "en": "What is this doctor specialized in?", "de": "Worauf ist dieser Arzt spezialisiert?", "nl": "Waar is deze dokter in gespecialiseerd?", "fr": "En quoi ce médecin est-il spécialisé ?", - "pl": "W czym specjalizuje się ten lekarz?" + "pl": "W czym specjalizuje się ten lekarz?", + "ca": "En què està especialitzat aquest metge?" }, "freeform": { "key": "healthcare:speciality" @@ -81,7 +86,8 @@ "en": "This is a general practitioner", "de": "Dies ist ein Allgemeinmediziner", "nl": "Dit is een huisarts", - "fr": "C'est un médecin généraliste" + "fr": "C'est un médecin généraliste", + "ca": "Açò és un metge generalista" } }, { @@ -91,7 +97,8 @@ "de": "Dies ist ein Gynäkologe", "nl": "Dit is een gynaecoloog", "fr": "C'est un gynécologue", - "pl": "To jest ginekolog" + "pl": "To jest ginekolog", + "ca": "Açò és un ginecòleg" } }, { @@ -101,7 +108,8 @@ "de": "Dies ist ein Psychiater", "nl": "Dit is een psychiater", "fr": "C'est un psychiatre", - "pl": "To jest psychiatra" + "pl": "To jest psychiatra", + "ca": "Açò és un psiquiatra" } }, { @@ -111,7 +119,8 @@ "de": "Dies ist ein Kinderarzt", "nl": "Dit is een kinderarts", "fr": "C'est un pédiatre", - "pl": "To jest pediatra" + "pl": "To jest pediatra", + "ca": "Açò és un pediatre" } } ] @@ -123,7 +132,8 @@ "en": "a doctors office", "de": "eine Arztpraxis", "nl": "een dokterspraktijk", - "fr": "un cabinet médical" + "fr": "un cabinet médical", + "ca": "un consultori mèdic" }, "tags": [ "amenity=doctors" diff --git a/assets/layers/dogpark/dogpark.json b/assets/layers/dogpark/dogpark.json index f760088b2..6d1b645ad 100644 --- a/assets/layers/dogpark/dogpark.json +++ b/assets/layers/dogpark/dogpark.json @@ -33,7 +33,8 @@ "da": "En hundeskov eller hundegård beregnet til hunde, hvor de kan løbe uden snor", "de": "Ein Ort ohne Leinenzwang für Hunde", "es": "Un lugar para perros, donde pueden correr sin correa", - "nl": "Een plaats waar honden vrij mogen rondlopen" + "nl": "Een plaats waar honden vrij mogen rondlopen", + "ca": "Un lloc per a gossos, on poden correr sense corretja" } } ], @@ -42,7 +43,8 @@ "da": "hundeskove", "de": "Hundeparks", "es": "parques de perros", - "nl": "hondenweides" + "nl": "hondenweides", + "ca": "parcs de gossos" }, "title": { "render": { @@ -50,7 +52,8 @@ "da": "hundeskov", "de": "Hundepark", "es": "parque para perros", - "nl": "hondenweide" + "nl": "hondenweide", + "ca": "parc per a gossos" }, "mappings": [ { @@ -106,7 +109,8 @@ "da": "Denne hundskov er ikke indhegnet", "de": "Dieser Hundepark ist nicht komplett umzäunt", "es": "Este parque para perros no está cerrado todo alrededor", - "nl": "Deze hondenweide is niet volledig omheind" + "nl": "Deze hondenweide is niet volledig omheind", + "ca": "Aquest parc de gossos no està tancat per tot el voltant" } } ], @@ -115,7 +119,8 @@ "da": "Er denne hundeskov indhegnet?", "de": "Ist dieser Hundepark umzäunt?", "es": "¿Este parque para perros está vallado?", - "nl": "Is deze hondenweide volledig omheind?" + "nl": "Is deze hondenweide volledig omheind?", + "ca": "Aquest parc de gossos està tancat?" } }, { @@ -128,7 +133,8 @@ "da": "Har en indhegning til hvalpe og små hunde", "de": "Hat einen separaten Bereich für Hundewelpen und kleine Hunde", "es": "Tiene un espacio separado para cachorros y perros pequeños", - "nl": "Heeft gescheiden deel voor puppy's en kleine honden" + "nl": "Heeft gescheiden deel voor puppy's en kleine honden", + "ca": "Té un espai separat per a cadells i gossos petits" } }, { @@ -138,7 +144,8 @@ "da": "Har ikke en indhegning til hvalpe og små hunde", "de": "Hat keinen separaten Bereich für Hundewelpen und kleine Hunde", "es": "No tiene un espacio separado para cachorros y perros pequeños", - "nl": "Heeft geen gescheiden deel voor puppy's en kleine honden" + "nl": "Heeft geen gescheiden deel voor puppy's en kleine honden", + "ca": "No té un espai separat per a cadells i gossos petits" } } ], @@ -147,7 +154,8 @@ "da": "Har denne hundeskov et separat indhegnet område for små hunde og hvalpe (hvalpegård)?", "de": "Hat der Hundepark einen separaten Bereich für Hundewelpen und kleine Hunde?", "es": "¿Este parque para perros tiene una zona separada vallada para perros pequeños y cachorros?", - "nl": "Heeft deze hondenweide een gescheiden en omheind deel voor puppy's en kleine honden?" + "nl": "Heeft deze hondenweide een gescheiden en omheind deel voor puppy's en kleine honden?", + "ca": "Aquest parc per a gossos té una zona tancada per a gossos petits i cadells?" } }, { diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 7b5d4a34f..af10dbe44 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -177,7 +177,8 @@ "de": "Ohne taktiles Pflaster", "fr": "Sans revêtement podotactile", "nl": "Zonder voelbare bestrating", - "pl": "Bez wypustek dla niewidomych" + "pl": "Bez wypustek dla niewidomych", + "ca": "Sense superfície podotàctil" }, "osmTags": "tactile_paving=no" }, diff --git a/assets/layers/postoffices/postoffices.json b/assets/layers/postoffices/postoffices.json index 0db761b05..d61face7a 100644 --- a/assets/layers/postoffices/postoffices.json +++ b/assets/layers/postoffices/postoffices.json @@ -414,28 +414,32 @@ "id": "has_atm", "question": { "en": "Does this post office have an ATM?", - "nl": "Heeft dit postkantoor een bankautomaat?" + "nl": "Heeft dit postkantoor een bankautomaat?", + "de": "Verfügt die Postfiliale über einen Geldautomat?" }, "mappings": [ { "if": "atm=yes", "then": { "en": "This post office has an ATM", - "nl": "Dit postkantoor heeft een bankautomaat" + "nl": "Dit postkantoor heeft een bankautomaat", + "de": "Die Postfiliale verfügt über einen Geldautomat" } }, { "if": "atm=no", "then": { "en": "This post office does not have an ATM", - "nl": "Dit postkantoor heeft geen bankautomaaat" + "nl": "Dit postkantoor heeft geen bankautomaaat", + "de": "Die Postfiliale verfügt nicht über einen Geldautomat" } }, { "if": "atm=separate", "then": { "en": "This post office does have an ATM, but it is mapped as a different icon", - "nl": "Dit postkantoor heeft een bankautomaat, maar deze staat apart op de kaart aangeduid" + "nl": "Dit postkantoor heeft een bankautomaat, maar deze staat apart op de kaart aangeduid", + "de": "Die Postfiliale verfügt über einen Geldautomat, der aber bereits separat kartiert ist" } } ] diff --git a/assets/layers/vending_machine/vending_machine.json b/assets/layers/vending_machine/vending_machine.json index 6bb13589c..3d305dae9 100644 --- a/assets/layers/vending_machine/vending_machine.json +++ b/assets/layers/vending_machine/vending_machine.json @@ -233,7 +233,8 @@ "if": "vending=parking_tickets", "then": { "en": "Parking tickets are sold", - "nl": "Parkeerkaarten worden verkocht" + "nl": "Parkeerkaarten worden verkocht", + "de": "Parkscheine werden verkauft" }, "icon": "./assets/layers/parking_ticket_machine/parking_tickets.svg" }, @@ -248,7 +249,8 @@ "if": "vending=public_transport_tickets", "then": { "en": "Public transport tickets are sold", - "nl": "Openbaar vervoerkaartjes worden verkocht" + "nl": "Openbaar vervoerkaartjes worden verkocht", + "de": "Fahrscheine werden verkauft" }, "icon": "./assets/themes/stations/public_transport_tickets.svg" } diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index dabbec8e1..ba9969aef 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,13 +1,20 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete" + "en": "Changes made with MapComplete", + "ca": "Canvis fets amb MapComplete", + "de": "Mit MapComplete erstellte Änderungen" }, "shortDescription": { - "en": "Shows changes made by MapComplete" + "en": "Shows changes made by MapComplete", + "ca": "Mostra els canvis fets amb MapComplete", + "de": "Mit MapComplete erstellte Änderungen anzeigen" }, "description": { - "en": "This maps shows all the changes made with MapComplete" + "en": "This maps shows all the changes made with MapComplete", + "ca": "Aquest mapa mostra tots els canvis fets amb MapComplete", + "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen", + "pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete" }, "icon": "./assets/svg/logo.svg", "hideFromOverview": true, @@ -20,7 +27,9 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers" + "en": "Changeset centers", + "ca": "Centre del conjunt de canvis", + "de": "Zentrum der Änderungssätze" }, "minzoom": 0, "source": { @@ -31,41 +40,56 @@ }, "title": { "render": { - "en": "Changeset for {theme}" + "en": "Changeset for {theme}", + "ca": "Conjunt de canvis per a {theme}", + "de": "Änderungssatz für {theme}" } }, "description": { - "en": "Shows all MapComplete changes" + "en": "Shows all MapComplete changes", + "ca": "Mostra tots els canvis de MapComplete", + "de": "Alle MapComplete-Änderungen anzeigen", + "pl": "Pokazuje wszystkie zmiany MapComplete" }, "tagRenderings": [ { "id": "show_changeset_id", "render": { - "en": "Changeset {id}" + "en": "Changeset {id}", + "ca": "Conjunt de canvi {id}", + "de": "Änderungssatz {id}" } }, { "id": "contributor", "question": { - "en": "What contributor did make this change?" + "en": "What contributor did make this change?", + "ca": "Quin col·laborador va fer aquest canvi?", + "de": "Wer hat diese Änderung vorgenommen?" }, "freeform": { "key": "user" }, "render": { - "en": "Change made by {user}" + "en": "Change made by {user}", + "ca": "Canvi fet per {user}", + "de": "Änderung von {user}" } }, { "id": "theme-id", "question": { - "en": "What theme was used to make this change?" + "en": "What theme was used to make this change?", + "ca": "Quin tema es va utilitzar per fer aquest canvi?", + "de": "Welches Thema wurde für diese Änderung verwendet?" }, "freeform": { "key": "theme" }, "render": { - "en": "Change with theme {theme}" + "en": "Change with theme {theme}", + "ca": "Canvi amb el tema {theme}", + "de": "Geändert mit Thema {theme}" } }, { @@ -74,19 +98,27 @@ "key": "locale" }, "question": { - "en": "What locale (language) was this change made in?" + "en": "What locale (language) was this change made in?", + "ca": "Amb quina configuració regional (idioma) s'ha fet aquest canvi?", + "de": "In welcher Benutzersprache wurde diese Änderung vorgenommen?" }, "render": { - "en": "User locale is {locale}" + "en": "User locale is {locale}", + "ca": "La configuració regional de l'usuari és {locale}", + "de": "Benutzersprache {locale}" } }, { "id": "host", "render": { - "en": "Change with with {host}" + "en": "Change with with {host}", + "ca": "Canviat amb {host}", + "de": "Änderung über {host}" }, "question": { - "en": "What host (website) was this change made with?" + "en": "What host (website) was this change made with?", + "ca": "Amb quin amfitrió (lloc web) es va fer aquest canvi?", + "de": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?" }, "freeform": { "key": "host" @@ -107,10 +139,16 @@ { "id": "version", "question": { - "en": "What version of MapComplete was used to make this change?" + "en": "What version of MapComplete was used to make this change?", + "ca": "Quina versió de MapComplete es va utilitzar per fer aquest canvi?", + "de": "Mit welcher Version von MapComplete wurde diese Änderung gemacht?", + "pl": "Która wersja MapComplete została wykorzystana, aby zrobić tę zmianę?" }, "render": { - "en": "Made with {editor}" + "en": "Made with {editor}", + "ca": "Fet amb {editor}", + "de": "Erstellt mit {editor}", + "pl": "Zrobione za pomocą {editor}" }, "freeform": { "key": "editor" @@ -452,7 +490,9 @@ } ], "question": { - "en": "Themename contains {search}" + "en": "Themename contains {search}", + "ca": "El nom del tema conté {search}", + "de": "Themenname enthält {search}" } } ] @@ -468,7 +508,9 @@ } ], "question": { - "en": "Made by contributor {search}" + "en": "Made by contributor {search}", + "ca": "Fet pel col·laborador {search}", + "de": "Erstellt von {search}" } } ] @@ -484,7 +526,9 @@ } ], "question": { - "en": "Not made by contributor {search}" + "en": "Not made by contributor {search}", + "ca": "No fet pel col·laborador {search}", + "de": "Nicht erstellt von {search}" } } ] @@ -501,7 +545,10 @@ } ], "question": { - "en": "Made before {search}" + "en": "Made before {search}", + "ca": "Fet abans de {search}", + "de": "Erstellt vor {search}", + "pl": "Stworzone przed {search}" } } ] @@ -518,7 +565,10 @@ } ], "question": { - "en": "Made after {search}" + "en": "Made after {search}", + "ca": "Fet després de {search}", + "de": "Erstellt nach {search}", + "pl": "Stworzone po {search}" } } ] @@ -534,7 +584,10 @@ } ], "question": { - "en": "User language (iso-code) {search}" + "en": "User language (iso-code) {search}", + "ca": "Idioma de l'usuari (codi iso) {search}", + "de": "Benutzersprache (ISO-Code) {search}", + "pl": "Język użytkownika (kod iso) {search}" } } ] @@ -550,7 +603,9 @@ } ], "question": { - "en": "Made with host {search}" + "en": "Made with host {search}", + "ca": "Fet amb l'amfitrió {search}", + "de": "Erstellt mit Host {search}" } } ] @@ -561,7 +616,10 @@ { "osmTags": "add-image>0", "question": { - "en": "Changeset added at least one image" + "en": "Changeset added at least one image", + "ca": "El conjunt de canvis ha afegit almenys una imatge", + "de": "Im Änderungssatz wurde mindestens ein Bild hinzugefügt", + "pl": "Zestaw zmian dodał co najmniej jedno zdjęcie" } } ] @@ -576,7 +634,9 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here" + "en": "More statistics can be found here", + "ca": "Es pot trobar més estadística aquí", + "de": "Mehr Statistiken gibt es hier" } }, { diff --git a/langs/layers/de.json b/langs/layers/de.json index 9008a2559..793e3762d 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -35,16 +35,6 @@ "1": { "title": "eine freistehende Posterbox" }, - "10": { - "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", - "title": "ein Schild" - }, - "11": { - "title": "eine Skulptur" - }, - "12": { - "title": "eine Wandmalerei" - }, "2": { "title": "eine wandmontierte Posterbox" }, @@ -71,6 +61,16 @@ }, "9": { "title": "ein Totem" + }, + "10": { + "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", + "title": "ein Schild" + }, + "11": { + "title": "eine Skulptur" + }, + "12": { + "title": "eine Wandmalerei" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Dies ist ein Brett" }, - "10": { - "then": "Dies ist eine Wandmalerei" - }, "2": { "then": "Dies ist eine Litfaßsäule" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Dies ist ein Totem" + }, + "10": { + "then": "Dies ist eine Wandmalerei" } }, "question": "Welche Art von Werbung ist das?", @@ -205,9 +205,6 @@ "1": { "then": "Brett" }, - "10": { - "then": "Wandmalerei" - }, "2": { "then": "Posterbox" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Totem" + }, + "10": { + "then": "Wandmalerei" } } } @@ -312,15 +312,6 @@ "1": { "then": "Wandbild" }, - "10": { - "then": "Azulejo (spanische dekorative Fliesenarbeit)" - }, - "11": { - "then": "Fliesenarbeit" - }, - "12": { - "then": "Holzschnitzerei" - }, "2": { "then": "Malerei" }, @@ -344,6 +335,15 @@ }, "9": { "then": "Relief" + }, + "10": { + "then": "Azulejo (spanische dekorative Fliesenarbeit)" + }, + "11": { + "then": "Fliesenarbeit" + }, + "12": { + "then": "Holzschnitzerei" } }, "question": "Um welche Art Kunstwerk handelt es sich?", @@ -1830,27 +1830,6 @@ "1": { "question": "Verfügt über einen
Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)
" }, - "10": { - "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" - }, - "11": { - "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" - }, - "12": { - "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" - }, - "13": { - "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" - }, - "14": { - "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" - }, - "15": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" - }, - "16": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" - }, "2": { "question": "Verfügt über einen
europäischen Netzstecker mit Erdungsstift (CEE7/4 Typ E)
Anschluss" }, @@ -1874,6 +1853,27 @@ }, "9": { "question": "Hat einen
Typ 2 CCS (Mennekes)
Anschluss" + }, + "10": { + "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" + }, + "11": { + "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" + }, + "12": { + "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" + }, + "13": { + "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" + }, + "14": { + "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" + }, + "15": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" + }, + "16": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" } } } @@ -1929,6 +1929,30 @@ "1": { "then": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)" }, + "2": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, + "3": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, + "4": { + "then": "Chademo-Anschluss" + }, + "5": { + "then": "Chademo-Anschluss" + }, + "6": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "7": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "8": { + "then": "Typ 1 ohne Kabel (J1772)" + }, + "9": { + "then": " Typ 1 ohne Kabel (J1772)" + }, "10": { "then": "Typ 1 CCS (Typ 1 Combo)" }, @@ -1959,9 +1983,6 @@ "19": { "then": "Typ 2 mit Kabel (mennekes)" }, - "2": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, "20": { "then": "Tesla Supercharger CCS (Typ 2 CSS von Tesla)" }, @@ -1992,32 +2013,11 @@ "29": { "then": " Bosch Active Connect mit 3 Pins und Kabel" }, - "3": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, "30": { "then": "Bosch Active Connect mit 5 Pins und Kabel" }, "31": { "then": " Bosch Active Connect mit 5 Pins und Kabel" - }, - "4": { - "then": "Chademo-Anschluss" - }, - "5": { - "then": "Chademo-Anschluss" - }, - "6": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "7": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "8": { - "then": "Typ 1 ohne Kabel (J1772)" - }, - "9": { - "then": " Typ 1 ohne Kabel (J1772)" } }, "question": "Welche Ladeanschlüsse gibt es hier?" @@ -3555,15 +3555,6 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" - }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3587,6 +3578,15 @@ }, "9": { "then": "Der Radweg ist aus Schotter" + }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieses Radwegs?", @@ -3635,15 +3635,6 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" - }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3667,6 +3658,15 @@ }, "9": { "then": "Der Radweg ist aus Schotter" + }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieser Straße?", @@ -4705,6 +4705,30 @@ "1": { "then": "Die Fitness-Station hat ein Schild mit Anweisungen für eine bestimmte Übung." }, + "2": { + "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." + }, + "3": { + "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." + }, + "4": { + "then": "Die Fitness-Station hat Stangen zum Dehnen." + }, + "5": { + "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." + }, + "6": { + "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." + }, + "7": { + "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." + }, + "8": { + "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." + }, + "9": { + "then": "Die Fitness-Station hat Pfosten für Slalomübungen." + }, "10": { "then": "Die Fitness-Station hat Trittsteine." }, @@ -4735,9 +4759,6 @@ "19": { "then": "Die Fitness-Station hat Kampfseile (battle ropes)." }, - "2": { - "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." - }, "20": { "then": "Die Fitness-Station hat ein Fahrradergometer." }, @@ -4752,27 +4773,6 @@ }, "24": { "then": "Die Fitness-Station hat eine Slackline." - }, - "3": { - "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." - }, - "4": { - "then": "Die Fitness-Station hat Stangen zum Dehnen." - }, - "5": { - "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." - }, - "6": { - "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." - }, - "7": { - "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." - }, - "8": { - "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." - }, - "9": { - "then": "Die Fitness-Station hat Pfosten für Slalomübungen." } }, "question": "Welche Übungsgeräte gibt es an dieser Fitness-Station?" @@ -4892,21 +4892,6 @@ "1": { "then": "Dies ist eine Pommesbude" }, - "10": { - "then": "Hier werden chinesische Gerichte serviert" - }, - "11": { - "then": "Hier werden griechische Gerichte serviert" - }, - "12": { - "then": "Hier werden indische Gerichte serviert" - }, - "13": { - "then": "Hier werden türkische Gerichte serviert" - }, - "14": { - "then": "Hier werden thailändische Gerichte serviert" - }, "2": { "then": "Bietet vorwiegend Pastagerichte an" }, @@ -4930,6 +4915,21 @@ }, "9": { "then": "Hier werden französische Gerichte serviert" + }, + "10": { + "then": "Hier werden chinesische Gerichte serviert" + }, + "11": { + "then": "Hier werden griechische Gerichte serviert" + }, + "12": { + "then": "Hier werden indische Gerichte serviert" + }, + "13": { + "then": "Hier werden türkische Gerichte serviert" + }, + "14": { + "then": "Hier werden thailändische Gerichte serviert" } }, "question": "Was für Essen gibt es hier?", @@ -6145,19 +6145,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Alle Notizen" - }, - "1": { - "question": "Importnotizen ausblenden" - }, - "2": { - "question": "Nur Importnotizen anzeigen" - } - } - }, "2": { "options": { "0": { @@ -6213,6 +6200,19 @@ "question": "Nur offene Notizen anzeigen" } } + }, + "10": { + "options": { + "0": { + "question": "Alle Notizen" + }, + "1": { + "question": "Importnotizen ausblenden" + }, + "2": { + "question": "Nur Importnotizen anzeigen" + } + } } }, "name": "OpenStreetMap-Hinweise", @@ -6541,21 +6541,6 @@ "1": { "then": "Dies ist ein normaler Stellplatz." }, - "10": { - "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." - }, - "11": { - "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." - }, - "12": { - "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." - }, - "13": { - "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." - }, - "14": { - "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." - }, "2": { "then": "Dies ist ein Behindertenstellplatz." }, @@ -6579,6 +6564,21 @@ }, "9": { "then": "Dies ist ein Stellplatz, der für Motorräder reserviert ist." + }, + "10": { + "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." + }, + "11": { + "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." + }, + "12": { + "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." + }, + "13": { + "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." + }, + "14": { + "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." } }, "question": "Welche Art von Stellplatz ist dies?" @@ -7587,6 +7587,30 @@ "1": { "question": "Recycling von Batterien" }, + "2": { + "question": "Recycling von Getränkekartons" + }, + "3": { + "question": "Recycling von Dosen" + }, + "4": { + "question": "Recycling von Kleidung" + }, + "5": { + "question": "Recycling von Speiseöl" + }, + "6": { + "question": "Recycling von Motoröl" + }, + "7": { + "question": "Recycling von Leuchtstoffröhren" + }, + "8": { + "question": "Recycling von Grünabfällen" + }, + "9": { + "question": "Recycling von Glasflaschen" + }, "10": { "question": "Recycling von Glas" }, @@ -7617,35 +7641,11 @@ "19": { "question": "Recycling von Restabfällen" }, - "2": { - "question": "Recycling von Getränkekartons" - }, "20": { "question": "Recycling von Druckerpatronen" }, "21": { "question": "Recycling von Fahrrädern" - }, - "3": { - "question": "Recycling von Dosen" - }, - "4": { - "question": "Recycling von Kleidung" - }, - "5": { - "question": "Recycling von Speiseöl" - }, - "6": { - "question": "Recycling von Motoröl" - }, - "7": { - "question": "Recycling von Leuchtstoffröhren" - }, - "8": { - "question": "Recycling von Grünabfällen" - }, - "9": { - "question": "Recycling von Glasflaschen" } } }, @@ -7713,6 +7713,30 @@ "1": { "then": "Getränkekartons können hier recycelt werden" }, + "2": { + "then": "Dosen können hier recycelt werden" + }, + "3": { + "then": "Kleidung kann hier recycelt werden" + }, + "4": { + "then": "Speiseöl kann hier recycelt werden" + }, + "5": { + "then": "Motoröl kann hier recycelt werden" + }, + "6": { + "then": "Hier können Leuchtstoffröhren recycelt werden" + }, + "7": { + "then": "Grünabfälle können hier recycelt werden" + }, + "8": { + "then": "Bio-Abfall kann hier recycelt werden" + }, + "9": { + "then": "Glasflaschen können hier recycelt werden" + }, "10": { "then": "Glas kann hier recycelt werden" }, @@ -7743,9 +7767,6 @@ "19": { "then": "Schuhe können hier recycelt werden" }, - "2": { - "then": "Dosen können hier recycelt werden" - }, "20": { "then": "Elektrokleingeräte können hier recycelt werden" }, @@ -7760,27 +7781,6 @@ }, "24": { "then": "Fahrräder können hier recycelt werden" - }, - "3": { - "then": "Kleidung kann hier recycelt werden" - }, - "4": { - "then": "Speiseöl kann hier recycelt werden" - }, - "5": { - "then": "Motoröl kann hier recycelt werden" - }, - "6": { - "then": "Hier können Leuchtstoffröhren recycelt werden" - }, - "7": { - "then": "Grünabfälle können hier recycelt werden" - }, - "8": { - "then": "Bio-Abfall kann hier recycelt werden" - }, - "9": { - "then": "Glasflaschen können hier recycelt werden" } }, "question": "Was kann hier recycelt werden?" @@ -8584,12 +8584,6 @@ "1": { "then": "Diese Straßenlaterne verwendet LEDs" }, - "10": { - "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" - }, - "11": { - "then": "Diese Straßenlaterne wird mit Gas beleuchtet" - }, "2": { "then": "Diese Straßenlaterne verwendet Glühlampenlicht" }, @@ -8613,6 +8607,12 @@ }, "9": { "then": "Diese Straßenlaterne verwendet Niederdruck-Natriumdampflampen (einfarbig orange)" + }, + "10": { + "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" + }, + "11": { + "then": "Diese Straßenlaterne wird mit Gas beleuchtet" } }, "question": "Mit welcher Art von Beleuchtung arbeitet diese Straßenlaterne?" @@ -9701,27 +9701,6 @@ "1": { "question": "Verkauf von Getränken" }, - "10": { - "question": "Verkauf von Milch" - }, - "11": { - "question": "Verkauf von Brot" - }, - "12": { - "question": "Verkauf von Eiern" - }, - "13": { - "question": "Verkauf von Käse" - }, - "14": { - "question": "Verkauf von Honig" - }, - "15": { - "question": "Verkauf von Kartoffeln" - }, - "16": { - "question": "Verkauf von Blumen" - }, "2": { "question": "Verkauf von Süßigkeiten" }, @@ -9745,6 +9724,27 @@ }, "9": { "question": "Verkauf von Fahrradschläuchen" + }, + "10": { + "question": "Verkauf von Milch" + }, + "11": { + "question": "Verkauf von Brot" + }, + "12": { + "question": "Verkauf von Eiern" + }, + "13": { + "question": "Verkauf von Käse" + }, + "14": { + "question": "Verkauf von Honig" + }, + "15": { + "question": "Verkauf von Kartoffeln" + }, + "16": { + "question": "Verkauf von Blumen" } } } @@ -9785,30 +9785,6 @@ "1": { "then": "Süßigkeiten werden verkauft" }, - "10": { - "then": "Brot wird verkauft" - }, - "11": { - "then": "Eier werden verkauft" - }, - "12": { - "then": "Käse wird verkauft" - }, - "13": { - "then": "Honig wird verkauft" - }, - "14": { - "then": "Kartoffeln werden verkauft" - }, - "15": { - "then": "Blumen werden verkauft" - }, - "16": { - "then": "Parkscheine werden verkauft" - }, - "18": { - "then": "Fahrscheine werden verkauft" - }, "2": { "then": "Lebensmittel werden verkauft" }, @@ -9832,6 +9808,30 @@ }, "9": { "then": "Milch wird verkauft" + }, + "10": { + "then": "Brot wird verkauft" + }, + "11": { + "then": "Eier werden verkauft" + }, + "12": { + "then": "Käse wird verkauft" + }, + "13": { + "then": "Honig wird verkauft" + }, + "14": { + "then": "Kartoffeln werden verkauft" + }, + "15": { + "then": "Blumen werden verkauft" + }, + "16": { + "then": "Parkscheine werden verkauft" + }, + "18": { + "then": "Fahrscheine werden verkauft" } }, "question": "Was wird in diesem Automaten verkauft?", @@ -10168,4 +10168,4 @@ } } } -} +} \ No newline at end of file From a97740b36e0fd2f653938b810175e5cbd58b485c Mon Sep 17 00:00:00 2001 From: paunofu Date: Tue, 19 Sep 2023 08:54:16 +0000 Subject: [PATCH 005/133] Translated using Weblate (Catalan) Currently translated at 72.2% (2238 of 3096 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 1211 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 1054 insertions(+), 157 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index a612bbe4b..5c3fa0592 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" + }, "2": { "title": "un mupi sobre la paret" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tòtem" - }, - "10": { - "description": "S'utilitza per a cartells publicitaris, retols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Açò és un tauló d'anunis" }, + "10": { + "then": "Açò és una paret pintada" + }, "2": { "then": "Açò és una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Açò és un tòtem" - }, - "10": { - "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,6 +205,9 @@ "1": { "then": "Tauló d'anuncis" }, + "10": { + "then": "Paret Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tòtem" - }, - "10": { - "then": "Paret Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relleu" - }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -1816,6 +1816,27 @@ "1": { "question": "Té un connector
Schuko sense pin de terra (CEE7/4 tipus F)
connector" }, + "10": { + "question": "Té un connector
Tipus 2 amb cable (mennekes)
" + }, + "11": { + "question": "Té un connector
Tesla Supercharger CCS (un tipus2_css de marca)
" + }, + "12": { + "question": "Té un connector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" + }, + "14": { + "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" + }, + "15": { + "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" + }, + "16": { + "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" + }, "2": { "question": "Té un connector
endoll de paret Europeu amb un pin de terra (CEE7/4 tipus F)
" }, @@ -1839,27 +1860,6 @@ }, "9": { "question": "Té un connector
Tipus 2 CCS (mennekes)
" - }, - "10": { - "question": "Té un connector
Tipus 2 amb cable (mennekes)
" - }, - "11": { - "question": "Té un connector
Tesla Supercharger CCS (un tipus2_css de marca)
" - }, - "12": { - "question": "Té un connector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" - }, - "14": { - "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" - }, - "15": { - "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" - }, - "16": { - "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" } } } @@ -1915,30 +1915,6 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, - "2": { - "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" - }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipus 1 amb cable (J1772)" - }, - "7": { - "then": "Tipus 1 amb cable (J1772)" - }, - "8": { - "then": "Tipus 1 sense cable (J1772)" - }, - "9": { - "then": "Tipus 1 sense cable (J1772)" - }, "10": { "then": "CSS 1Tipus 1 (també conegut com Tipus 1 combo)" }, @@ -1969,6 +1945,9 @@ "19": { "then": "Tipus 2 amb cable (mennekes)" }, + "2": { + "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" + }, "20": { "then": "CSS Supercarregador Tesla (tipus2_css de la marca)" }, @@ -1999,11 +1978,32 @@ "29": { "then": "Bosch Active Connect amb 3 pins i cable" }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, "30": { "then": "Bosch Active Connect amb 5 pins i cable" }, "31": { "then": "Bosch Active Connect amb 5 pins i cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipus 1 amb cable (J1772)" + }, + "7": { + "then": "Tipus 1 amb cable (J1772)" + }, + "8": { + "then": "Tipus 1 sense cable (J1772)" + }, + "9": { + "then": "Tipus 1 sense cable (J1772)" } }, "question": "Quins tipus de connexions de càrrega estan disponibles aquí?" @@ -2252,10 +2252,19 @@ "question": "Quin voltatge ofereixen els endolls amb
Tipus 2 CCS (mennekes)
?" }, "website": { - "question": "Quina és la pàgina web on es pot trobar més informació sobre aquest punt de recàrrega?" + "question": "Quina és la pàgina web on es pot trobar més informació sobre aquest punt de recàrrega?", + "render": "Més info a {website}" } }, "title": { + "mappings": { + "0": { + "then": "Estació de càrrega per a bicicletes elèctriques" + }, + "1": { + "then": "Estació de càrrega per a cotxes" + } + }, "render": "Estació de càrrega" }, "units": { @@ -2341,7 +2350,18 @@ }, "climbing_area": { "description": "Una àrea on l'escalada és possible, p.e. una penya, lloc, bloc, … Conté l'agregació de rutes", + "name": "Oportunitats d'escalada", + "presets": { + "0": { + "description": "Una oportunitat d'escalada", + "title": "una oportuinitat d'escalada" + } + }, "tagRenderings": { + "Rock type (crag/rock/cliff only)": { + "question": "Quin és el tipus de roca aquí?", + "render": "El tipus de roca és {rock}" + }, "name": { "render": "{name}" } @@ -2350,11 +2370,23 @@ "mappings": { "2": { "then": "Llocs d'escalada" + }, + "3": { + "then": "Oportunitat d'escalada {name}" } - } + }, + "render": "Oportunitat d'escalada" } }, "climbing_club": { + "description": "Un club d'escalada o organització", + "name": "Club d'escalada", + "presets": { + "0": { + "description": "Un club d'escalada", + "title": "un club d'escalada" + } + }, "tagRenderings": { "climbing_club-name": { "render": "{name}" @@ -2532,8 +2564,8 @@ "title": "un creuament" }, "1": { - "description": "Senyal de trànsit en una carretera", - "title": "una senyal de trànsit" + "description": "Semàfor en una carretera", + "title": "un semàfor" } }, "tagRenderings": { @@ -2668,7 +2700,7 @@ "then": "Creuament, sense semàfors" }, "1": { - "then": "Creuament amb senyals de trànsit" + "then": "Creuament amb semàfors" }, "2": { "then": "Pas de zebra" @@ -2684,6 +2716,9 @@ "mappings": { "0": { "then": "Semàfor" + }, + "1": { + "then": "Creuament amb semàfors" } }, "render": "Encreuament" @@ -2802,6 +2837,9 @@ "1": { "then": "Aquest carril bici està pavimentat" }, + "10": { + "then": "Aquesta via ciclista està feta de gravilla" + }, "2": { "then": "Aquest carril bici està fet d'asfalt" }, @@ -2810,6 +2848,9 @@ }, "8": { "then": "Aquest carril bici està fet de fusta" + }, + "9": { + "then": "Aquesta via ciclista està feta de grava" } }, "question": "De què està feta la superfície d'aquest carrer?", @@ -3056,7 +3097,8 @@ "render": "Informació addicional: {description}" }, "defibrillator-email": { - "question": "Quin és el correu electrònic on preguntar sobre aquest desfibril·lador?" + "question": "Quin és el correu electrònic on preguntar sobre aquest desfibril·lador?", + "render": "Correu electrònic per a preguntes sobre aquest desfibrilador: {email}" }, "defibrillator-fixme": { "question": "Hi ha alguna cosa malament en la manera de com està mapejat això, que no heu pogut solucionar aquí? (deixeu una nota als experts d'OpenStreetMap)", @@ -3113,15 +3155,25 @@ } }, "dentist": { + "description": "Aquesta capa mostra clíniques dentals", "name": "Dentista", + "presets": { + "0": { + "title": "una clínica dental" + } + }, "tagRenderings": { "name": { "question": "Com s'anomena aquest dentista?", "render": "El dentista s'anomena {name}" } + }, + "title": { + "render": "Clínica Dental {name}" } }, "direction": { + "description": "Aquesta capa visualitza direccions", "name": "Direcció de la visualització" }, "doctors": { @@ -3257,6 +3309,7 @@ } }, "elevator": { + "description": "Aquesta capa mostra ascensors i demana l'estat operatiu i les dimensions de l'ascensor. Útil per obtenir informació sobre l'accessibilitat en cadira de rodes", "name": "Ascensor", "presets": { "0": { @@ -3265,22 +3318,188 @@ }, "tagRenderings": { "door-width": { - "question": "Quina és l'amplada de l'entrada d'aquest ascensor?" + "question": "Quina és l'amplada de l'entrada d'aquest ascensor?", + "render": "Les portes de l'ascensor tenen un amplada de {canonical(door:width)}" + }, + "elevator-depth": { + "question": "Quina és la profunditat de l'ascensor?", + "render": "L'ascensor té un profunditat de {canonical(elevator:depth)}" + }, + "elevator-width": { + "question": "Quina és l'amplada d'aquest ascensor?", + "render": "L'ascensor té una amplada de {canonical(elevator:width)}" }, "operational_status": { "mappings": { + "0": { + "then": "L'ascensor està trencat" + }, + "1": { + "then": "L'ascensor està tancat p.e. perquè s'estan fent obres de reforma" + }, + "2": { + "then": "L'ascesor funciona" + }, "3": { "then": "Aquest ascensor funciona" } + }, + "question": "Funciona aquest ascensor?" + }, + "speech_output": { + "render": { + "special": { + "question": "Aquest ascensor en quins idiomes té sortida de veu?", + "render_list_item": "Aquest ascensor té sortida de veu en {language():font-bold}", + "render_single_language": "Aquest ascensor té sortida de veu en {language():font-bold}" + } + } + }, + "tactile_writing_language": { + "render": { + "special": { + "question": "Aquest ascensor en quins idiomes té l'escriptura tàctil (braille)?", + "render_list_item": "Aquest ascensor té l'escriptura tàctil en language():font-bold}", + "render_single_language": "Aquest ascensor té l'escriptura tàctil en language():font-bold}" + } + } + } + }, + "title": { + "render": "Ascensor" + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": "metre" + }, + "1": { + "human": "centímetre" + } } } } }, + "elongated_coin": { + "description": "Capa que mostra premses de cèntims.", + "name": "Premses de cèntims", + "presets": { + "0": { + "title": "una premsa de cèntim" + } + }, + "tagRenderings": { + "charge": { + "freeform": { + "placeholder": "Cost (p. e, 0.50 euros)" + }, + "mappings": { + "0": { + "then": "Costa 1 euro premsar un cèntim." + }, + "1": { + "then": "Costa 2 euros premsar un cèntim." + } + }, + "question": "Quant costa premsar un cèntim?", + "render": "Costa {charge} premsar un cèntim." + }, + "coin": { + "freeform": { + "placeholder": "Tipus de moneda (p. e. 10 cèntims)" + }, + "mappings": { + "0": { + "then": "Esta premsa de cèntims utilitza una moneda de 2 cèntims per a premsar." + }, + "1": { + "then": "Esta premsa de cèntims utilitza una moneda de 5 cèntims per a premsar." + }, + "2": { + "then": "Esta premsa de cèntims utilitza una moneda de 10 cèntims per a premsar." + }, + "3": { + "then": "Esta premsa de cèntims utilitza una moneda de 20 cèntims per a premsar." + }, + "4": { + "then": "Esta premsa de cèntims utilitza una moneda de 50 cèntims per a premsar." + } + }, + "question": "Quina moneda s'utilitza per a premsar?", + "render": "Esta premsa de cèntims utilitza una moneda de {coin:type} per a premsar." + }, + "designs": { + "freeform": { + "placeholder": "Nombre de dissenys (p. e. 5)" + }, + "mappings": { + "0": { + "then": "Esta premsa té un disseny disponible." + }, + "1": { + "then": "Esta premsa té dos dissenys disponibles." + }, + "2": { + "then": "Esta premsa té tres dissenys disponibles." + }, + "3": { + "then": "Esta premsa té quatre dissenys disponibles." + } + }, + "question": "Quants dissenys hi han disponibles?", + "render": "Esta premsa té {coin:design_count} dissenys disponibles." + }, + "indoor": { + "mappings": { + "0": { + "then": "Esta premsa està ubicada en interior." + }, + "1": { + "then": "Esta premsa està ubicada a l'exterior." + } + }, + "question": "La premsa de cèntims està a l'interior?" + } + }, + "title": { + "render": "Premsa de cèntims" + } + }, "entrance": { "description": "Una capa que mostra entrades i ofereix la possibilitat de sondejar algunes dades avançades que són importants per a, per exemple, usuaris de cadires de rodes (però també ciclistes, repartidors, …)", "name": "Entrada", + "presets": { + "0": { + "title": "una entrada" + }, + "1": { + "title": "una porta interior" + } + }, "tagRenderings": { "Door_type": { + "mappings": { + "0": { + "then": "El tipus de porta és desconegut" + }, + "1": { + "then": "Una porta clàssica, amb frontisses suportades per unions" + }, + "2": { + "then": "Una porta giratòria que penja d'un eix central i que rota dins d'una carcasa cilíndrica" + }, + "3": { + "then": "Una porta corredissa on la porta llisca cap als costats, normalment paral·lela a una paret" + }, + "4": { + "then": "Una porta que roda des de dalt, normalment es veu per als garatges" + }, + "5": { + "then": "Açò és una entrada sense una porta física" + } + }, + "question": "Quin és el tipus d'aquesta porta?", "questionHint": "A la pregunta següent es pregunta si la porta està automatitzada o no" }, "Entrance type": { @@ -3288,37 +3507,118 @@ "0": { "then": "No es coneix cap tipus d'entrada específica" }, + "1": { + "then": "Es tracta d'una porta interior, que separa una habitació o un passadís dins d'un únic edifici" + }, + "2": { + "then": "Aquesta és l'entrada principal" + }, + "3": { + "then": "Aquesta és una entrada secundària" + }, "4": { "then": "Aquesta és una entrada de servei - normalment utilitzada per empleats, repartidors, …" + }, + "5": { + "then": "Aquesta és una sortida on no es pot entrar" + }, + "6": { + "then": "Aquesta és una entrada per on només es pot entrar (però no sortir)" + }, + "7": { + "then": "Aquesta és la sortida d'emergència" + }, + "8": { + "then": "Aquesta és l'entrada d'una casa particular" } - } + }, + "question": "Quin tipus d'entrada és aquesta?" }, "automatic_door": { "mappings": { + "0": { + "then": "Aquesta és una porta automàtica" + }, "1": { "then": "Aquesta porta no està automatitzada" }, + "2": { + "then": "Aquesta porta s'obrirà automàticament quan es detecti moviment" + }, + "3": { + "then": "Aquesta porta s'obrirà automàticament quan s'active un sensor al terra" + }, + "4": { + "then": "Aquesta porta s'obrirà automàticament quan es prem un botó" + }, + "5": { + "then": "Aquesta porta gira automàticament tot el temps, però té un botó per a frenar-la, p. e. per a usuaris de cadira de rodes" + }, + "6": { + "then": "Aquesta porta gira automàticament tot el temps" + }, "7": { "then": "Aquesta porta l'obri el personal quan es sol·licita polsant un botó" + }, + "8": { + "then": "Aquesta porta l'obrirà el personal quan es sol·liciti" } } }, + "kerb-height": { + "freeform": { + "placeholder": "Alçada de la vorada de la porta" + }, + "mappings": { + "0": { + "then": "Aquesta porta no té voral" + } + }, + "question": "Quina és l'altura d'aquest voral?", + "render": "L'alçada del voral d'aquesta porta és {kerb:height}" + }, "width": { + "question": "Quina és l'amplada d'aquesta porta/entrada?", "render": "Aquesta porta té una amplària de {canonical(width)}" } }, "title": { "render": "Entrada" + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": "metre" + }, + "1": { + "human": "centimetre" + } + } + } } }, "etymology": { + "description": "Tots els objectes que tenen una etimologia coneguda", + "name": "Té etimologia", "tagRenderings": { + "etymology_multi_apply": { + "render": "{multi_apply(_same_name_ids, name:etymology:wikidata;name:etimology, aplicació automàtica de dades a tots els segments amb el mateix nom, vertader)}" + }, "simple etymology": { + "mappings": { + "0": { + "then": "L'origen d'aquest nom és desconegut en tota la literatura" + } + }, "questionHint": "Això podria estar escrit al cartell del nom del carrer" }, "street-name-sign-image": { "render": "{image_carousel(image:streetsign)}
{image_upload(image:streetsign, Afegeix una imatge de la placa amb el nom del carrer)}" }, + "wikipedia": { + "render": "Existeix un article de la Viquipèdia sobre aquest carrer:
{wikipedia():max-height:25rem}" + }, "wikipedia-etymology": { "render": "

Article de la Viquipèdia del nom donant

{wikipedia(name:etymology:wikidata):max-height:20rem}" } @@ -3360,10 +3660,74 @@ } } }, + "1": { + "options": { + "0": { + "question": "Accepta efectu" + } + } + }, + "2": { + "options": { + "0": { + "question": "Accepta el pagament amb targeta" + } + } + }, + "3": { + "options": { + "0": { + "question": "Accepta targetes de dèbit" + } + } + }, + "4": { + "options": { + "0": { + "question": "Accepta targetes de crèdit" + } + } + }, + "5": { + "options": { + "0": { + "question": "Amb i sense imatges" + }, + "1": { + "question": "Té com a mínim una imatge" + }, + "2": { + "question": "Probablement no té una imatge" + } + } + }, + "6": { + "options": { + "0": { + "question": "Amb superfícies podotàctils" + } + } + }, "7": { "options": { + "0": { + "question": "Amb o sense superfícies podotàctils" + }, + "1": { + "question": "Amb paviments superfícies podotàctils" + }, "2": { "question": "Sense superfície podotàctil" + }, + "3": { + "question": "No hi ha informació sobre superfícies podotàctils" + } + } + }, + "8": { + "options": { + "0": { + "question": "Té opcions orgàniques" } } } @@ -3426,20 +3790,169 @@ "fitness_centre": { "description": "Capa que mostra centres de fitnes o gimnasos", "name": "Centre de fitnes o gimnàs", + "presets": { + "0": { + "title": "un centre de fitness" + } + }, "tagRenderings": { "name": { + "freeform": { + "placeholder": "Nom del centre de fitness" + }, + "mappings": { + "0": { + "then": "Aquest centre de fitness no té nom" + } + }, + "question": "Quin és el nom d'aquest centre de fitness?", "render": "Aquest gimnàs / centre de fitness s'anomena {name}" } + }, + "title": { + "render": "Centre de fitness" + } + }, + "fitness_station": { + "description": "Troba una estació de fitness a prop teu i afegeix-ne les que falten.", + "name": "Estacions de fitness", + "presets": { + "0": { + "title": "una estació de fitness" + } + }, + "tagRenderings": { + "name": { + "freeform": { + "placeholder": "Nom de l'estació de fitness" + }, + "mappings": { + "0": { + "then": "L'estació de fitness no té nom" + } + }, + "question": "Quin és el nom d'aquesta estació de fitness?", + "render": "L'estació de fitness es diu {name}" + }, + "operator": { + "question": "Qui manté l'estació de fitness?", + "render": "{operator} manté l'estació de fitness." + }, + "type": { + "mappings": { + "0": { + "then": "Aquesta estació de fitness té una barra horitzontal, prou alta per a fer traccions." + }, + "1": { + "then": "Aquesta estació de fitness té un cartell amb instruccions per a un exercici concret." + }, + "2": { + "then": "Aquesta estació de fitness té una instal·lació per fer abdominals." + }, + "3": { + "then": "Aquest estació de fitness té una instal·lació per a flexions. Normalment consta d'una o més barres horitzontals baixes." + } + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Estació de fitness {name}" + } + }, + "render": "Estació de fitness" + } + }, + "fixme": { + "description": "Objectes OSM que probablement s'han de solucionar, basats en una etiqueta FIXME.", + "name": "Objectes d'OSM amb etiquetes FIXME", + "tagRenderings": { + "fixme": { + "mappings": { + "0": { + "then": "Aquest problema s'ha resolt" + } + }, + "question": "Que hi ha mal amb aquest element?", + "render": "Text Fixme: {fixme}" + }, + "note": { + "render": "Text de la nota: {note}" + } + }, + "title": { + "render": "Objecte OSM amb etiqueta FIXME" } }, "food": { + "deletion": { + "extraDeleteReasons": { + "0": { + "explanation": "{title()} ha tancat permanentment" + } + }, + "nonDeleteMappings": { + "0": { + "then": "Això és en realitat un bar" + }, + "1": { + "then": "Això és en realitat un cafè" + } + } + }, "description": "Una capa que mostra restaurants i locals de menjar ràpid (amb un renderitzat especial per a fregiduries)", + "filter": { + "1": { + "options": { + "0": { + "question": "No cal reservar" + } + } + }, + "2": { + "options": { + "0": { + "question": "Té menú vegetarià" + }, + "1": { + "question": "Només negocis de menjar ràpid" + }, + "2": { + "question": "Només restaurants" + } + } + }, + "3": { + "options": { + "0": { + "question": "Té menú vegà" + } + } + }, + "4": { + "options": { + "0": { + "question": "Té menú halal" + } + } + }, + "5": { + "options": { + "0": { + "question": "Té menú halal" + } + } + } + }, "name": "Restaurants i menjar ràpid", "presets": { "0": { + "description": "Un lloc per menjar formal amb instal·lacions per seure que venen àpats complets servits per cambrers", "title": "un restaurant" }, "1": { + "description": "Un negoci de menjar centrat en el servei ràpid sols en mostrador i menjar per a endur", "title": "un de menjar ràpid" }, "2": { @@ -3456,9 +3969,27 @@ "1": { "then": "Això és una fregiduria" }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" + }, "2": { "then": "Principalment serveix pasta" }, + "3": { + "then": "Aquesta és una botiga de kebabs" + }, "4": { "then": "Això és un sandvitxeria" }, @@ -3476,21 +4007,6 @@ }, "9": { "then": "Aquí es serveixen plats francesos" - }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin menjar es serveix aquí?", @@ -3504,9 +4020,11 @@ "1": { "then": "Un restaurant, centrat en crear una bona experiència on es serveix a taula" } - } + }, + "question": "Quin tipus de negoci és aquest?" }, "Name": { + "question": "Quin és el nom d'aquest negoci?", "render": "El nom d'aquest negoci és {name}" }, "Reservation": { @@ -3596,6 +4114,62 @@ }, "question": "Aquesta botiga de patates fregides utilitza oli vegetal o animal per a cuinar?" }, + "friture-organic": { + "mappings": { + "0": { + "then": "Hi ha disponibles snacks orgànics" + }, + "1": { + "then": "No hi han disponibles snacks orgànics" + }, + "2": { + "then": "Només hi han disponibles snacks orgànics" + } + }, + "question": "Aquesta botiga de patates fregides ofereix snacks orgànics?" + }, + "friture-take-your-container": { + "mappings": { + "0": { + "then": "Pots portar els teus propis recipients per a arreplegar la teua comanda, estalviant material d'un sol ús i, per tant, brossa" + }, + "1": { + "then": "Portar el teu propi recipient no està permès" + }, + "2": { + "then": "Has de portar el teu propi recipient per a demanar aquí." + } + }, + "question": "Si portes el teu propi recipient (com olla de cuina o olles menudes), s'utilitza per a empaquetar la teua comanda?" + }, + "friture-vegan": { + "mappings": { + "0": { + "then": "Hi han snacks vegans disponibles" + }, + "1": { + "then": "Hi ha una petita selecció de snacks vegans disponibles" + }, + "2": { + "then": "No hi ha snacks vegans disponibles" + } + }, + "question": "Aquesta botiga de patates fregides té snacks vegans?" + }, + "friture-vegetarian": { + "mappings": { + "0": { + "then": "Hi ha snacks vegetarians disponibles" + }, + "1": { + "then": "Només una petita selecció de snacks son vegetarians" + }, + "2": { + "then": "No hi han sacks disponibles" + } + }, + "question": "Aquesta botiga de patates fregides ofereix snacks vegetarians?" + }, "halal (no friture)": { "mappings": { "0": { @@ -3644,6 +4218,7 @@ } }, "ghost_bike": { + "description": "Una capa que mostra monuments commemoratius als ciclistes morts en accidents de trànsit", "name": "Bicicleta fantasma", "presets": { "0": { @@ -3667,23 +4242,46 @@ "then": "No hi ha cap nom marcat a la bicicleta" } }, - "questionHint": "Si us plau, respecteu la privadesa: només ompliu el nom si està àmpliament publicat o marcat a la bicicleta. Opta per deixar de banda el cognom." + "question": "A qui recorda aquesta bicicleta fantasma?", + "questionHint": "Si us plau, respecteu la privadesa: només ompliu el nom si està àmpliament publicat o marcat a la bicicleta. Opta per deixar de banda el cognom.", + "render": "En record de {subjecte}" }, "ghost_bike-source": { "question": "En quina pàgina web es pot trobar més informació sobre la bicicleta blanca o l'accident?", "render": "Més informació disponible" + }, + "ghost_bike-start_date": { + "question": "Quan es va instal·lar aquesta bicicleta Ghost?" } }, "title": { + "mappings": { + "0": { + "then": "Bicicleta fantasma en el record de {subjecte}" + }, + "1": { + "then": "Bicicleta fantasma en el record de {name}" + } + }, "render": "Bicicleta blanca" } }, "governments": { + "description": "Aquesta capa mostra edificis governamentals. Es va configurar com a capa encarregada per al client d'OSOC '22", + "name": "Governs", + "presets": { + "0": { + "title": "una oficina Governamental" + } + }, "tagRenderings": { "name": { "question": "Quin és el nom d'aquesta oficina gornavental?", "render": "Aquesta Oficina Governamental s'anomena {name}" } + }, + "title": { + "render": "Oficina governamental {name}" } }, "gps_track": { @@ -3698,9 +4296,73 @@ } }, "hackerspace": { + "description": "Espai hacker", + "name": "Espai hacker", "presets": { + "0": { + "description": "Un espai hacker és un lloc on la gent interesada en el software es reuneix", + "title": "un espai hacker" + }, "1": { - "description": "Un espai maker és un lloc on entusiastes del DIY es reuneixen per a experimentar amb electrònica com arudino, tires LED, …" + "description": "Un espai maker és un lloc on entusiastes del DIY es reuneixen per a experimentar amb electrònica com arudino, tires LED, …", + "title": "un espai maker" + } + }, + "tagRenderings": { + "available_devices": { + "renderings": { + "0": { + "mappings": { + "0": { + "then": "Hi ha {device-name} disponible a aquest espai hacker" + }, + "1": { + "then": "No hi ha {negative-name} disponible a aquest espai hacker" + } + }, + "question": "Hi ha {device-name} disponible a aquest espai hacker?" + } + }, + "rewrite": { + "into": { + "0": { + "1": "una impressora 3D", + "2": "Impressora 3D" + }, + "1": { + "1": "un tallador laser", + "2": "tallador laser" + }, + "2": { + "1": "un trepant CNC", + "2": "trepant CNC" + } + } + } + }, + "hackerspaces-name": { + "question": "Quin és el nom d'aquest espai hacker?", + "render": "Aquest espai hacker es diu {name}" + }, + "hackerspaces-start_date": { + "question": "Quan es va fundar aquest espai hacker?", + "render": "Aquest espai hacker es va fundar el {start_date}" + }, + "is_makerspace": { + "mappings": { + "0": { + "then": "Aquest és un espai maker" + }, + "1": { + "then": "Aquest és un espai hacker tradicional (orientat al software)" + } + }, + "question": "Açò és un espai hacker o un espai maker?" + }, + "opening_hours_24_7": { + "override": { + "question": "Quan obri aquest espai hacker?" + } } }, "title": { @@ -3708,10 +4370,12 @@ "0": { "then": " {name}" } - } + }, + "render": "Espai hacker" } }, "hospital": { + "description": "Una capa que mostra els terrenys de l'hospital", "name": "Hospitals", "tagRenderings": { "inpatient": { @@ -3729,10 +4393,20 @@ "name": { "question": "Quin és el nom d'aquest hospital?", "render": "Aquest hospital s'anomena {name}" + }, + "oh-visitor": { + "question": "Quan poden anar els visitants?", + "questionHint": "Aquests són els horaris habituals dels visitants. Algunes plantes tenen diferents horaris de visita o poden permetre els visitants en cas d'emergència", + "render": "

Horari d'obertura per a visitants

S'admeten visitants habituals en els moments següents: {opening_hours_table(opening_hours:visitors)}

Algunes palntes poden tenir un horari d'obertura diferent. Molts hospitals també permeten visites en cas d'emergència.

" } + }, + "title": { + "render": "Hospital" } }, "hotel": { + "description": "Capa que mostra tots els hotels", + "name": "Hotels", "presets": { "0": { "title": "un hotel" @@ -3740,8 +4414,20 @@ }, "tagRenderings": { "name": { + "freeform": { + "placeholder": "Nom de l'hotel" + }, + "question": "Quin és el nom d'aquest hotel?", "render": "Aquest hotel es diu {name}" } + }, + "title": { + "mappings": { + "0": { + "then": "Hotel {name}" + } + }, + "render": "Hotel" } }, "hydrant": { @@ -3852,9 +4538,48 @@ } } }, + "icons": { + "description": "Una capa que actua com a biblioteca per a les icones d'etiquetes, especialment per mostrar-se com a insígnia al costat d'un PDI" + }, "indoors": { "description": "Mapeig interior bàsic: mostra els contorns de les habitacions", - "name": "Interiors" + "name": "Interiors", + "tagRenderings": { + "name": { + "freeform": { + "placeholder": "Nom de l'habitació" + }, + "question": "Quin és el nom d'aquesta habitació?", + "render": "Aquesta habitació es diu {name}" + }, + "ref": { + "freeform": { + "placeholder": "Número de referència de l'habitació (p. e. '1.1' o 'A1')" + }, + "question": "Quin és el número de referència d'aquesta habitació?", + "render": "Aquesta habitació té el número de referència {ref}" + } + }, + "title": { + "mappings": { + "0": { + "then": "Habitació interior {name}" + }, + "1": { + "then": "Àrea interior {name}" + }, + "2": { + "then": "Paret interior {name}" + }, + "3": { + "then": "Passadis interior {name}" + }, + "4": { + "then": "Porta interior {name}" + } + }, + "render": "Àrea interior {nom}" + } }, "information_board": { "description": "Una capa que mostra panells informatius turístics (p.e. informen sobre el paissatge, una construcció, una característica, un mapa, …)", @@ -3869,14 +4594,49 @@ } }, "kerbs": { + "description": "Una capa que mostra les vorades.", + "filter": { + "0": { + "options": { + "0": { + "question": "Tots els tipus de vorades" + }, + "1": { + "question": "Vorada elevada (>3 cm)" + }, + "2": { + "question": "Vorada rebaixada (~3 cm)" + }, + "3": { + "question": "Vorada a ras (~0 cm)" + }, + "4": { + "question": "Sense vorada" + }, + "5": { + "question": "Vorada sense altura coneguda" + } + } + } + }, + "name": "Vroades", + "presets": { + "0": { + "title": "una vorada" + } + }, "tagRenderings": { "kerb-height": { + "freeform": { + "placeholder": "Altura de la vorada" + }, "mappings": { "0": { "then": "Aquest gual està rebaixat i és més baix que 1cm." } }, - "question": "Quina és l'altura d'aquest gual?" + "question": "Quina és l'altura d'aquest gual?", + "render": "Altura de la vorada {kerb:height}" }, "kerb-type": { "mappings": { @@ -3888,6 +4648,12 @@ }, "2": { "then": "Aquest gual està a ras (~0cm)" + }, + "3": { + "then": "Aquí no hi ha vorada" + }, + "4": { + "then": "Hi ha una vorada d'altura desconeguda" } }, "question": "Quina és l'altura d'aquest gual?" @@ -3907,6 +4673,9 @@ "question": "Hi ha una superfície podotàctil a aquest gual?" } }, + "title": { + "render": "Vorada" + }, "units": { "0": { "applicableUnits": { @@ -3978,6 +4747,7 @@ } }, "map": { + "description": "Un mapa", "name": "Mapes", "presets": { "0": { @@ -4001,6 +4771,54 @@ }, "maproulette": { "description": "Capa que mostra totes les tasques de MapRoulette", + "filter": { + "0": { + "options": { + "0": { + "question": "Mostra les tasques amb tots els estats" + }, + "1": { + "question": "Mostra les tasques que es creen" + }, + "2": { + "question": "Mostra les tasques que estan arreglades" + }, + "3": { + "question": "Mostra tasques que són falsos positius" + }, + "4": { + "question": "Mostra les tasques que s'han omès" + }, + "5": { + "question": "Mostra les tasques que s'han suprimit" + }, + "6": { + "question": "Mostra les tasques que ja estan arreglades" + }, + "7": { + "question": "Mostra les tasques marcades com a massa difícils" + }, + "8": { + "question": "Mostra les tasques que estan desactivades" + } + } + }, + "1": { + "options": { + "0": { + "question": "El nom del repte conté {cerca}" + } + } + }, + "2": { + "options": { + "0": { + "question": "L'identificador de desafiament coincideix amb {cerca}" + } + } + } + }, + "name": "Tasques de MapRoulette", "tagRenderings": { "mark_duplicate": { "render": { @@ -4027,20 +4845,62 @@ "mappings": { "0": { "then": "Es crea la tasca" + }, + "1": { + "then": "La tasca està arreglada" } } } } }, + "maproulette_challenge": { + "description": "Capa que mostra les tasques d'un sol repte de MapRoulette. Aquesta capa està pensada per ser reutilitzada i ampliada en temes; consulteu [la documentació](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) sobre com fer-ho.", + "filter": { + "0": { + "options": { + "0": { + "question": "Mostra les tasques amb tots els estats" + }, + "1": { + "question": "Mostra les tasques que es creen" + }, + "2": { + "question": "Mostra les tasques que estan arreglades" + }, + "3": { + "question": "Mostra les tasques que son falsos positius" + }, + "4": { + "question": "Mostra les tasques que s'han omès" + } + } + } + }, + "title": { + "render": "Element a MapRoulette" + } + }, "maxspeed": { "description": "Mostra la velocitat permesa per a cada carretera", "name": "Velocitat", "tagRenderings": { "maxspeed-maxspeed": { + "mappings": { + "0": { + "then": "Aquest és un carrer residencial, que té una velocitat màxima de 20 km/h" + } + }, "question": "Quina és la velocitat màxima legal que es permet conduir en aquesta carretera?", "render": "La velocitat màxima permesa a aquesta via és {canonical(maxspeed)}" } }, + "title": { + "mappings": { + "0": { + "then": "Carretera sense nom" + } + } + }, "units": { "0": { "applicableUnits": { @@ -4056,10 +4916,32 @@ } } }, + "memorial": { + "tagRenderings": { + "inscription": { + "question": "Quina és la inscripció d'aquesta placa?", + "render": "La inscripció d'aquesta placa diu:

{inscripció}

" + } + }, + "title": { + "render": "Placa commemorativa" + } + }, "nature_reserve": { + "description": "Una reserva natural és una zona on la natura pot seguir el seu curs", "filter": { + "0": { + "options": { + "0": { + "question": "De lliure accés" + } + } + }, "1": { "options": { + "0": { + "question": "Totes les reserves naturals" + }, "1": { "question": "Els gossos poden anar lliurement" }, @@ -4069,6 +4951,7 @@ } } }, + "name": "Reserva Natural", "presets": { "0": { "description": "Afegeix una reserva natural que falta", @@ -4097,11 +4980,23 @@ "then": "Accessible amb una taxa" } }, - "question": "Aquesta reserva natural és accessible al públic?" + "question": "Aquesta reserva natural és accessible al públic?", + "render": "Accés a aquesta reserva natural: {access:description}" }, "Curator": { "question": "Qui és el conservador d'aquesta reserva natural?", - "questionHint": "Respecteu la privadesa: només empleneu un nom si es publica àmpliament" + "questionHint": "Respecteu la privadesa: només empleneu un nom si es publica àmpliament", + "render": "{curator} és el conservador d'aquesta reserva natural" + }, + "Dogs?": { + "mappings": { + "0": { + "then": "Els gossos han d'anar lligats" + }, + "1": { + "then": "No s'admeten gossos" + } + } }, "Editable description": { "question": "Hi ha alguna informació addicional?" @@ -4406,6 +5301,7 @@ } }, "pedestrian_path": { + "description": "Senderes per a vianants, especialment utilitzades per a la navegació interior i les entrades ràpides a aquesta capa", "name": "Camins per a vianants" }, "pharmacy": { @@ -5139,30 +6035,6 @@ "1": { "question": "Reciclatge de piles" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" - }, "10": { "question": "Reciclatge de vidre" }, @@ -5193,11 +6065,35 @@ "19": { "question": "Reciclatge del rebuig" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, "20": { "question": "Reciclatge de cartutxos d'impressora" }, "21": { "question": "Reciclatge de bicicletes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" } } }, @@ -5265,30 +6161,6 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" - }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -5319,6 +6191,9 @@ "19": { "then": "Aquí es poden reciclar sabates" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -5333,6 +6208,27 @@ }, "24": { "then": "Aquí es poden reciclar bicicletes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -5847,6 +6743,12 @@ "1": { "then": "Aquest fanal utilitza LED" }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" + }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -5870,12 +6772,6 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" - }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -6498,10 +7394,11 @@ "then": "No s'ha senyalitzat cap entrada" }, "1": { - "then": "Cap de les entrades de {_entrance_count} encara no tenen informació de l'ampalda" + "then": "Cap de les {_entrance_count} entrades té informació d'amplada encara" } }, "render": { + "after": "{_entrances_count_without_width_count} entrades encara no tenen informació d'amplada", "before": "

Entrades

Aquest edifici té {_entrances_count} entrades:", "special": { "tagrendering": "Una entrada de {canonical(width)}" @@ -6731,4 +7628,4 @@ } } } -} \ No newline at end of file +} From 9567dee3307ae71f37d7591939ec70fda778de84 Mon Sep 17 00:00:00 2001 From: paunofu Date: Tue, 19 Sep 2023 11:31:43 +0000 Subject: [PATCH 006/133] Translated using Weblate (Spanish) Currently translated at 45.6% (1414 of 3096 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/es/ --- langs/layers/es.json | 331 +++++++++++++++++++++---------------------- 1 file changed, 163 insertions(+), 168 deletions(-) diff --git a/langs/layers/es.json b/langs/layers/es.json index 31857c562..8e9360f4c 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un lletrer" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" + }, "2": { "title": "un mupi sobre la pared" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tótem" - }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un lletrer" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Esto es un tablón de anuncios" }, + "10": { + "then": "Esto es una pared pintada" + }, "2": { "then": "Esto es una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Esto es un tótem" - }, - "10": { - "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,6 +205,9 @@ "1": { "then": "Tablon de anuncios" }, + "10": { + "then": "Pared Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tótem" - }, - "10": { - "then": "Pared Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" + }, + "12": { + "then": "Tallado en madera" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relieve" - }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, - "12": { - "then": "Tallado en madera" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1436,6 +1436,27 @@ "0": { "question": "Todos los conectores" }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" + }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E)
" }, @@ -1459,27 +1480,6 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" - }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1534,30 +1534,6 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" - }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1588,6 +1564,9 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1618,11 +1597,32 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -1853,8 +1853,8 @@ "title": "un cruce" }, "1": { - "description": "Señal de tráfico en una carretera", - "title": "una señal de tráfico" + "description": "Semáforo en una carretera", + "title": "un semáforo" } }, "tagRenderings": { @@ -1950,7 +1950,7 @@ "then": "Cruce, sin semáforos" }, "1": { - "then": "Cruce con señales de tráfico" + "then": "Cruce con semáforo" }, "2": { "then": "Paso de cebra" @@ -1965,10 +1965,10 @@ "title": { "mappings": { "0": { - "then": "Señal de tráfico" + "then": "Semáforo" }, "1": { - "then": "Cruce con señales de tráfico" + "then": "Cruce con semáforos" } }, "render": "Cruce" @@ -2017,6 +2017,12 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2031,12 +2037,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2082,6 +2082,9 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2093,9 +2096,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -2402,7 +2402,7 @@ } }, "elongated_coin": { - "description": "Capa mostrando prensas de centavo.", + "description": "Capa que muestra prensas de centavos.", "name": "Prensas de centavo", "presets": { "0": { @@ -2416,14 +2416,14 @@ }, "mappings": { "0": { - "then": "Cuesta 1 euro para presionar un centavo." + "then": "Cuesta 1 euro prensar un centavo." }, "1": { - "then": "Cuesta 2 euros para presionar un centavo." + "then": "Cuesta 2 euros prensa un centavo." } }, - "question": "¿Cuánto cuesta presionar un centavo?", - "render": "Cuesta {charge} para presionar un centavo." + "question": "¿Cuánto cuesta prensar un centavo?", + "render": "Cuesta {charge} prensar un centavo." }, "coin": { "freeform": { @@ -2431,23 +2431,23 @@ }, "mappings": { "0": { - "then": "Esta prensa de centavo utiliza una moneda de 2 centavos para presionar." + "then": "Esta prensa de centavo utiliza una moneda de 2 centavos para prensar." }, "1": { - "then": "Esta prensa de centavo utiliza una moneda de 5 centavos para presionar." + "then": "Esta prensa de centavo utiliza una moneda de 5 centavos para prensar." }, "2": { - "then": "Esta prensa de centavo utiliza una moneda de 10 centavos para presionar." + "then": "Esta prensa de centavo utiliza una moneda de 10 centavos para prensar." }, "3": { - "then": "Esta prensa de centavo utiliza una moneda de 25 centavos para presionar." + "then": "Esta prensa de centavo utiliza una moneda de 25 centavos para prensar." }, "4": { - "then": "Esta prensa de centavo utiliza una moneda de 50 centavos para presionar." + "then": "Esta prensa de centavo utiliza una moneda de 50 centavos para prensar." } }, - "question": "Qué moneda se utiliza para presionar?", - "render": "Esta prensa de centavo utiliza una moneda {coin:type} para presionar." + "question": "Qué moneda se utiliza para prensar?", + "render": "Esta prensa de centavo utiliza una moneda {coin:type} para prensar." }, "designs": { "freeform": { @@ -2467,7 +2467,7 @@ "then": "Esta prensa tiene cuatro diseños disponibles." } }, - "question": "Cuántos diseños son disponibles?", + "question": "Cuántos diseños hay disponibles?", "render": "Esta prensa tiene {coin:design_count} diseños disponibles." }, "indoor": { @@ -2502,7 +2502,7 @@ "then": "Una puerta giratoria que cuelga de un eje central y que rota dentro de una carcasa cilíndrica" }, "3": { - "then": "Una puerta corredera donde las hojas se deslizan de lado a lado, típicamente con una pared" + "then": "Una puerta corredera donde la puerta se desliza hacia un lado, generalmente paralela a una pared" }, "5": { "then": "Esta es una entrada sin una puerta física" @@ -2720,6 +2720,18 @@ "0": { "then": "Esto es una pizzería" }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" + }, "2": { "then": "Principalmente sirve pasta" }, @@ -2740,18 +2752,6 @@ }, "9": { "then": "Aquí se sirven platos Franceses" - }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué comida se sirve aquí?", @@ -3149,6 +3149,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las nostras de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } + }, "2": { "options": { "0": { @@ -3204,19 +3217,6 @@ "question": "Solo mostrar las notas abiertas" } } - }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las nostras de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } } }, "name": "Notas de OpenStreetMap", @@ -3832,21 +3832,6 @@ "1": { "question": "Reciclaje de baterías" }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "9": { - "question": "Reciclaje de botellas de cristal" - }, "10": { "question": "Reciclaje de cristal" }, @@ -3870,6 +3855,21 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" + }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "9": { + "question": "Reciclaje de botellas de cristal" } } } @@ -3912,24 +3912,6 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" - }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -3953,6 +3935,24 @@ }, "19": { "then": "Aquí se pueden reciclar zapatos" + }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -4256,11 +4256,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}" @@ -4301,6 +4296,12 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" + }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -4321,12 +4322,6 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" - }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -4901,4 +4896,4 @@ } } } -} \ No newline at end of file +} From d7c76b7f9badec2c0edbad8fca33a0e92d2bfd8b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 12:11:08 +0000 Subject: [PATCH 007/133] Translated using Weblate (Catalan) Currently translated at 72.2% (2238 of 3096 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 5c3fa0592..4247423ec 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -3359,8 +3359,8 @@ "render": { "special": { "question": "Aquest ascensor en quins idiomes té l'escriptura tàctil (braille)?", - "render_list_item": "Aquest ascensor té l'escriptura tàctil en language():font-bold}", - "render_single_language": "Aquest ascensor té l'escriptura tàctil en language():font-bold}" + "render_list_item": "Aquest ascensor té l'escriptura tàctil en {language():font-bold}", + "render_single_language": "Aquest ascensor té l'escriptura tàctil en {language():font-bold}" } } } @@ -4244,7 +4244,7 @@ }, "question": "A qui recorda aquesta bicicleta fantasma?", "questionHint": "Si us plau, respecteu la privadesa: només ompliu el nom si està àmpliament publicat o marcat a la bicicleta. Opta per deixar de banda el cognom.", - "render": "En record de {subjecte}" + "render": "En record de {subject}" }, "ghost_bike-source": { "question": "En quina pàgina web es pot trobar més informació sobre la bicicleta blanca o l'accident?", @@ -4257,7 +4257,7 @@ "title": { "mappings": { "0": { - "then": "Bicicleta fantasma en el record de {subjecte}" + "then": "Bicicleta fantasma en el record de {subject}" }, "1": { "then": "Bicicleta fantasma en el record de {name}" @@ -4578,7 +4578,7 @@ "then": "Porta interior {name}" } }, - "render": "Àrea interior {nom}" + "render": "Àrea interior {name}" } }, "information_board": { @@ -4806,14 +4806,14 @@ "1": { "options": { "0": { - "question": "El nom del repte conté {cerca}" + "question": "El nom del repte conté {search}" } } }, "2": { "options": { "0": { - "question": "L'identificador de desafiament coincideix amb {cerca}" + "question": "L'identificador de desafiament coincideix amb {search}" } } } @@ -4920,7 +4920,7 @@ "tagRenderings": { "inscription": { "question": "Quina és la inscripció d'aquesta placa?", - "render": "La inscripció d'aquesta placa diu:

{inscripció}

" + "render": "La inscripció d'aquesta placa diu:

{inscription}

" } }, "title": { From 6d5617d5a9c47737b6cfc27e33dbadc6bddd9561 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 14:17:03 +0200 Subject: [PATCH 008/133] Chore: remove unneeded file --- src/UI/Wikipedia/WikipediaBoxOptions.ts | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/UI/Wikipedia/WikipediaBoxOptions.ts diff --git a/src/UI/Wikipedia/WikipediaBoxOptions.ts b/src/UI/Wikipedia/WikipediaBoxOptions.ts deleted file mode 100644 index fe5ac106a..000000000 --- a/src/UI/Wikipedia/WikipediaBoxOptions.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface WikipediaBoxOptions { - addHeader?: boolean - firstParagraphOnly?: true | boolean - allowToAdd?: boolean -} From 203f24fca01047edd2f01ed541fc93841ac1f17d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 14:29:11 +0200 Subject: [PATCH 009/133] Fix: cleanup of wikipedia panel, fix #1570 --- src/UI/Wikipedia/WikipediaArticle.svelte | 44 +++++++------ src/UI/Wikipedia/WikipediaPanel.svelte | 80 +++++++++++++----------- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/UI/Wikipedia/WikipediaArticle.svelte b/src/UI/Wikipedia/WikipediaArticle.svelte index dd0f61aa0..b2a35eec6 100644 --- a/src/UI/Wikipedia/WikipediaArticle.svelte +++ b/src/UI/Wikipedia/WikipediaArticle.svelte @@ -1,35 +1,38 @@ - - - - +{#if $wikipediaDetails.articleUrl} + + + + +{/if} {#if $wikipediaDetails.wikidata} {/if} +{#if $wikipediaDetails.articleUrl} -{#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} - - - -{:else} + {#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} + + + + {:else} @@ -47,4 +50,5 @@ + {/if} {/if} diff --git a/src/UI/Wikipedia/WikipediaPanel.svelte b/src/UI/Wikipedia/WikipediaPanel.svelte index 8112dc15d..8ca905fc7 100644 --- a/src/UI/Wikipedia/WikipediaPanel.svelte +++ b/src/UI/Wikipedia/WikipediaPanel.svelte @@ -2,57 +2,63 @@ /** * Shows one or more wikidata info boxes or wikipedia articles in a tabbed component. */ - import type { FullWikipediaDetails } from "../../Logic/Web/Wikipedia" - import Wikipedia from "../../Logic/Web/Wikipedia" - import Locale from "../i18n/Locale" - import { Store } from "../../Logic/UIEventSource" - import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui" - import WikipediaTitle from "./WikipediaTitle.svelte" - import WikipediaArticle from "./WikipediaArticle.svelte" - import { onDestroy } from "svelte" + import type { FullWikipediaDetails } from "../../Logic/Web/Wikipedia"; + import Wikipedia from "../../Logic/Web/Wikipedia"; + import Locale from "../i18n/Locale"; + import { Store } from "../../Logic/UIEventSource"; + import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"; + import WikipediaTitle from "./WikipediaTitle.svelte"; + import WikipediaArticle from "./WikipediaArticle.svelte"; + import { onDestroy } from "svelte"; /** * Either a wikidata item or a ':
' link */ - export let wikiIds: Store + export let wikiIds: Store; let wikipediaStores: Store[]> = Locale.language.bind((language) => wikiIds.map((wikiIds) => wikiIds.map((id) => Wikipedia.fetchArticleAndWikidata(id, language))) - ) - let _wikipediaStores + ); + let _wikipediaStores; onDestroy( wikipediaStores.addCallbackAndRunD((wikipediaStores) => { - _wikipediaStores = wikipediaStores + _wikipediaStores = wikipediaStores; }) - ) + ); {#if _wikipediaStores !== undefined} - - - {#each _wikipediaStores as store (store.tag)} - (selected ? "tab-selected" : "tab-unselected")}> - - - {/each} - - - {#each _wikipediaStores as store (store.tag)} - - - - {/each} - - + {#if _wikipediaStores.length === 1} + + {:else} + + + {#each _wikipediaStores as store (store.tag)} + (selected ? "tab-selected" : "tab-unselected")}> + + + {/each} + + + {#each _wikipediaStores as store (store.tag)} + + + + {/each} + + + {/if} {/if} From d1aa751e1831cd1090ea5a738462b052fad2a4d9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 16:28:33 +0200 Subject: [PATCH 010/133] Themes: update tagging of climbing shoe repair --- assets/themes/climbing/climbing.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index 9bb9eaf68..d89247da8 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -351,7 +351,7 @@ "source": { "=osmTags": { "and": [ - "service:repair:climbing_shoes=yes", + "service:climbing_shoes:repair=yes", { "or": [ "shop=shoe_repair", @@ -375,7 +375,7 @@ }, "mappings": [ { - "if": "service:repair:climbing_shoes=yes", + "if": "service:climbing_shoes:repair=yes", "then": { "en": "This shop repairs climbing shoes", "de": "Dieser Laden repariert Kletterschuhe", @@ -385,7 +385,7 @@ } }, { - "if": "service:repair:climbing_shoes=no", + "if": "service:climbing_shoes:repair=no", "then": { "en": "This shop does not repair climbing shoes", "de": "Dieser Shop repariert keine Kletterschuhe", @@ -423,7 +423,7 @@ }, "mappings": [ { - "if": "service:repair:climbing_shoes=yes", + "if": "service:climbing_shoes:repair=yes", "then": { "en": "This shop repairs climbing shoes", "de": "Dieses Geschäft repariert Kletterschuhe", @@ -433,7 +433,7 @@ } }, { - "if": "service:repair:climbing_shoes=no", + "if": "service:climbing_shoes:repair=no", "then": { "en": "This shop does not repair climbing shoes", "de": "Dieses Geschäft repariert keine Kletterschuhe", From 382f96596ed474bef086d64cabb748257416f2de Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 22:49:20 +0200 Subject: [PATCH 011/133] Fix: fix #1503, fix #1571 --- src/Logic/Tags/TagUtils.ts | 3 + src/Models/ThemeConfig/TagRenderingConfig.ts | 15 +- .../TagRendering/TagRenderingQuestion.svelte | 192 +++++++++++------- 3 files changed, 132 insertions(+), 78 deletions(-) diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 94c60522b..91ce6c5e1 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -132,6 +132,9 @@ export class TagUtils { /** * Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set. + * + * @see MatchesMultiAnswer to do the reverse + * * E.g: * * const tag = TagUtils.ParseUploadableTag({"and": [ diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 2039022e8..dafdd4e59 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -243,7 +243,10 @@ export default class TagRenderingConfig { if (txt === "") { throw context + " Rendering for language " + ln + " is empty" } - if (txt.indexOf("{" + this.freeform.key + "}") >= 0 || txt.indexOf("&LBRACE" + this.freeform.key + "&RBRACE") ) { + if ( + txt.indexOf("{" + this.freeform.key + "}") >= 0 || + txt.indexOf("&LBRACE" + this.freeform.key + "&RBRACE") + ) { continue } if (txt.indexOf("{" + this.freeform.key + ":") >= 0) { @@ -645,6 +648,16 @@ export default class TagRenderingConfig { /** * Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply * + * const config = new TagRenderingConfig({"id":"bookcase-booktypes","render":{"en":"This place mostly serves {books}" }, + * "question":{"en":"What kind of books can be found in this public bookcase?"}, + * "freeform":{"key":"books","addExtraTags":["fixme=Freeform tag `books` used, to be doublechecked"], + * "inline":true}, + * "multiAnswer":true, + * "mappings":[{"if":"books=children","then":"Mostly children books"}, + * {"if":"books=adults","then": "Mostly books for adults"}]} + * , "testcase") + * config.constructChangeSpecification(undefined, undefined, [false, true, false], {amenity: "public_bookcase"}) // => new And([new Tag("books","adult")]) + * * @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set * * @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 8f0979dad..0522baf0e 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -1,76 +1,114 @@ {#if config.question !== undefined} From 1a67e005fddb1751096af0439bcd51f844cfed3d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Sep 2023 23:01:59 +0200 Subject: [PATCH 012/133] Fix tests --- src/Models/ThemeConfig/TagRenderingConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index dafdd4e59..efa1dd528 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -656,7 +656,7 @@ export default class TagRenderingConfig { * "mappings":[{"if":"books=children","then":"Mostly children books"}, * {"if":"books=adults","then": "Mostly books for adults"}]} * , "testcase") - * config.constructChangeSpecification(undefined, undefined, [false, true, false], {amenity: "public_bookcase"}) // => new And([new Tag("books","adult")]) + * config.constructChangeSpecification(undefined, undefined, [false, true, false], {amenity: "public_bookcase"}) // => new And([new Tag("books","adults")]) * * @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set * From 5f04a695172934d4052ba7ee90ae378e66893d6a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 20 Sep 2023 01:47:32 +0200 Subject: [PATCH 013/133] Refactoring: port PlantNet-detection to svelte, re-integrate wikipedia component --- langs/en.json | 6 +- package.json | 2 +- public/css/index-tailwind-output.css | 18 +-- src/Logic/Web/PlantNet.ts | 42 +++--- src/UI/Base/BackButton.svelte | 3 +- src/UI/Base/NextButton.svelte | 2 +- src/UI/BigComponents/PlantNetSpeciesSearch.ts | 127 ------------------ src/UI/PlantNet/PlantNet.svelte | 123 +++++++++++++++++ src/UI/PlantNet/PlantNetSpeciesList.svelte | 37 +++++ src/UI/PlantNet/SpeciesButton.svelte | 56 ++++++++ src/UI/Popup/PlantNetDetectionViz.ts | 68 ++++------ src/UI/SpecialVisualizations.ts | 2 +- src/UI/StylesheetTestGui.svelte | 4 + src/UI/Wikipedia/WikidataPreviewBox.ts | 2 +- src/UI/Wikipedia/WikidataSearchBox.ts | 2 +- src/UI/Wikipedia/WikipediaArticle.svelte | 6 +- src/UI/Wikipedia/WikipediaPanel.svelte | 2 +- src/index.css | 5 + 18 files changed, 297 insertions(+), 210 deletions(-) delete mode 100644 src/UI/BigComponents/PlantNetSpeciesSearch.ts create mode 100644 src/UI/PlantNet/PlantNet.svelte create mode 100644 src/UI/PlantNet/PlantNetSpeciesList.svelte create mode 100644 src/UI/PlantNet/SpeciesButton.svelte diff --git a/langs/en.json b/langs/en.json index cc9974402..7d75d0a7b 100644 --- a/langs/en.json +++ b/langs/en.json @@ -380,6 +380,7 @@ "born": "Born: {value}", "died": "Died: {value}" }, + "readMore": "Read the rest of the article", "searchToShort": "Your search query is too short, enter a longer text", "searchWikidata": "Search on Wikidata", "wikipediaboxTitle": "Wikipedia" @@ -498,7 +499,9 @@ }, "plantDetection": { "back": "Back to species overview", + "button": "Automatically detect the plant species using the AI of Plantnet.org", "confirm": "Select species", + "done": "The species has been applied", "error": "Something went wrong while detecting the tree species: {error}", "howTo": { "intro": "For optimal results,", @@ -515,7 +518,8 @@ "poweredByPlantnet": "Powered by plantnet.org", "querying": "Querying plantnet.org with {length} images", "seeInfo": "See more information about the species", - "takeImages": "Take images of the tree to automatically detect the tree type" + "takeImages": "Take images of the tree to automatically detect the tree type", + "tryAgain": "Select a different species" }, "privacy": { "editing": "When you make a change to the map, this change is recorded on OpenStreetMap and is publicly available to anyone. A changeset made with MapComplete includes the following data:
  • The changes you made
  • Your username
  • When this change is made
  • The theme you used while making the change
  • The language of the user interface
  • An indication of how close you were to changed objects. Other mappers can use this information to determine if a change was made based on survey or on remote research
Please refer to the privacy policy on OpenStreetMap.org for detailed information. We'd like to remind you that you can use a fictional name when signing up.", diff --git a/package.json b/package.json index fff429293..534568b5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.32.0", + "version": "0.33.0", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 91a0cd7e0..e068f5f49 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -1096,6 +1096,10 @@ video { height: 2.75rem; } +.h-10 { + height: 2.5rem; +} + .h-48 { height: 12rem; } @@ -1104,10 +1108,6 @@ video { height: 10rem; } -.h-10 { - height: 2.5rem; -} - .h-80 { height: 20rem; } @@ -1709,11 +1709,6 @@ video { padding-right: 0.5rem; } -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - .pl-1 { padding-left: 0.25rem; } @@ -2209,6 +2204,11 @@ input[type=text] { border-radius: 0.5rem; } +.border-region { + border: 2px dashed var(--interactive-background); + border-radius: 0.5rem; +} + /******************* Styling of input elements **********************/ /** diff --git a/src/Logic/Web/PlantNet.ts b/src/Logic/Web/PlantNet.ts index d22cae763..dab705ad0 100644 --- a/src/Logic/Web/PlantNet.ts +++ b/src/Logic/Web/PlantNet.ts @@ -985,6 +985,27 @@ export default class PlantNet { } } +export interface PlantNetSpeciesMatch { + score: number + gbif: { id: string /*Actually a number*/ } + species: { + scientificNameWithoutAuthor: string + scientificNameAuthorship: string + genus: { + scientificNameWithoutAuthor: string + scientificNameAuthorship: string + scientificName: string + } + family: { + scientificNameWithoutAuthor: string + scientificNameAuthorship: string + scientificName: string + } + commonNames: string[] + scientificName: string + } +} + export interface PlantNetResult { query: { project: string @@ -995,26 +1016,7 @@ export interface PlantNetResult { language: string preferedReferential: string bestMatch: string - results: { - score: number - gbif: { id: string /*Actually a number*/ } - species: { - scientificNameWithoutAuthor: string - scientificNameAuthorship: string - genus: { - scientificNameWithoutAuthor: string - scientificNameAuthorship: string - scientificName: string - } - family: { - scientificNameWithoutAuthor: string - scientificNameAuthorship: string - scientificName: string - } - commonNames: string[] - scientificName: string - } - }[] + results: PlantNetSpeciesMatch[] version: string remainingIdentificationRequests: number } diff --git a/src/UI/Base/BackButton.svelte b/src/UI/Base/BackButton.svelte index 40f4d1eea..5f48bf7e6 100644 --- a/src/UI/Base/BackButton.svelte +++ b/src/UI/Base/BackButton.svelte @@ -10,12 +10,13 @@ const dispatch = createEventDispatcher<{ click }>() export let clss: string | undefined = undefined + export let imageClass: string | undefined = undefined dispatch("click")} options={{ extraClasses: twMerge("flex items-center", clss) }} > - + diff --git a/src/UI/Base/NextButton.svelte b/src/UI/Base/NextButton.svelte index a546fd8c1..6b4a64dd8 100644 --- a/src/UI/Base/NextButton.svelte +++ b/src/UI/Base/NextButton.svelte @@ -20,6 +20,6 @@
- +
diff --git a/src/UI/BigComponents/PlantNetSpeciesSearch.ts b/src/UI/BigComponents/PlantNetSpeciesSearch.ts deleted file mode 100644 index b7e503f6e..000000000 --- a/src/UI/BigComponents/PlantNetSpeciesSearch.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { VariableUiElement } from "../Base/VariableUIElement" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import PlantNet from "../../Logic/Web/PlantNet" -import Loading from "../Base/Loading" -import Wikidata from "../../Logic/Web/Wikidata" -import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox" -import { Button } from "../Base/Button" -import Combine from "../Base/Combine" -import Title from "../Base/Title" -import Translations from "../i18n/Translations" -import List from "../Base/List" -import Svg from "../../Svg" - -export default class PlantNetSpeciesSearch extends VariableUiElement { - /*** - * Given images, queries plantnet to search a species matching those images. - * A list of species will be presented to the user, after which they can confirm an item. - * The wikidata-url is returned in the callback when the user selects one - */ - constructor(images: Store, onConfirm: (wikidataUrl: string) => Promise) { - const t = Translations.t.plantDetection - super( - images - .bind((images) => { - if (images.length === 0) { - return null - } - return UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0, 5))) - }) - .map((result) => { - if (images.data.length === 0) { - return new Combine([ - t.takeImages, - t.howTo.intro, - new List([t.howTo.li0, t.howTo.li1, t.howTo.li2, t.howTo.li3]), - ]).SetClass("flex flex-col") - } - if (result === undefined) { - return new Loading(t.querying.Subs(images.data)) - } - - if (result["error"] !== undefined) { - return t.error.Subs(result).SetClass("alert") - } - console.log(result) - const success = result["success"] - - const selectedSpecies = new UIEventSource(undefined) - const speciesInformation = success.results - .filter((species) => species.score >= 0.005) - .map((species) => { - const wikidata = UIEventSource.FromPromise( - Wikidata.Sparql<{ species }>( - ["?species", "?speciesLabel"], - ['?species wdt:P846 "' + species.gbif.id + '"'] - ) - ) - - const confirmButton = new Button(t.seeInfo, async () => { - await selectedSpecies.setData(wikidata.data[0].species?.value) - }).SetClass("btn") - - const match = t.matchPercentage - .Subs({ match: Math.round(species.score * 100) }) - .SetClass("font-bold") - - const extraItems = new Combine([match, confirmButton]).SetClass( - "flex flex-col" - ) - - return new WikidataPreviewBox( - wikidata.map((wd) => - wd == undefined ? undefined : wd[0]?.species?.value - ), - { - whileLoading: new Loading( - t.loadingWikidata.Subs({ - species: species.species.scientificNameWithoutAuthor, - }) - ), - extraItems: [new Combine([extraItems])], - - imageStyle: "max-width: 8rem; width: unset; height: 8rem", - } - ).SetClass("border-2 border-subtle rounded-xl block mb-2") - }) - const plantOverview = new Combine([ - new Title(t.overviewTitle), - t.overviewIntro, - t.overviewVerify.SetClass("font-bold"), - ...speciesInformation, - ]).SetClass("flex flex-col") - - return new VariableUiElement( - selectedSpecies.map((wikidataSpecies) => { - if (wikidataSpecies === undefined) { - return plantOverview - } - return new Combine([ - new Button( - new Combine([ - Svg.back_svg().SetClass( - "w-6 mr-1 bg-white rounded-full p-1" - ), - t.back, - ]).SetClass("flex"), - () => { - selectedSpecies.setData(undefined) - } - ).SetClass("btn btn-secondary"), - - new Button( - new Combine([ - Svg.confirm_svg().SetClass("w-6 mr-1"), - t.confirm, - ]).SetClass("flex"), - () => { - onConfirm(wikidataSpecies) - } - ).SetClass("btn"), - ]).SetClass("flex justify-between") - }) - ) - }) - ) - } -} diff --git a/src/UI/PlantNet/PlantNet.svelte b/src/UI/PlantNet/PlantNet.svelte new file mode 100644 index 000000000..170c7fc18 --- /dev/null +++ b/src/UI/PlantNet/PlantNet.svelte @@ -0,0 +1,123 @@ + + +
+ + {#if collapsedMode} + + {:else if $error !== undefined} + + {:else if $imageUrls.length === 0} + +
+ {collapsedMode = true}}> + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ {:else if selectedOption === undefined} + speciesSelected(species.detail)}> + {collapsedMode = true}}> + + + {:else if !done} +
+
+ + +
+
+ {selectedOption = undefined}}> + + + { done = true; onConfirm(selectedOption); }} > + + +
+
+ {:else} + + + {done = false; selectedOption = undefined}}> + + + {/if} +
+ + +
+ +
diff --git a/src/UI/PlantNet/PlantNetSpeciesList.svelte b/src/UI/PlantNet/PlantNetSpeciesList.svelte new file mode 100644 index 000000000..5cb734156 --- /dev/null +++ b/src/UI/PlantNet/PlantNetSpeciesList.svelte @@ -0,0 +1,37 @@ + + +{#if $options === undefined} + + + +{:else} +
+
+ + +
+

+ +

+ + + {#each $options as species} + + {/each} +
+{/if} diff --git a/src/UI/PlantNet/SpeciesButton.svelte b/src/UI/PlantNet/SpeciesButton.svelte new file mode 100644 index 000000000..de7f87333 --- /dev/null +++ b/src/UI/PlantNet/SpeciesButton.svelte @@ -0,0 +1,56 @@ + + +{ + console.log("Dispatching: ", $wikidataId) + return dispatch("selected", $wikidataId); }}> + {#if $wikidata === undefined} + + + + {:else} + new WikidataPreviewBox(wikidataId, + { imageStyle: "max-width: 8rem; width: unset; height: 8rem", + extraItems: [t.matchPercentage + .Subs({ match: Math.round(species.score * 100) }) + .SetClass("thanks w-fit self-center")] + }).SetClass("w-full")}> + {/if} + diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index 198fc7a93..69397cabd 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -1,18 +1,14 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Toggle from "../Input/Toggle" -import Lazy from "../Base/Lazy" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch" import Wikidata from "../../Logic/Web/Wikidata" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" -import { SubtleButton } from "../Base/SubtleButton" -import Combine from "../Base/Combine" -import Svg from "../../Svg" -import Translations from "../i18n/Translations" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement" +import PlantNet from "../PlantNet/PlantNet.svelte" export class PlantNetDetectionViz implements SpecialVisualization { funcName = "plantnet_detection" @@ -37,45 +33,29 @@ export class PlantNetDetectionViz implements SpecialVisualization { imagePrefixes = [].concat(...args.map((a) => a.split(","))) } - const detect = new UIEventSource(false) - const toggle = new Toggle( - new Lazy(() => { - const allProvidedImages: Store = AllImageProviders.LoadImagesFor( - tags, - imagePrefixes - ) - const allImages: Store = allProvidedImages.map((pi) => - pi.map((pi) => pi.url) - ) - return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => { - selectedWikidata = Wikidata.ExtractKey(selectedWikidata) - const change = new ChangeTagAction( - tags.data.id, - new And([ - new Tag("species:wikidata", selectedWikidata), - new Tag("source:species:wikidata", "PlantNet.org AI"), - ]), - tags.data, - { - theme: state.layout.id, - changeType: "plantnet-ai-detection", - } - ) - await state.changes.applyAction(change) - }) - }), - new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() => - detect.setData(true) - ), - detect + const allProvidedImages: Store = AllImageProviders.LoadImagesFor( + tags, + imagePrefixes ) + const imageUrls: Store = allProvidedImages.map((pi) => pi.map((pi) => pi.url)) - return new Combine([ - toggle, - new Combine([ - Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"), - Translations.t.plantDetection.poweredByPlantnet, - ]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"), - ]).SetClass("flex flex-col") + async function applySpecies(selectedWikidata) { + selectedWikidata = Wikidata.ExtractKey(selectedWikidata) + const change = new ChangeTagAction( + tags.data.id, + new And([ + new Tag("species:wikidata", selectedWikidata), + new Tag("source:species:wikidata", "PlantNet.org AI"), + ]), + tags.data, + { + theme: state.layout.id, + changeType: "plantnet-ai-detection", + } + ) + await state.changes.applyAction(change) + } + + return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies }) } } diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 213451e97..26a94ec7f 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -538,7 +538,7 @@ export default class SpecialVisualizations { const keys = args[0].split(";").map((k) => k.trim()) const wikiIds: Store = tagsSource.map((tags) => { const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") - return tags[key]?.split(";")?.map((id) => id.trim()) + return tags[key]?.split(";")?.map((id) => id.trim()) ?? [] }) return new SvelteUIElement(WikipediaPanel, { wikiIds, diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte index 4ad143006..7d40c735f 100644 --- a/src/UI/StylesheetTestGui.svelte +++ b/src/UI/StylesheetTestGui.svelte @@ -29,6 +29,10 @@ areas, where some buttons might appear.

+
+ Highly interactive area (mostly: active question) +
+
-
+
{selectedOption = undefined}}> - { done = true; onConfirm(selectedOption); }} > + { done = true; onConfirm(selectedOption); }} >
@@ -115,8 +115,8 @@ {/if} -
- +
+
diff --git a/src/UI/PlantNet/SpeciesButton.svelte b/src/UI/PlantNet/SpeciesButton.svelte index de7f87333..9e3a44b91 100644 --- a/src/UI/PlantNet/SpeciesButton.svelte +++ b/src/UI/PlantNet/SpeciesButton.svelte @@ -36,9 +36,7 @@ const wikidataId: Store = UIEventSource.FromPromise( ).mapD(wd => wd[0]?.species?.value); -{ - console.log("Dispatching: ", $wikidataId) - return dispatch("selected", $wikidataId); }}> + dispatch("selected", $wikidataId)}> {#if $wikidata === undefined} image.pictureUrl === v); const t = Translations.t.image.nearby; const c = [lon, lat]; @@ -35,6 +35,7 @@ date: new Date(image.date) }); let distance = Math.round(GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)); + $: { const currentTags = tags.data; const key = Object.keys(image.osmTags)[0]; diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index 69397cabd..ed5a7acc4 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -1,6 +1,5 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" -import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch" import Wikidata from "../../Logic/Web/Wikidata" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" From c25e2787472d8b7e94bdfe6b6438272d9eecfb93 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 12:14:02 +0200 Subject: [PATCH 036/133] Chore: translation sync --- assets/layers/bench/bench.json | 8 ++++++-- langs/layers/en.json | 23 +++++++++++++++++++++++ langs/layers/nl.json | 3 +++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index f6e5650c0..9bea58a07 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -890,7 +890,9 @@ "mappings": [ { "if": "tourism=artwork", - "addExtraTags": ["not:tourism:artwork="], + "addExtraTags": [ + "not:tourism:artwork=" + ], "then": { "en": "This bench has an integrated artwork", "nl": "Deze bank heeft een geïntegreerd kunstwerk", @@ -915,7 +917,9 @@ "he": "לספסל זה אין יצירת אמנות משולבת", "pl": "Ta ławka nie ma wbudowanego dzieła sztuki" }, - "addExtraTags": ["tourism="] + "addExtraTags": [ + "tourism=" + ] }, { "if": "tourism=", diff --git a/langs/layers/en.json b/langs/layers/en.json index 460b13f6c..dba18196c 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -672,6 +672,9 @@ }, "1": { "then": "This bench does not have an integrated artwork" + }, + "2": { + "then": "This bench probably doesn't have an integrated artwork" } }, "question": "Does this bench have an artistic element?", @@ -8765,6 +8768,14 @@ }, "1": { "title": "a surveillance camera mounted on a wall" + }, + "2": { + "description": "An ALPR typically has two lenses and an array of infrared lights.", + "title": "an ALPR camera (Automatic Number Plate Reader)" + }, + "3": { + "description": "An ALPR typically has two lenses and an array of infrared lights.", + "title": "an ALPR camera (Automatic Number Plate Reader) mounted on a wall" } }, "tagRenderings": { @@ -8858,6 +8869,18 @@ "question": "In which geographical direction does this camera film?", "render": "Films to a compass heading of {camera:direction}" }, + "has_alpr": { + "mappings": { + "0": { + "then": "This is a normal camera" + }, + "1": { + "then": "This is an ALPR (Automatic License Plate Reader)" + } + }, + "question": "Can this camera automatically detect license plates?", + "questionHint": "An ALPR (Automatic License Plate Reader) typically has two lenses and an array of infrared LEDS in between." + }, "is_indoor": { "mappings": { "0": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 8256d43c2..3961df739 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -568,6 +568,9 @@ }, "1": { "then": "Deze bank heeft geen geïntegreerd kunstwerk" + }, + "2": { + "then": "Deze bank heeft waarschijnlijk geen geïntegreerd kunstwerk" } }, "question": "Heeft deze bank een geïntegreerd kunstwerk?", From 246b16317d93b8e2effe9b41f7ed8c362a61cff6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 14:50:35 +0200 Subject: [PATCH 037/133] Themes: add icons to question --- .../surveillance_camera.json | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index a2a2404eb..2e59fb397 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -52,7 +52,7 @@ { "if": "surveillance:type=camera", "then": { - "en": "This is a normal camera" + "en": "This is a camera without number plate recognition." } }, { @@ -79,11 +79,7 @@ }, "mappings": [ { - "if": { - "and": [ - "camera:type=fixed" - ] - }, + "if": "camera:type=fixed", "then": { "en": "A fixed (non-moving) camera", "nl": "Een vaste camera", @@ -92,14 +88,11 @@ "de": "Eine fest montierte (nicht bewegliche) Kamera", "ca": "Una càmera fixa (no movible)", "es": "Cámara fija (no móvil)" - } + }, + "icon": "./assets/themes/surveillance/cam_right.svg" }, { - "if": { - "and": [ - "camera:type=dome" - ] - }, + "if": "camera:type=dome", "then": { "en": "A dome camera (which can turn)", "nl": "Een dome (bolvormige camera die kan draaien)", @@ -109,7 +102,8 @@ "de": "Eine Kuppelkamera (drehbar)", "ca": "Càmera de cúpula (que pot girar)", "es": "Cámara con domo (que se puede girar)" - } + }, + "icon": "./assets/themes/surveillance/dome.svg" }, { "if": { From c14cbc9fe95bf1d2432ea7290cae0d0f7a9b6ada Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 14:53:52 +0200 Subject: [PATCH 038/133] Fix: upload flow deals better with point reuse: it actually opens the feature now --- .../Actors/FeaturePropertiesStore.ts | 79 +++--- .../NewGeometryFromChangesFeatureSource.ts | 237 ++++++++++-------- src/Logic/Osm/Actions/CreateNewNodeAction.ts | 8 +- src/Models/ThemeViewState.ts | 2 +- src/UI/Map/MapLibreAdaptor.ts | 7 - src/UI/Popup/AddNewPoint/AddNewPoint.svelte | 185 +++++++------- .../TagRendering/TagRenderingQuestion.svelte | 1 - 7 files changed, 282 insertions(+), 237 deletions(-) diff --git a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index a4f2be3e7..cd7522a29 100644 --- a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -1,5 +1,6 @@ import { FeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" +import { OsmTags } from "../../../Models/OsmFeature" /** * Constructs a UIEventStore for the properties of every Feature, indexed by id @@ -13,40 +14,6 @@ export default class FeaturePropertiesStore { } } - public getStore(id: string): UIEventSource> { - return this._elements.get(id) - } - - public trackFeatureSource(source: FeatureSource) { - const self = this - source.features.addCallbackAndRunD((features) => { - for (const feature of features) { - const id = feature.properties.id - if (id === undefined) { - console.trace("Error: feature without ID:", feature) - throw "Error: feature without ID" - } - - const source = self._elements.get(id) - if (source === undefined) { - self._elements.set(id, new UIEventSource(feature.properties)) - continue - } - - if (source.data === feature.properties) { - continue - } - - // Update the tags in the old store and link them - const changeMade = FeaturePropertiesStore.mergeTags(source.data, feature.properties) - feature.properties = source.data - if (changeMade) { - source.ping() - } - } - }) - } - /** * Overwrites the tags of the old properties object, returns true if a change was made. * Metatags are overriden if they are in the new properties, but not removed @@ -67,7 +34,7 @@ export default class FeaturePropertiesStore { } if (newProperties[oldPropertiesKey] === undefined) { changeMade = true - delete oldProperties[oldPropertiesKey] + // delete oldProperties[oldPropertiesKey] } } @@ -83,6 +50,48 @@ export default class FeaturePropertiesStore { return changeMade } + public getStore(id: string): UIEventSource> { + const store = this._elements.get(id) + if (store === undefined) { + console.error("PANIC: no store for", id) + } + return store + } + + public trackFeature(feature: { properties: OsmTags }) { + const id = feature.properties.id + if (id === undefined) { + console.trace("Error: feature without ID:", feature) + throw "Error: feature without ID" + } + + const source = this._elements.get(id) + if (source === undefined) { + this._elements.set(id, new UIEventSource(feature.properties)) + return + } + + if (source.data === feature.properties) { + return + } + + // Update the tags in the old store and link them + const changeMade = FeaturePropertiesStore.mergeTags(source.data, feature.properties) + feature.properties = source.data + if (changeMade) { + source.ping() + } + } + + public trackFeatureSource(source: FeatureSource) { + const self = this + source.features.addCallbackAndRunD((features) => { + for (const feature of features) { + self.trackFeature(feature) + } + }) + } + // noinspection JSUnusedGlobalSymbols public addAlias(oldId: string, newId: string): void { if (newId === undefined) { diff --git a/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index a9b884167..a62070e17 100644 --- a/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -4,8 +4,9 @@ import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" import { OsmId, OsmTags } from "../../../Models/OsmFeature" -import { Feature } from "geojson" -import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" +import { Feature, Point } from "geojson" +import { TagUtils } from "../../Tags/TagUtils" +import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore" export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource { // This class name truly puts the 'Java' into 'Javascript' @@ -15,115 +16,145 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc * * These elements are probably created by the 'SimpleAddUi' which generates a new point, but the import functionality might create a line or polygon too. * Other sources of new points are e.g. imports from nodes + * + * Alternatively, an already existing point might suddenly match the layer, especially if a point in a wall is reused + * + * Note that the FeaturePropertiesStore will track a featuresource, such as this one */ public readonly features: UIEventSource = new UIEventSource([]) + private readonly _seenChanges: Set + private readonly _features: Feature[] + private readonly _backend: string + private readonly _allElementStorage: IndexedFeatureSource + private _featureProperties: FeaturePropertiesStore - constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) { - const seenChanges = new Set() - const features = this.features.data + constructor( + changes: Changes, + allElementStorage: IndexedFeatureSource, + featureProperties: FeaturePropertiesStore + ) { + this._allElementStorage = allElementStorage + this._featureProperties = featureProperties + this._seenChanges = new Set() + this._features = this.features.data + this._backend = changes.backend const self = this - const backend = changes.backend - changes.pendingChanges.addCallbackAndRunD((changes) => { - if (changes.length === 0) { - return + changes.pendingChanges.addCallbackAndRunD((changes) => self.handleChanges(changes)) + } + + private addNewFeature(feature: Feature) { + const features = this._features + feature.id = feature.properties.id + features.push(feature) + } + + /** + * Handles a single pending change + * @returns true if something changed + * @param change + * @private + */ + private handleChange(change: ChangeDescription): boolean { + const backend = this._backend + const allElementStorage = this._allElementStorage + + console.log("Handling pending change") + if (change.id > 0) { + // This is an already existing object + // In _most_ of the cases, this means that this _isn't_ a new object + // However, when a point is snapped to an already existing point, we have to create a representation for this point! + // For this, we introspect the change + if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) { + // The current point already exists, we don't have to do anything here + return false + } + console.debug("Detected a reused point, for", change) + // The 'allElementsStore' does _not_ have this point yet, so we have to create it + // However, we already create a store for it + const { lon, lat } = <{ lon: number; lat: number }>change.changes + const feature = >{ + type: "Feature", + properties: { + id: change.type + "/" + change.id, + ...TagUtils.changeAsProperties(change.tags), + }, + geometry: { + type: "Point", + coordinates: [lon, lat], + }, + } + this._featureProperties.trackFeature(feature) + this.addNewFeature(feature) + return true + } else if (change.changes === undefined) { + // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point + // Not something that should be handled here + return false + } + + try { + const tags: OsmTags & { id: OsmId & string } = { + id: (change.type + "/" + change.id), + } + for (const kv of change.tags) { + tags[kv.k] = kv.v } - let somethingChanged = false + tags["_backend"] = this._backend - function add(feature) { - feature.id = feature.properties.id - features.push(feature) - somethingChanged = true + switch (change.type) { + case "node": + const n = new OsmNode(change.id) + n.tags = tags + n.lat = change.changes["lat"] + n.lon = change.changes["lon"] + const geojson = n.asGeoJson() + this.addNewFeature(geojson) + break + case "way": + const w = new OsmWay(change.id) + w.tags = tags + w.nodes = change.changes["nodes"] + w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon]) + this.addNewFeature(w.asGeoJson()) + break + case "relation": + const r = new OsmRelation(change.id) + r.tags = tags + r.members = change.changes["members"] + this.addNewFeature(r.asGeoJson()) + break + } + return true + } catch (e) { + console.error("Could not generate a new geometry to render on screen for:", e) + } + } + + private handleChanges(changes: ChangeDescription[]) { + const seenChanges = this._seenChanges + if (changes.length === 0) { + return + } + + let somethingChanged = false + + for (const change of changes) { + if (seenChanges.has(change)) { + // Already handled + continue + } + seenChanges.add(change) + + if (change.tags === undefined) { + // If tags is undefined, this is probably a new point that is part of a split road + continue } - for (const change of changes) { - if (seenChanges.has(change)) { - // Already handled - continue - } - seenChanges.add(change) - - if (change.tags === undefined) { - // If tags is undefined, this is probably a new point that is part of a split road - continue - } - - console.log("Handling pending change") - if (change.id > 0) { - // This is an already existing object - // In _most_ of the cases, this means that this _isn't_ a new object - // However, when a point is snapped to an already existing point, we have to create a representation for this point! - // For this, we introspect the change - if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) { - // The current point already exists, we don't have to do anything here - continue - } - console.debug("Detected a reused point") - // The 'allElementsStore' does _not_ have this point yet, so we have to create it - new OsmObjectDownloader(backend) - .DownloadObjectAsync(change.type + "/" + change.id) - .then((feat) => { - console.log("Got the reused point:", feat) - if (feat === "deleted") { - throw "Panic: snapping to a point, but this point has been deleted in the meantime" - } - for (const kv of change.tags) { - feat.tags[kv.k] = kv.v - } - const geojson = feat.asGeoJson() - self.features.data.push(geojson) - self.features.ping() - }) - continue - } else if (change.changes === undefined) { - // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point - // Not something that should be handled here - continue - } - - try { - const tags: OsmTags & { id: OsmId & string } = { - id: (change.type + "/" + change.id), - } - for (const kv of change.tags) { - tags[kv.k] = kv.v - } - - tags["_backend"] = backendUrl - - switch (change.type) { - case "node": - const n = new OsmNode(change.id) - n.tags = tags - n.lat = change.changes["lat"] - n.lon = change.changes["lon"] - const geojson = n.asGeoJson() - add(geojson) - break - case "way": - const w = new OsmWay(change.id) - w.tags = tags - w.nodes = change.changes["nodes"] - w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [ - lat, - lon, - ]) - add(w.asGeoJson()) - break - case "relation": - const r = new OsmRelation(change.id) - r.tags = tags - r.members = change.changes["members"] - add(r.asGeoJson()) - break - } - } catch (e) { - console.error("Could not generate a new geometry to render on screen for:", e) - } - } - if (somethingChanged) { - self.features.ping() - } - }) + somethingChanged ||= this.handleChange(change) + } + if (somethingChanged) { + this.features.ping() + } } } diff --git a/src/Logic/Osm/Actions/CreateNewNodeAction.ts b/src/Logic/Osm/Actions/CreateNewNodeAction.ts index 224f24aab..251a9413a 100644 --- a/src/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/src/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -97,7 +97,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { }, meta: this.meta, } - if (this._snapOnto === undefined) { + if (this._snapOnto?.coordinates === undefined) { return [newPointChange] } @@ -113,6 +113,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { console.log("Attempting to snap:", { geojson, projected, projectedCoor, index }) // We check that it isn't close to an already existing point let reusedPointId = undefined + let reusedPointCoordinates: [number, number] = undefined let outerring: [number, number][] if (geojson.geometry.type === "LineString") { @@ -125,11 +126,13 @@ export default class CreateNewNodeAction extends OsmCreateAction { if (GeoOperations.distanceBetween(prev, projectedCoor) < this._reusePointDistance) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index] + reusedPointCoordinates = this._snapOnto.coordinates[index] } const next = outerring[index + 1] if (GeoOperations.distanceBetween(next, projectedCoor) < this._reusePointDistance) { // We reuse this point instead! reusedPointId = this._snapOnto.nodes[index + 1] + reusedPointCoordinates = this._snapOnto.coordinates[index + 1] } if (reusedPointId !== undefined) { this.setElementId(reusedPointId) @@ -139,12 +142,13 @@ export default class CreateNewNodeAction extends OsmCreateAction { type: "node", id: reusedPointId, meta: this.meta, + changes: { lat: reusedPointCoordinates[0], lon: reusedPointCoordinates[1] }, }, ] } const locations = [ - ...this._snapOnto.coordinates.map(([lat, lon]) => <[number, number]>[lon, lat]), + ...this._snapOnto.coordinates?.map(([lat, lon]) => <[number, number]>[lon, lat]), ] const ids = [...this._snapOnto.nodes] diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 4058b8c75..77f7a5dd4 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -244,7 +244,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.newFeatures = new NewGeometryFromChangesFeatureSource( this.changes, indexedElements, - this.osmConnection.Backend() + this.featureProperties ) layoutSource.addSource(this.newFeatures) diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 75f2d54d4..4af8ac980 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -376,12 +376,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { } const background: RasterLayerProperties = this.rasterLayer?.data?.properties if (!background) { - console.error( - "Attempting to 'setBackground', but the background is", - background, - "for", - map.getCanvas() - ) return } if (this._currentRasterLayer === background.id) { @@ -457,7 +451,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { if (!map) { return } - console.log("Rotation allowed:", allow) if (allow === false) { map.rotateTo(0, { duration: 0 }) map.setPitch(0) diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index b95fbb5a5..d89e94402 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -3,109 +3,109 @@ * This component ties together all the steps that are needed to create a new point. * There are many subcomponents which help with that */ - import type { SpecialVisualizationState } from "../../SpecialVisualization" - import PresetList from "./PresetList.svelte" - import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig" - import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" - import Tr from "../../Base/Tr.svelte" - import SubtleButton from "../../Base/SubtleButton.svelte" - import FromHtml from "../../Base/FromHtml.svelte" - import Translations from "../../i18n/Translations.js" - import TagHint from "../TagHint.svelte" - import { And } from "../../../Logic/Tags/And.js" - import LoginToggle from "../../Base/LoginToggle.svelte" - import Constants from "../../../Models/Constants.js" - import FilteredLayer from "../../../Models/FilteredLayer" - import { Store, UIEventSource } from "../../../Logic/UIEventSource" - import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" - import LoginButton from "../../Base/LoginButton.svelte" - import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte" - import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" - import { OsmWay } from "../../../Logic/Osm/OsmObject" - import { Tag } from "../../../Logic/Tags/Tag" - import type { WayId } from "../../../Models/OsmFeature" - import Loading from "../../Base/Loading.svelte" - import type { GlobalFilter } from "../../../Models/GlobalFilter" - import { onDestroy } from "svelte" - import NextButton from "../../Base/NextButton.svelte" - import BackButton from "../../Base/BackButton.svelte" - import ToSvelte from "../../Base/ToSvelte.svelte" - import Svg from "../../../Svg" - import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" - import { twJoin } from "tailwind-merge" + import type { SpecialVisualizationState } from "../../SpecialVisualization"; + import PresetList from "./PresetList.svelte"; + import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"; + import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; + import Tr from "../../Base/Tr.svelte"; + import SubtleButton from "../../Base/SubtleButton.svelte"; + import FromHtml from "../../Base/FromHtml.svelte"; + import Translations from "../../i18n/Translations.js"; + import TagHint from "../TagHint.svelte"; + import { And } from "../../../Logic/Tags/And.js"; + import LoginToggle from "../../Base/LoginToggle.svelte"; + import Constants from "../../../Models/Constants.js"; + import FilteredLayer from "../../../Models/FilteredLayer"; + import { Store, UIEventSource } from "../../../Logic/UIEventSource"; + import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"; + import LoginButton from "../../Base/LoginButton.svelte"; + import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; + import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; + import { OsmWay } from "../../../Logic/Osm/OsmObject"; + import { Tag } from "../../../Logic/Tags/Tag"; + import type { WayId } from "../../../Models/OsmFeature"; + import Loading from "../../Base/Loading.svelte"; + import type { GlobalFilter } from "../../../Models/GlobalFilter"; + import { onDestroy } from "svelte"; + import NextButton from "../../Base/NextButton.svelte"; + import BackButton from "../../Base/BackButton.svelte"; + import ToSvelte from "../../Base/ToSvelte.svelte"; + import Svg from "../../../Svg"; + import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"; + import { twJoin } from "tailwind-merge"; - export let coordinate: { lon: number; lat: number } - export let state: SpecialVisualizationState + export let coordinate: { lon: number; lat: number }; + export let state: SpecialVisualizationState; let selectedPreset: { preset: PresetConfig layer: LayerConfig icon: string tags: Record - } = undefined - let checkedOfGlobalFilters: number = 0 - let confirmedCategory = false + } = undefined; + let checkedOfGlobalFilters: number = 0; + let confirmedCategory = false; $: if (selectedPreset === undefined) { - confirmedCategory = false - creating = false - checkedOfGlobalFilters = 0 + confirmedCategory = false; + creating = false; + checkedOfGlobalFilters = 0; } - let flayer: FilteredLayer = undefined - let layerIsDisplayed: UIEventSource | undefined = undefined - let layerHasFilters: Store | undefined = undefined - let globalFilter: UIEventSource = state.layerState.globalFilters - let _globalFilter: GlobalFilter[] = [] + let flayer: FilteredLayer = undefined; + let layerIsDisplayed: UIEventSource | undefined = undefined; + let layerHasFilters: Store | undefined = undefined; + let globalFilter: UIEventSource = state.layerState.globalFilters; + let _globalFilter: GlobalFilter[] = []; onDestroy( globalFilter.addCallbackAndRun((globalFilter) => { - console.log("Global filters are", globalFilter) - _globalFilter = globalFilter ?? [] + console.log("Global filters are", globalFilter); + _globalFilter = globalFilter ?? []; }) - ) + ); $: { - flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id) - layerIsDisplayed = flayer?.isDisplayed - layerHasFilters = flayer?.hasFilter + flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); + layerIsDisplayed = flayer?.isDisplayed; + layerHasFilters = flayer?.hasFilter; } - const t = Translations.t.general.add + const t = Translations.t.general.add; - const zoom = state.mapProperties.zoom + const zoom = state.mapProperties.zoom; - const isLoading = state.dataIsLoading - let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined) - let snappedToObject: UIEventSource = new UIEventSource(undefined) + const isLoading = state.dataIsLoading; + let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined); + let snappedToObject: UIEventSource = new UIEventSource(undefined); // Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map - let preciseInputIsTapped = false + let preciseInputIsTapped = false; - let creating = false + let creating = false; /** * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters. * Will delete the lastclick-location */ function abort() { - state.selectedElement.setData(undefined) + state.selectedElement.setData(undefined); // When aborted, we force the contributors to place the pin _again_ // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map - state.lastClickObject.features.setData([]) - preciseInputIsTapped = false + state.lastClickObject.features.setData([]); + preciseInputIsTapped = false; } async function confirm() { - creating = true - const location: { lon: number; lat: number } = preciseCoordinate.data - const snapTo: WayId | undefined = snappedToObject.data + creating = true; + const location: { lon: number; lat: number } = preciseCoordinate.data; + const snapTo: WayId | undefined = snappedToObject.data; const tags: Tag[] = selectedPreset.preset.tags.concat( ..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) - ) - console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags) + ); + console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); - let snapToWay: undefined | OsmWay = undefined + let snapToWay: undefined | OsmWay = undefined; if (snapTo !== undefined) { - const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0) + const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); if (downloaded !== "deleted") { - snapToWay = downloaded + snapToWay = downloaded; } } @@ -113,33 +113,42 @@ theme: state.layout?.id ?? "unkown", changeType: "create", snapOnto: snapToWay, - }) - await state.changes.applyAction(newElementAction) - state.newFeatures.features.ping() + reusePointWithinMeters: 1 + }); + await state.changes.applyAction(newElementAction); + state.newFeatures.features.ping(); // The 'changes' should have created a new point, which added this into the 'featureProperties' - const newId = newElementAction.newElementId - console.log("Applied pending changes, fetching store for", newId) - const tagsStore = state.featureProperties.getStore(newId) + const newId = newElementAction.newElementId; + console.log("Applied pending changes, fetching store for", newId); + const tagsStore = state.featureProperties.getStore(newId); + if (!tagsStore) { + console.error("Bug: no tagsStore found for", newId); + } { // Set some metainfo - const properties = tagsStore.data + const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this - delete properties["_referencing_ways"] - properties["_referencing_ways"] = `["${snapTo}"]` + delete properties["_referencing_ways"]; + properties["_referencing_ways"] = `["${snapTo}"]`; } - properties["_backend"] = state.osmConnection.Backend() - properties["_last_edit:timestamp"] = new Date().toISOString() - const userdetails = state.osmConnection.userDetails.data - properties["_last_edit:contributor"] = userdetails.name - properties["_last_edit:uid"] = "" + userdetails.uid - tagsStore.ping() + properties["_backend"] = state.osmConnection.Backend(); + properties["_last_edit:timestamp"] = new Date().toISOString(); + const userdetails = state.osmConnection.userDetails.data; + properties["_last_edit:contributor"] = userdetails.name; + properties["_last_edit:uid"] = "" + userdetails.uid; + tagsStore.ping(); } - const feature = state.indexedFeatures.featuresById.data.get(newId) - abort() - state.selectedLayer.setData(selectedPreset.layer) - state.selectedElement.setData(feature) - tagsStore.ping() + const feature = state.indexedFeatures.featuresById.data.get(newId); + console.log("Selecting feature", feature, "and opening their popup"); + abort(); + state.selectedLayer.setData(selectedPreset.layer); + state.selectedElement.setData(feature); + tagsStore.ping(); + } + + function confirmSync() { + confirm().then(_ => console.debug("New point successfully handled")).catch(e => console.error("Handling the new point went wrong due to", e)); } @@ -328,7 +337,7 @@ "absolute top-0 flex w-full justify-center p-12" )} > - +
diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 0522baf0e..878b33923 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -88,7 +88,6 @@ } - console.log("Inited 'checkMappings' to", checkedMappings); if (confg.freeform?.key) { if (!confg.multiAnswer) { // Somehow, setting multi-answer freeform values is broken if this is not set From 0811a14b2319248435c7ebf47ed53406fcaf4245 Mon Sep 17 00:00:00 2001 From: paunofu Date: Thu, 21 Sep 2023 06:20:00 +0000 Subject: [PATCH 039/133] Translated using Weblate (Catalan) Currently translated at 79.1% (2472 of 3124 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 773 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 617 insertions(+), 156 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index f46da7870..124f207ea 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" + }, "2": { "title": "un mupi sobre la paret" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tòtem" - }, - "10": { - "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Açò és un tauló d'anunis" }, + "10": { + "then": "Açò és una paret pintada" + }, "2": { "then": "Açò és una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Açò és un tòtem" - }, - "10": { - "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,6 +205,9 @@ "1": { "then": "Tauló d'anuncis" }, + "10": { + "then": "Paret Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tòtem" - }, - "10": { - "then": "Paret Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relleu" - }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -1820,6 +1820,27 @@ "1": { "question": "Té un connector
Schuko sense pin de terra (CEE7/4 tipus F)
connector" }, + "10": { + "question": "Té un connector
Tipus 2 amb cable (mennekes)
" + }, + "11": { + "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" + }, + "12": { + "question": "Té un connector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" + }, + "14": { + "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" + }, + "15": { + "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" + }, + "16": { + "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" + }, "2": { "question": "Té un connector
endoll de paret Europeu amb un pin de terra (CEE7/4 tipus F)
" }, @@ -1843,27 +1864,6 @@ }, "9": { "question": "Té un connector
CCS Tipus 2 (mennekes)
" - }, - "10": { - "question": "Té un connector
Tipus 2 amb cable (mennekes)
" - }, - "11": { - "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" - }, - "12": { - "question": "Té un connector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" - }, - "14": { - "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" - }, - "15": { - "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" - }, - "16": { - "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" } } } @@ -1919,30 +1919,6 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, - "2": { - "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" - }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipus 1 amb cable (J1772)" - }, - "7": { - "then": "Tipus 1 amb cable (J1772)" - }, - "8": { - "then": "Tipus 1 sense cable (J1772)" - }, - "9": { - "then": "Tipus 1 sense cable (J1772)" - }, "10": { "then": "CSS 1Tipus 1 (també conegut com Tipus 1 combo)" }, @@ -1973,8 +1949,11 @@ "19": { "then": "Tipus 2 amb cable (mennekes)" }, + "2": { + "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" + }, "20": { - "then": "CSS Supercarregador Tesla (tipus2_css de la marca)" + "then": "CSS Supercarregador Tesla (un tipus2_css de la marca)" }, "21": { "then": "CSS Supercarregador Tesla (un tipus2_css de la marca)" @@ -2003,11 +1982,32 @@ "29": { "then": "Bosch Active Connect amb 3 pins i cable" }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, "30": { "then": "Bosch Active Connect amb 5 pins i cable" }, "31": { "then": "Bosch Active Connect amb 5 pins i cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipus 1 amb cable (J1772)" + }, + "7": { + "then": "Tipus 1 amb cable (J1772)" + }, + "8": { + "then": "Tipus 1 sense cable (J1772)" + }, + "9": { + "then": "Tipus 1 sense cable (J1772)" } }, "question": "Quins tipus de connexions de càrrega estan disponibles aquí?" @@ -2061,7 +2061,7 @@ "Parking:fee": { "mappings": { "0": { - "then": "No hi ha costos d'aparcament addicionals mentre es carrega" + "then": "No cal pagar una taxa addicional mentres es carrega" }, "1": { "then": "Cal pagar una taxa addicional d'aparcament mentres es carrega" @@ -2254,6 +2254,9 @@ } } }, + "questions-technical": { + "render": "

Preguntes tècniques

Les preguntes següents són molt tècniques. No dubteu a ignorar-les
{questions(tehcnical)}" + }, "ref": { "question": "Quin és el número de referència d'aquest punt de càrrega?" }, @@ -2862,6 +2865,9 @@ "1": { "then": "Aquest carril bici està pavimentat" }, + "10": { + "then": "Aquesta via ciclista està feta de gravilla" + }, "2": { "then": "Aquest carril bici està fet d'asfalt" }, @@ -2873,9 +2879,6 @@ }, "9": { "then": "Aquesta via ciclista està feta de grava" - }, - "10": { - "then": "Aquesta via ciclista està feta de gravilla" } }, "question": "De què està feta la superfície d'aquest carrer?", @@ -3994,6 +3997,21 @@ "1": { "then": "Això és una fregiduria" }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" + }, "2": { "then": "Principalment serveix pasta" }, @@ -4017,21 +4035,6 @@ }, "9": { "then": "Aquí es serveixen plats francesos" - }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin menjar es serveix aquí?", @@ -5757,6 +5760,9 @@ "1": { "then": "S'accepten monedes de 2 cèntims" }, + "10": { + "then": "S'accepten monedes de 20 cèntims" + }, "2": { "then": "S'accepten monedes de 5 cèntims" }, @@ -5780,9 +5786,6 @@ }, "9": { "then": "S'accepten monedes de 10 cèntims" - }, - "10": { - "then": "S'accepten monedes de 20 cèntims" } }, "question": "Quines monedes es poden utilitzar per a pagar aquí?" @@ -6168,30 +6171,6 @@ "1": { "question": "Reciclatge de piles" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" - }, "10": { "question": "Reciclatge de vidre" }, @@ -6222,11 +6201,35 @@ "19": { "question": "Reciclatge del rebuig" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, "20": { "question": "Reciclatge de cartutxos d'impressora" }, "21": { "question": "Reciclatge de bicicletes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" } } }, @@ -6294,30 +6297,6 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" - }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -6348,6 +6327,9 @@ "19": { "then": "Aquí es poden reciclar sabates" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -6362,6 +6344,27 @@ }, "24": { "then": "Aquí es poden reciclar bicicletes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -7020,6 +7023,12 @@ "1": { "then": "Aquest fanal utilitza LED" }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" + }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -7043,12 +7052,6 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" - }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -7183,23 +7186,107 @@ } }, "ticket_machine": { + "description": "Troba màquines de bitllets per a bitllets de transport públic", + "name": "Màquines de bitllets", "tagRenderings": { "operator": { - "question": "Qui és l'operador d'aquesta màquina de venda de bitllets?" + "freeform": { + "placeholder": "Nom de l'operador" + }, + "mappings": { + "0": { + "then": "Ferrocarrils holandesos (NS)" + } + }, + "question": "Qui és l'operador d'aquesta màquina de venda de bitllets?", + "render": "{operator} opera aquesta màquina de bitllets" } + }, + "title": { + "render": "Màquina de bitllets" + } + }, + "ticket_validator": { + "description": "Trobeu validadors de bitllets per validar bitllets de transport públic", + "name": "Validadors de bitllets", + "presets": { + "0": { + "description": "Un validador de bitllets per validar un bitllet de transport públic. Pot ser un lector digital, llegir una targeta o un bitllet, o una màquina que estampa o perfora un bitllet.", + "title": "un validador de bitllets" + } + }, + "tagRenderings": { + "barrier": { + "mappings": { + "0": { + "then": "Aquest validador de bitllets forma part d'una porta" + } + }, + "render": "Aquest validador de bitllets forma part d'una barrera de tipus {barrier}" + }, + "payment-options": { + "override": { + "mappings+": { + "0": { + "then": "Aquest validador de bitllets accepta OV-Chipkaart" + }, + "1": { + "then": "Aquest validador de bitllets accepta OV-Chipkaart" + } + } + } + }, + "validator-operator": { + "freeform": { + "placeholder": "Nom de l'operador" + }, + "mappings": { + "0": { + "then": "Ferrocarrils holandesos (NS)" + } + }, + "question": "Qui és l'operador d'aquest validador de bitllets?", + "render": "{operator} opera aquest validador de bitllets" + } + }, + "title": { + "render": "Validador de bitllets" } }, "toilet": { + "description": "Una capa que mostra banys (publics)", + "filter": { + "0": { + "options": { + "0": { + "question": "Accessible amb cadira de rodes" + } + } + }, + "1": { + "options": { + "0": { + "question": "Té un canviador" + } + } + } + }, "name": "Lavabos", "presets": { "0": { "title": "un lavabo públic" }, "1": { + "description": "Un lavabo que tingui almenys un lavabo accessible per a cadira de rodes", "title": "un lavabo amb lavabo accessible per a cadires de rodes" } }, "tagRenderings": { + "opening_hours_24_7": { + "override": { + "question": "Quan obrin aquests lavabos?" + } + }, "toilet-access": { "mappings": { "0": { @@ -7213,12 +7300,36 @@ }, "3": { "then": "Accessible, però s'ha de demanar la clau per a entrar" + }, + "4": { + "then": "Accés públic" } }, "question": "Aquests serveis són d'accés públic?", "render": "L'accés és {access}" }, + "toilet-changing_table:location": { + "mappings": { + "0": { + "then": "El canviador està al lavabo per a dones. " + }, + "1": { + "then": "El canviador està al lavabo per a homes. " + }, + "2": { + "then": "El canviador està al lavabo per a usuaris de cadira de rodes. " + }, + "3": { + "then": "El canviador està en una habitació dedicada. " + } + }, + "question": "On està el canviador?", + "render": "El cambiador està a {changing_table:location}" + }, "toilet-charge": { + "freeform": { + "placeholder": "p. ex. 0.50 eur" + }, "question": "Quant s'ha de pagar per aquests lavabos?", "render": "La taxa és {charge}" }, @@ -7298,22 +7409,63 @@ "question": "Hi ha un lavabo específic per a usuaris amb cadira de rodes?" }, "wheelchair-door-width": { - "question": "Quina és l'amplada de la porta per al lavabo accéssible?" + "question": "Quina és l'amplada de la porta per al lavabo accéssible?", + "render": "La porta del vàter accessible amb cadira de rodes té {canònic(porta:amplada)} d'ample" } }, "title": { "render": "Lavabo" + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": "metre" + }, + "1": { + "human": "centimetre" + } + } + } } }, "toilet_at_amenity": { + "description": "Una capa que mostra banys (públics) ubicats en diferents llocs", + "filter": { + "0": { + "options": { + "0": { + "question": "Accessible amb cadira de rodes" + } + } + } + }, + "name": "", "tagRenderings": { "toilet-access": { "mappings": { + "0": { + "then": "Accés públic" + }, "1": { "then": "Només accessible a clients de l'instal·lació" + }, + "2": { + "then": "No accessible, inclús per als clients de la instal·lació" + }, + "3": { + "then": "Accessible, però hi ha que demanar la clau per a entrar" + }, + "4": { + "then": "Accés públic" } }, - "question": "Aquests serveis són d'accés públic?" + "question": "Aquests serveis són d'accés públic?", + "render": "L'accés és {toilet:access}" + }, + "toilet-charge": { + "question": "Quant s'ha de pagar per aquests lavabos?", + "render": "La taxa és {toilet:charge}" }, "toilets-fee": { "mappings": { @@ -7352,6 +7504,7 @@ "name": "Línies de bus", "tagRenderings": { "colour": { + "question": "Quin és el color d'aquest línia d'autobús?", "render": "Aquesta línea d'autobús té el color {colour}" }, "from": { @@ -7363,6 +7516,7 @@ "render": "Aquesta línea d'autobús és part de la xarxa {network}" }, "operator": { + "question": "Quina companyia opera aquesta línia d'autobús?", "render": "{operator} opera aquesta línea d'autobús" }, "to": { @@ -7499,13 +7653,20 @@ }, "tree-heritage": { "mappings": { + "1": { + "then": "Registrat com a patrimoni per la Direction du Patrimoine culturel Brussel·les" + }, "2": { "then": "Registrat com a patrimoni per una organització diferent" }, "3": { "then": "No registrat com a patrimoni" + }, + "4": { + "then": "Registrat com a patrimoni per una organització diferent" } - } + }, + "question": "Aquest arbre és patrimoni registrat?" }, "tree-leaf_type": { "mappings": { @@ -7514,8 +7675,32 @@ }, "1": { "then": "Amb fulles d'agulla" + }, + "2": { + "then": "Permanentment sense fulles" } - } + }, + "question": "És un arbre de fulla ampla o d'agulla?" + }, + "tree-species-wikidata": { + "question": "Quina espècie és aquest arbre?" + }, + "tree_node-name": { + "mappings": { + "0": { + "then": "L'arbre no té nom." + } + }, + "question": "Té nom aquest arbre?", + "render": "Nom : {name}" + }, + "tree_node-ref:OnroerendErfgoed": { + "question": "Quina és la identificació emesa per Onroerend Erfgoed Flanders?", + "render": "\"\"/Identifiació Onroerend Erfgoed: {ref:OnroerendErfgoed}" + }, + "tree_node-wikidata": { + "question": "Quin és l'identificador de Wikidata d'aquest arbre?", + "render": "\"\"/Wikidata{wikidata}" } }, "title": { @@ -7523,6 +7708,7 @@ } }, "usersettings": { + "description": "Una capa especial que no està pensada per mostrar-se en un mapa, però que s'utilitza per configurar la configuració de l'usuari", "tagRenderings": { "all-questions-at-once": { "mappings": { @@ -7532,7 +7718,8 @@ "1": { "then": "Mostra les preguntes una per una" } - } + }, + "question": "Les preguntes amb camps de dades desconeguts haurien d'aparèixer una per una o juntes?" }, "contributor-thanks": { "mappings": { @@ -7552,6 +7739,43 @@ "mappings": { "0": { "then": "Permet girar el mapa" + }, + "1": { + "then": "Mantingueu sempre el nord apuntant cap amunt" + } + }, + "question": "El nord hauria d'estar sempre amunt?" + }, + "inbox": { + "mappings": { + "0": { + "then": { + "special": { + "text": "Obre la teva safata d'entrada" + } + } + }, + "1": { + "then": { + "special": { + "text": "Tens {_unreadMessages}
Open your inbox" + } + } + } + } + }, + "language_picker": { + "mappings": { + "0": { + "then": "L'idioma es va establir mitjançant un paràmetre d'URL i l'usuari no pot definir-lo.²" + } + } + }, + "mangrove-keys": { + "render": { + "after": "Qualsevol persona que tingui aquest fitxer pot fer ressenyes amb la vostra identitat", + "special": { + "text": "Baixeu la clau privada del vostre compte de Mangrove" } } }, @@ -7572,6 +7796,13 @@ }, "question": "Sota quina llicència vols publicar les teves fotos?" }, + "settings-link": { + "render": { + "special": { + "text": "Obriu la vostra configuració a OpenStreetMap.org" + } + } + }, "show_debug": { "mappings": { "0": { @@ -7579,16 +7810,30 @@ }, "1": { "then": "No mostris informació de depuració" + }, + "2": { + "then": "No mostris informació de depuració" } }, "question": "Vols mostrar la informació de depuració de la configuració de l'usuari?" }, "show_tags": { "mappings": { + "0": { + "then": "No mostris mai les etiquetes." + }, + "1": { + "then": "Mostra les etiquetes que s'aplicaran un cop hagi fet {__userjourney_tagsVisibleAt} conjunts de canvis" + }, + "2": { + "then": "Mostra les etiquetes que s'aplicaran en fer un canvi" + }, "3": { "then": "Mostra les etiquetes que s'aplicaran a l'hora de fer un canvi i mostra la taula d'etiquetes a cada element" } - } + }, + "question": "Mostra les etiquetes d'OpenStreetMap en brut?", + "questionHint": "Les etiquetes són atributs que té cada element. Aquestes són les dades tècniques que s'emmagatzemen a la base de dades. No necessiteu aquesta informació per editar amb MapComplete, però és possible que els usuaris avançats la vulguin fer servir com a referència." }, "translation-completeness": { "mappings": { @@ -7630,21 +7875,211 @@ "mappings": { "0": { "then": "S'ha trobat un enllaç al vostre perfil de Mastodon: {_mastodon_link}" + }, + "1": { + "then": "Hem trobat un enllaç al que sembla ser un compte de mastodon, però no està verificat. Editeu la descripció del vostre perfil i col·loqueu-hi el següent: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>" } } } + }, + "title": { + "render": "Configuració" + } + }, + "vending_machine": { + "description": "Capa que mostra màquines expenedores", + "filter": { + "1": { + "options": { + "0": { + "question": "Totes les màquines expenedores" + }, + "1": { + "question": "Venda de begudes" + }, + "10": { + "question": "Venda de llet" + }, + "11": { + "question": "Venda de pa" + }, + "12": { + "question": "Venda d'ous" + }, + "13": { + "question": "Venda de formatge" + }, + "14": { + "question": "Venda de mel" + }, + "15": { + "question": "Venda de patates" + }, + "16": { + "question": "Venda de flors" + }, + "2": { + "question": "Venda de llaminadures" + }, + "3": { + "question": "Venda de menjar" + }, + "4": { + "question": "Venda de tabaco" + }, + "5": { + "question": "Venda de preservatius" + }, + "6": { + "question": "Venda de cafè" + }, + "7": { + "question": "Venda d'aigua" + }, + "8": { + "question": "Venda de diaris" + }, + "9": { + "question": "Venda de càmeres interiors de bicicletes" + } + } + } + }, + "name": "Màquines expenedores", + "presets": { + "0": { + "title": "una màquina expenedora" + } + }, + "tagRenderings": { + "indoor": { + "mappings": { + "0": { + "then": "Aquesta màquina expenedora està a l'exterior" + }, + "1": { + "then": "Aquesta màquina expenedora està a l'interior" + }, + "2": { + "then": "Aquesta màquina expenedora està a l'exterior" + } + }, + "question": "Aquesta màquina expenedora està a l'exterior?" + }, + "operator": { + "freeform": { + "placeholder": "Nom de l'operadora" + }, + "question": "Qui opera aquesta màquina expenedora?", + "render": "{operator} gestiona aquesta màquina expenedora" + }, + "vending": { + "mappings": { + "0": { + "then": "Es venen begudes" + }, + "1": { + "then": "Es venen llaminadures" + }, + "10": { + "then": "Es ven pa" + }, + "11": { + "then": "Es venen ous" + }, + "12": { + "then": "Es ven formatge" + }, + "13": { + "then": "Es ven mel" + }, + "14": { + "then": "Es venen papes" + }, + "15": { + "then": "Es venen flors" + }, + "16": { + "then": "Es venen tiquets d'aparcament" + }, + "17": { + "then": "Es venen cèntims premsats" + }, + "18": { + "then": "Es venen bitllets de transport públic" + }, + "2": { + "then": "Es ven menjar" + }, + "3": { + "then": "Es ven tabaco" + }, + "4": { + "then": "Es venen preservatius" + }, + "5": { + "then": "Es ven cafè" + }, + "6": { + "then": "Es ven aigua" + }, + "7": { + "then": "Es venen diaris" + }, + "8": { + "then": "Es venen càmeres interiors de bicicletes" + }, + "9": { + "then": "Es ven llet" + } + }, + "question": "Que ven aquesta màquina expenedora?", + "render": "Aquesta màquina expenedora ven {vending}" + } + }, + "title": { + "mappings": { + "0": { + "then": "Maquina expenedora {name}" + }, + "1": { + "then": "Màquina expenedora {brand}" + } + }, + "render": "Màquina expenedora" } }, "veterinary": { - "name": "Veterinari" + "name": "Veterinari", + "presets": { + "0": { + "description": "un veterinari, que tracta gossos", + "title": "un veterinari" + } + }, + "tagRenderings": { + "vetName": { + "question": "Qui és el nom d'aquest veterinari?", + "render": "El nom d'aquest veterinari és {name}" + } + }, + "title": { + "render": "Veterinari" + } }, "viewpoint": { + "description": "Un bon mirador o una bonica vista. Ideal per afegir una imatge si no hi caben cap altra categoria", "name": "Mirador", "presets": { "0": { "title": "un mirador" } }, + "tagRenderings": { + "viewpoint-description": { + "question": "Vols afegir una descripció?" + } + }, "title": { "render": "Mirador" } @@ -7653,6 +8088,8 @@ "description": "Una capa que mostra “village-green” (que són zones verdes comunals, però no parcs del tot)" }, "visitor_information_centre": { + "description": "Un centre de visitants ofereix informació sobre una atracció o lloc d'interès específic on es troba.", + "name": "Centre d'Informació al Visitant", "title": { "mappings": { "1": { @@ -7760,6 +8197,9 @@ }, "1": { "then": "Aquesta paperera no té un dispensador per a bosses d'excrements (gossos)" + }, + "2": { + "then": "Aquesta paperera no té un dispensador per a bosses d'excrements (gossos)" } }, "question": "Aquesta paperera té un dispensador per a bosses per a excrements de gossos?" @@ -7796,6 +8236,7 @@ } }, "waste_disposal": { + "description": "Contenidor de fem, contenidor mitjà o gran per a l'eliminació de residus (domèstics)", "filter": { "0": { "options": { @@ -7853,6 +8294,9 @@ }, "question": "Quin tipus de contenidor de brossa és aquest?" } + }, + "title": { + "render": "Contenidor de fem" } }, "windturbine": { @@ -7868,7 +8312,24 @@ "question": "Quin és el diàmetre del rotor d'aquest aerogenerador en metres?", "render": "El diàmetre del rotor d'aquest aerogenerador és de {rotor:diameter} metres." }, + "turbine-height": { + "question": "Quina és l'alçada total d'aquest aerogenerador (inclòs el radi del rotor), en metres?", + "render": "L'alçada total (inclòs el radi del rotor) d'aquest aerogenerador és de {altura} metres." + }, + "turbine-operator": { + "question": "Qui opera aquest aerogenerador?", + "render": "{operator} gestiona aquest aerogenerador." + }, + "turbine-output": { + "question": "Quina és la potència de sortida d'aquest aerogenerador? (p. ex. 2,3 MW)", + "render": "La potència de sortida d'aquest aerogenerador és {generator:output:electricity}." + }, + "turbine-start-date": { + "question": "Quan va entrar en funcionament aquest aerogenerador?", + "render": "Aquest aerogenerador va entrar en funcionament el dia {start_date}." + }, "windturbine-fixme": { + "question": "Hi ha alguna cosa malament en la manera que està mapejat això que no heu pogut solucionar aquí? (deixeu una nota als experts d'OpenStreetMap)", "render": "Informació addicional per als experts en OpenStreetMap: {fixme}" } }, @@ -7906,4 +8367,4 @@ } } } -} \ No newline at end of file +} From bf2f3d97ffbf1e61807aa8fb7b4d4da5464607f1 Mon Sep 17 00:00:00 2001 From: paunofu Date: Thu, 21 Sep 2023 06:41:26 +0000 Subject: [PATCH 040/133] Translated using Weblate (Spanish) Currently translated at 45.2% (1414 of 3124 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/es/ --- langs/layers/es.json | 297 +++++++++++++++++++++---------------------- 1 file changed, 146 insertions(+), 151 deletions(-) diff --git a/langs/layers/es.json b/langs/layers/es.json index a15a76200..7a0ca49a9 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un lletrer" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" + }, "2": { "title": "un mupi sobre la pared" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tótem" - }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un lletrer" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Esto es un tablón de anuncios" }, + "10": { + "then": "Esto es una pared pintada" + }, "2": { "then": "Esto es una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Esto es un tótem" - }, - "10": { - "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,6 +205,9 @@ "1": { "then": "Tablon de anuncios" }, + "10": { + "then": "Pared Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tótem" - }, - "10": { - "then": "Pared Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" + }, + "12": { + "then": "Tallado en madera" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relieve" - }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, - "12": { - "then": "Tallado en madera" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1440,6 +1440,27 @@ "0": { "question": "Todos los conectores" }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" + }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E)
" }, @@ -1463,27 +1484,6 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" - }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1538,30 +1538,6 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" - }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1592,6 +1568,9 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1622,11 +1601,32 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -2021,6 +2021,12 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2035,12 +2041,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2086,6 +2086,9 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2097,9 +2100,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -2724,6 +2724,18 @@ "0": { "then": "Esto es una pizzería" }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" + }, "2": { "then": "Principalmente sirve pasta" }, @@ -2744,18 +2756,6 @@ }, "9": { "then": "Aquí se sirven platos Franceses" - }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué comida se sirve aquí?", @@ -3153,6 +3153,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las nostras de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } + }, "2": { "options": { "0": { @@ -3208,19 +3221,6 @@ "question": "Solo mostrar las notas abiertas" } } - }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las nostras de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } } }, "name": "Notas de OpenStreetMap", @@ -3836,21 +3836,6 @@ "1": { "question": "Reciclaje de baterías" }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "9": { - "question": "Reciclaje de botellas de cristal" - }, "10": { "question": "Reciclaje de cristal" }, @@ -3874,6 +3859,21 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" + }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "9": { + "question": "Reciclaje de botellas de cristal" } } } @@ -3916,24 +3916,6 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" - }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -3957,6 +3939,24 @@ }, "19": { "then": "Aquí se pueden reciclar zapatos" + }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -4260,11 +4260,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}" @@ -4305,6 +4300,12 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" + }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -4325,12 +4326,6 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" - }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -4773,7 +4768,7 @@ "tree_node-name": { "mappings": { "0": { - "then": "No identificas la especie." + "then": "El árbol no tiene nombre." } }, "question": "¿Tiene nombre este árbol?", @@ -4831,7 +4826,7 @@ } }, "viewpoint": { - "description": "Un buen punto de vista o una buena vista. Ideal para añadir una imagen si no encaja en ninguna otra categoría", + "description": "Un buen mirador o una buena vista. Ideal para añadir una imagen si no encaja en ninguna otra categoría", "tagRenderings": { "viewpoint-description": { "question": "¿Quieres añadir una descripción?" @@ -4905,4 +4900,4 @@ } } } -} \ No newline at end of file +} From d7c054b4d2cde2befdc650b613d57720ef419439 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 21 Sep 2023 14:59:18 +0200 Subject: [PATCH 041/133] Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ --- langs/layers/ca.json | 1 - 1 file changed, 1 deletion(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 124f207ea..cca9b48dc 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -7440,7 +7440,6 @@ } } }, - "name": "", "tagRenderings": { "toilet-access": { "mappings": { From d522c2fc4e7d642340dceac8c2820a2bc0fb7739 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 13:02:57 +0000 Subject: [PATCH 042/133] Translated using Weblate (Catalan) Currently translated at 79.1% (2472 of 3124 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index cca9b48dc..92282f44e 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -7410,7 +7410,7 @@ }, "wheelchair-door-width": { "question": "Quina és l'amplada de la porta per al lavabo accéssible?", - "render": "La porta del vàter accessible amb cadira de rodes té {canònic(porta:amplada)} d'ample" + "render": "La porta del vàter accessible amb cadira de rodes té {canonical(door:width)} d'ample" } }, "title": { @@ -7460,11 +7460,11 @@ } }, "question": "Aquests serveis són d'accés públic?", - "render": "L'accés és {toilet:access}" + "render": "L'accés és {toilets:access}" }, "toilet-charge": { "question": "Quant s'ha de pagar per aquests lavabos?", - "render": "La taxa és {toilet:charge}" + "render": "La taxa és {toilets:charge}" }, "toilets-fee": { "mappings": { From 45f3aadef34b241e8b7ced0e0aee58a21890fb6a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 13:08:29 +0000 Subject: [PATCH 043/133] Translated using Weblate (Catalan) Currently translated at 79.1% (2472 of 3124 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 92282f44e..79048f0ad 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -8313,7 +8313,7 @@ }, "turbine-height": { "question": "Quina és l'alçada total d'aquest aerogenerador (inclòs el radi del rotor), en metres?", - "render": "L'alçada total (inclòs el radi del rotor) d'aquest aerogenerador és de {altura} metres." + "render": "L'alçada total (inclòs el radi del rotor) d'aquest aerogenerador és de {height} metres." }, "turbine-operator": { "question": "Qui opera aquest aerogenerador?", From db1dbc0f07c3fdda08dbb0be229893be03cdf8ab Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 15:28:40 +0200 Subject: [PATCH 044/133] Fix tests, version bump --- package.json | 2 +- src/Logic/Osm/OsmConnection.ts | 2 +- test/Logic/Actors/Actors.spec.ts | 2 +- .../OSM/Actions/ReplaceGeometryAction.spec.ts | 6 +++--- test/Logic/OSM/Actions/SplitAction.spec.ts | 16 ++++++++-------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 2216e5314..443d54e3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.33.1", + "version": "0.33.2", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 0ee8505d1..e650250a9 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -179,7 +179,7 @@ export class OsmConnection { /** * The backend host, without path or trailing '/' * - * new OsmConnection().Backend() // => "https://api.openstreetmap.org" + * new OsmConnection().Backend() // => "https://www.openstreetmap.org" */ public Backend(): string { return this._oauth_config.url diff --git a/test/Logic/Actors/Actors.spec.ts b/test/Logic/Actors/Actors.spec.ts index 751964722..ef31ee319 100644 --- a/test/Logic/Actors/Actors.spec.ts +++ b/test/Logic/Actors/Actors.spec.ts @@ -21,7 +21,7 @@ const latestTags = { "public_bookcase:type": "reading_box", } -Utils.injectJsonDownloadForTests("https://api.openstreetmap.org/api/0.6/node/5568693115", { +Utils.injectJsonDownloadForTests("https://www.openstreetmap.org/api/0.6/node/5568693115", { version: "0.6", generator: "CGImap 0.8.5 (1815943 spike-06.openstreetmap.org)", copyright: "OpenStreetMap and contributors", diff --git a/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts b/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts index bcc6e69d7..94098e525 100644 --- a/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts +++ b/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts @@ -327,7 +327,7 @@ describe("ReplaceGeometryAction", () => { const wayId = "way/160909312" Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/map.json?bbox=3.2166673243045807,51.21467321525788,3.217007964849472,51.21482442824023", + "https://www.openstreetmap.org/api/0.6/map.json?bbox=3.2166673243045807,51.21467321525788,3.217007964849472,51.21482442824023", { version: "0.6", generator: "CGImap 0.8.6 (1549677 spike-06.openstreetmap.org)", @@ -715,7 +715,7 @@ describe("ReplaceGeometryAction", () => { } ) - Utils.injectJsonDownloadForTests("https://api.openstreetmap.org/api/0.6/way/160909312/full", { + Utils.injectJsonDownloadForTests("https://www.openstreetmap.org/api/0.6/way/160909312/full", { version: "0.6", generator: "CGImap 0.8.6 (2407324 spike-06.openstreetmap.org)", copyright: "OpenStreetMap and contributors", @@ -880,7 +880,7 @@ describe("ReplaceGeometryAction", () => { [3.2166673243045807, 51.21467321525788], [3.217007964849472, 51.21482442824023], ]) - const url = `https://api.openstreetmap.org/api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` + const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` const data = await Utils.downloadJson(url) const fullNodeDatabase = new FullNodeDatabaseSource() fullNodeDatabase.handleOsmJson(data, 0, 0, 0) diff --git a/test/Logic/OSM/Actions/SplitAction.spec.ts b/test/Logic/OSM/Actions/SplitAction.spec.ts index aee9a5860..53147d224 100644 --- a/test/Logic/OSM/Actions/SplitAction.spec.ts +++ b/test/Logic/OSM/Actions/SplitAction.spec.ts @@ -9,7 +9,7 @@ describe("SplitAction", () => { { // Setup of download Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/941079939/full", + "https://www.openstreetmap.org/api/0.6/way/941079939/full", { version: "0.6", generator: "CGImap 0.8.5 (957273 spike-08.openstreetmap.org)", @@ -210,7 +210,7 @@ describe("SplitAction", () => { ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/941079939/relations", + "https://www.openstreetmap.org/api/0.6/way/941079939/relations", { version: "0.6", generator: "CGImap 0.8.5 (2419440 spike-07.openstreetmap.org)", @@ -222,7 +222,7 @@ describe("SplitAction", () => { ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/295132739/full", + "https://www.openstreetmap.org/api/0.6/way/295132739/full", { version: "0.6", generator: "CGImap 0.8.5 (3138407 spike-07.openstreetmap.org)", @@ -409,7 +409,7 @@ describe("SplitAction", () => { } ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/295132739/relations", + "https://www.openstreetmap.org/api/0.6/way/295132739/relations", // Mimick that there are no relations relation is missing { version: "0.6", @@ -422,7 +422,7 @@ describe("SplitAction", () => { ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/61435323/full", + "https://www.openstreetmap.org/api/0.6/way/61435323/full", { version: "0.6", generator: "CGImap 0.8.5 (53092 spike-08.openstreetmap.org)", @@ -488,7 +488,7 @@ describe("SplitAction", () => { } ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/61435323/relations", + "https://www.openstreetmap.org/api/0.6/way/61435323/relations", { version: "0.6", generator: "CGImap 0.8.5 (3622541 spike-06.openstreetmap.org)", @@ -2567,7 +2567,7 @@ describe("SplitAction", () => { } ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/61435332/full", + "https://www.openstreetmap.org/api/0.6/way/61435332/full", { version: "0.6", generator: "CGImap 0.8.5 (3819319 spike-06.openstreetmap.org)", @@ -2620,7 +2620,7 @@ describe("SplitAction", () => { } ) Utils.injectJsonDownloadForTests( - "https://api.openstreetmap.org/api/0.6/way/509668834/full", + "https://www.openstreetmap.org/api/0.6/way/509668834/full", { version: "0.6", generator: "CGImap 0.8.5 (3735280 spike-06.openstreetmap.org)", From badfbb60e41ba0745df3e5b3dead9ab47a5c8e97 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 21 Sep 2023 15:29:34 +0200 Subject: [PATCH 045/133] Chore: formatting --- Docs/BuiltinQuestions.md | 25 +- Docs/Layers/all_vending_machine.md | 21 +- Docs/Layers/artwork.md | 4 +- Docs/Layers/bench.md | 4 +- Docs/Layers/bicycle_library.md | 4 +- Docs/Layers/bicycle_rental.md | 4 +- Docs/Layers/bicycle_rental_non_docking.md | 4 +- Docs/Layers/bike_cafe.md | 4 +- Docs/Layers/bike_shop.md | 4 +- Docs/Layers/bike_themed_object.md | 4 +- Docs/Layers/birdhide.md | 2 +- Docs/Layers/cafe_pub.md | 4 +- Docs/Layers/car_rental.md | 4 +- Docs/Layers/climbing.md | 2 +- Docs/Layers/climbing_area.md | 2 +- Docs/Layers/climbing_club.md | 4 +- Docs/Layers/climbing_gym.md | 4 +- Docs/Layers/defibrillator.md | 2 +- Docs/Layers/dentist.md | 4 +- Docs/Layers/doctors.md | 4 +- Docs/Layers/dogfoodb.md | 8 +- Docs/Layers/dogshop.md | 4 +- Docs/Layers/drinking_water.md | 1 + Docs/Layers/elevator.md | 38 + Docs/Layers/elongated_coin.md | 37 +- Docs/Layers/fitness_centre.md | 4 +- Docs/Layers/food.md | 8 +- Docs/Layers/friture.md | 8 +- Docs/Layers/ghost_bike.md | 2 +- Docs/Layers/governments.md | 4 +- Docs/Layers/hackerspace.md | 4 +- Docs/Layers/hospital.md | 4 +- Docs/Layers/hotel.md | 4 +- Docs/Layers/kindergarten_childcare.md | 4 +- Docs/Layers/medical-shops.md | 4 +- Docs/Layers/nature_reserve.md | 4 +- Docs/Layers/parking_ticket_machine.md | 21 +- Docs/Layers/pharmacy.md | 4 +- Docs/Layers/physiotherapist.md | 4 +- Docs/Layers/public_bookcase.md | 4 +- Docs/Layers/recycling.md | 4 +- Docs/Layers/school.md | 4 +- Docs/Layers/shops.md | 4 +- .../Layers/shops_with_climbing_shoe_repair.md | 10 +- Docs/Layers/shower.md | 2 +- Docs/Layers/sport_pitch.md | 4 +- Docs/Layers/sport_shops.md | 4 +- Docs/Layers/sports_centre.md | 4 +- Docs/Layers/tertiary_education.md | 4 +- Docs/Layers/ticket_machine.md | 21 +- Docs/Layers/toilet.md | 3 +- Docs/Layers/toilet_at_amenity.md | 2 +- Docs/Layers/vending_machine.md | 21 +- Docs/SpecialInputElements.md | 16 +- Docs/SpecialRenderings.md | 14 +- Docs/TagInfo/mapcomplete_atm.json | 14 +- Docs/TagInfo/mapcomplete_bicycle_rental.json | 2 +- Docs/TagInfo/mapcomplete_bicyclelib.json | 2 +- Docs/TagInfo/mapcomplete_blind_osm.json | 20 + Docs/TagInfo/mapcomplete_cafes_and_pubs.json | 2 +- Docs/TagInfo/mapcomplete_climbing.json | 323 ++++++- Docs/TagInfo/mapcomplete_cyclofix.json | 10 +- Docs/TagInfo/mapcomplete_education.json | 6 +- Docs/TagInfo/mapcomplete_elongated_coin.json | 106 ++- Docs/TagInfo/mapcomplete_food.json | 2 +- Docs/TagInfo/mapcomplete_fritures.json | 2 +- Docs/TagInfo/mapcomplete_ghostbikes.json | 2 +- Docs/TagInfo/mapcomplete_hackerspaces.json | 2 +- Docs/TagInfo/mapcomplete_healthcare.json | 14 +- Docs/TagInfo/mapcomplete_hotels.json | 2 +- Docs/TagInfo/mapcomplete_indoors.json | 20 + Docs/TagInfo/mapcomplete_onwheels.json | 36 +- Docs/TagInfo/mapcomplete_parkings.json | 95 +- Docs/TagInfo/mapcomplete_personal.json | 473 ++++++++-- Docs/TagInfo/mapcomplete_pets.json | 6 +- Docs/TagInfo/mapcomplete_shops.json | 4 +- Docs/TagInfo/mapcomplete_sports.json | 6 +- Docs/TagInfo/mapcomplete_vending_machine.json | 95 +- Docs/TagInfo/mapcomplete_waste.json | 2 +- Docs/Themes/climbing.md | 2 + Docs/Themes/mapcomplete-changes.md | 4 + Docs/wikiIndex.txt | 22 +- .../charging_station/charging_station.json | 37 +- .../layers/ticket_machine/ticket_machine.json | 18 +- .../ticket_validator/ticket_validator.json | 39 +- assets/layers/toilet/toilet.json | 48 +- .../toilet_at_amenity/toilet_at_amenity.json | 27 +- .../layers/transit_routes/transit_routes.json | 6 +- assets/layers/tree_node/tree_node.json | 41 +- assets/layers/usersettings/usersettings.json | 54 +- .../vending_machine/vending_machine.json | 153 ++-- assets/layers/veterinary/veterinary.json | 15 +- assets/layers/viewpoint/viewpoint.json | 8 +- .../visitor_information_centre.json | 6 +- assets/layers/waste_basket/waste_basket.json | 3 +- .../layers/waste_disposal/waste_disposal.json | 6 +- assets/layers/windturbine/windturbine.json | 27 +- .../mapcomplete-changes.json | 4 +- langs/layers/ca.json | 384 ++++---- langs/layers/es.json | 293 ++++--- langs/themes/ca.json | 64 +- package-lock.json | 18 +- .../ThemeConfig/Conversion/Validation.ts | 742 ++++++++-------- src/UI/Base/Table.ts | 10 +- src/UI/BigComponents/ContactLink.svelte | 7 +- src/UI/BigComponents/CopyrightPanel.ts | 6 +- src/UI/Image/ImageUploadFlow.ts | 5 +- src/UI/Popup/LinkableImage.svelte | 97 +- src/UI/Popup/NearbyImages.svelte | 66 +- src/UI/Popup/NearbyImagesCollapsed.svelte | 62 +- src/UI/Popup/SendEmail.svelte | 24 +- .../TagRendering/TagRenderingQuestion.svelte | 165 ++-- src/UI/StylesheetTestGui.svelte | 14 +- src/UI/Wikipedia/WikipediaArticle.svelte | 58 +- src/UI/Wikipedia/WikipediaPanel.svelte | 46 +- src/assets/contributors.json | 12 +- src/assets/language_native.json | 6 +- src/assets/language_translations.json | 826 +++++++++++++----- src/assets/translators.json | 10 +- src/land.ts | 10 +- 120 files changed, 3330 insertions(+), 1759 deletions(-) diff --git a/Docs/BuiltinQuestions.md b/Docs/BuiltinQuestions.md index e89204269..10b886651 100644 --- a/Docs/BuiltinQuestions.md +++ b/Docs/BuiltinQuestions.md @@ -108,8 +108,8 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [24/7](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D24/7) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | ["by appointment"](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D"by appointment") [](https://taginfo.openstreetmap.org/keys/service:electricity#values) [service:electricity](https://wiki.openstreetmap.org/wiki/Key:service:electricity) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dlimited) [ask](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dask) [no](https://wiki.openstreetmap.org/wiki/Tag:service:electricity%3Dno) -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) [10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 CHF) [20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 CHF) [50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 CHF) [100 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 CHF) [200 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 CHF) [1000 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D1000 CHF) [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [string](../SpecialInputElements.md#string) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) @@ -293,13 +293,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer @@ -543,9 +543,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` @@ -566,9 +573,15 @@ The question is *what notes can you use to pay here?* - *100 euro notes are accepted* corresponds with `payment:notes:denominations=100 EUR` - *200 euro notes are accepted* corresponds with `payment:notes:denominations=200 EUR` - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` + - *10 francs notes are accepted* corresponds with `payment:notes:denominations=10 CHF` + - *20 francs notes are accepted* corresponds with `payment:notes:denominations=20 CHF` + - *50 francs notes are accepted* corresponds with `payment:notes:denominations=50 CHF` + - *100 francs notes are accepted* corresponds with `payment:notes:denominations=100 CHF` + - *200 francs notes are accepted* corresponds with `payment:notes:denominations=200 CHF` + - *1000 francs notes are accepted* corresponds with `payment:notes:denominations=1000 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/Layers/all_vending_machine.md b/Docs/Layers/all_vending_machine.md index 4e73b1556..3d7f30a6e 100644 --- a/Docs/Layers/all_vending_machine.md +++ b/Docs/Layers/all_vending_machine.md @@ -49,8 +49,8 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/vending#values) [vending](https://wiki.openstreetmap.org/wiki/Key:vending) | [string](../SpecialInputElements.md#string) | [drinks](https://wiki.openstreetmap.org/wiki/Tag:vending%3Ddrinks) [sweets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dsweets) [food](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dfood) [cigarettes](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcigarettes) [condoms](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcondoms) [coffee](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcoffee) [water](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dwater) [newspapers](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dnewspapers) [bicycle_tube](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dbicycle_tube) [milk](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dmilk) [bread](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dbread) [eggs](https://wiki.openstreetmap.org/wiki/Tag:vending%3Deggs) [cheese](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcheese) [honey](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dhoney) [potatoes](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dpotatoes) [flowers](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dflowers) [parking_tickets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dparking_tickets) [elongated_coin](https://wiki.openstreetmap.org/wiki/Tag:vending%3Delongated_coin) [public_transport_tickets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dpublic_transport_tickets) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [24/7](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D24/7) -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) [10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 CHF) [20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 CHF) [50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 CHF) [100 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 CHF) [200 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 CHF) [1000 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D1000 CHF) [](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/indoor#values) [indoor](https://wiki.openstreetmap.org/wiki/Key:indoor) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dno) [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) @@ -193,9 +193,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` @@ -216,9 +223,15 @@ The question is *what notes can you use to pay here?* - *100 euro notes are accepted* corresponds with `payment:notes:denominations=100 EUR` - *200 euro notes are accepted* corresponds with `payment:notes:denominations=200 EUR` - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` + - *10 francs notes are accepted* corresponds with `payment:notes:denominations=10 CHF` + - *20 francs notes are accepted* corresponds with `payment:notes:denominations=20 CHF` + - *50 francs notes are accepted* corresponds with `payment:notes:denominations=50 CHF` + - *100 francs notes are accepted* corresponds with `payment:notes:denominations=100 CHF` + - *200 francs notes are accepted* corresponds with `payment:notes:denominations=200 CHF` + - *1000 francs notes are accepted* corresponds with `payment:notes:denominations=1000 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/Layers/artwork.md b/Docs/Layers/artwork.md index 8a20c9275..1763fb0fd 100644 --- a/Docs/Layers/artwork.md +++ b/Docs/Layers/artwork.md @@ -171,7 +171,7 @@ The question is *Is there a website with more information about this artwork?* This rendering asks information about the property [website](https://wiki.openstreetmap.org/wiki/Key:website) -This is rendered with `More information on this website` +This is rendered with `{link(More information on this website,&LBRACEwebsite&RBRACE,,)}` @@ -502,7 +502,7 @@ This tagrendering has no question and is thus read-only id | question | osmTags ---- | ---------- | --------- has_image.0 | With and without images (default) | -has_image.1 | Has at least one image | image~.+\|image:0~.+|image:1~.+|image:2~.+|image:3~.+|mapillary~.+ +has_image.1 | Has at least one image | image~.+\|image:0~.+\|image:1~.+\|image:2~.+\|image:3~.+\|mapillary~.+ has_image.2 | Probably does not have an image | diff --git a/Docs/Layers/bench.md b/Docs/Layers/bench.md index f7622c697..5d588d8e8 100644 --- a/Docs/Layers/bench.md +++ b/Docs/Layers/bench.md @@ -367,7 +367,7 @@ The question is *Is there a website with more information about this artwork?* This rendering asks information about the property [website](https://wiki.openstreetmap.org/wiki/Key:website) -This is rendered with `More information on this website` +This is rendered with `{link(More information on this website,&LBRACEwebsite&RBRACE,,)}` @@ -486,7 +486,7 @@ has_backrest.2 | Has no backrest | backrest=no id | question | osmTags ---- | ---------- | --------- has_image.0 | With and without images (default) | -has_image.1 | Has at least one image | image~.+\|image:0~.+|image:1~.+|image:2~.+|image:3~.+|mapillary~.+ +has_image.1 | Has at least one image | image~.+\|image:0~.+\|image:1~.+\|image:2~.+\|image:3~.+\|mapillary~.+ has_image.2 | Probably does not have an image | diff --git a/Docs/Layers/bicycle_library.md b/Docs/Layers/bicycle_library.md index 8aafc7e2b..d926e49a5 100644 --- a/Docs/Layers/bicycle_library.md +++ b/Docs/Layers/bicycle_library.md @@ -157,13 +157,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/bicycle_rental.md b/Docs/Layers/bicycle_rental.md index e19d50e70..46e5d474c 100644 --- a/Docs/Layers/bicycle_rental.md +++ b/Docs/Layers/bicycle_rental.md @@ -147,13 +147,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/bicycle_rental_non_docking.md b/Docs/Layers/bicycle_rental_non_docking.md index e8e1d2cbe..f89d2d350 100644 --- a/Docs/Layers/bicycle_rental_non_docking.md +++ b/Docs/Layers/bicycle_rental_non_docking.md @@ -145,13 +145,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/bike_cafe.md b/Docs/Layers/bike_cafe.md index 3e9fb9d8e..791caa5e3 100644 --- a/Docs/Layers/bike_cafe.md +++ b/Docs/Layers/bike_cafe.md @@ -204,13 +204,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/bike_shop.md b/Docs/Layers/bike_shop.md index 924e5eb3e..3608947d1 100644 --- a/Docs/Layers/bike_shop.md +++ b/Docs/Layers/bike_shop.md @@ -189,13 +189,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/bike_themed_object.md b/Docs/Layers/bike_themed_object.md index 53bdff75d..c6588d226 100644 --- a/Docs/Layers/bike_themed_object.md +++ b/Docs/Layers/bike_themed_object.md @@ -131,13 +131,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/birdhide.md b/Docs/Layers/birdhide.md index 2c2ce0c7b..b1bb1be28 100644 --- a/Docs/Layers/birdhide.md +++ b/Docs/Layers/birdhide.md @@ -216,7 +216,7 @@ This tagrendering has no question and is thus read-only id | question | osmTags ---- | ---------- | --------- -wheelchair.0 | Wheelchair accessible | wheelchair=yes\|wheelchair=designated|wheelchair=permissive +wheelchair.0 | Wheelchair accessible | wheelchair=yes\|wheelchair=designated\|wheelchair=permissive diff --git a/Docs/Layers/cafe_pub.md b/Docs/Layers/cafe_pub.md index 1e1cbd5aa..3e2ed5f32 100644 --- a/Docs/Layers/cafe_pub.md +++ b/Docs/Layers/cafe_pub.md @@ -200,13 +200,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/car_rental.md b/Docs/Layers/car_rental.md index 800b3cbff..178aaeb93 100644 --- a/Docs/Layers/car_rental.md +++ b/Docs/Layers/car_rental.md @@ -124,13 +124,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/climbing.md b/Docs/Layers/climbing.md index aa993e94b..eac07bd89 100644 --- a/Docs/Layers/climbing.md +++ b/Docs/Layers/climbing.md @@ -79,7 +79,7 @@ The question is *Is there a (unofficial) website with more informations (e.g. t This rendering asks information about the property [url](https://wiki.openstreetmap.org/wiki/Key:url) -This is rendered with `{url}` +This is rendered with `{url}` diff --git a/Docs/Layers/climbing_area.md b/Docs/Layers/climbing_area.md index 9aea13403..45c92d534 100644 --- a/Docs/Layers/climbing_area.md +++ b/Docs/Layers/climbing_area.md @@ -196,7 +196,7 @@ The question is *Is there a (unofficial) website with more informations (e.g. t This rendering asks information about the property [url](https://wiki.openstreetmap.org/wiki/Key:url) -This is rendered with `{url}` +This is rendered with `{url}` diff --git a/Docs/Layers/climbing_club.md b/Docs/Layers/climbing_club.md index eb94c1de5..00a84787b 100644 --- a/Docs/Layers/climbing_club.md +++ b/Docs/Layers/climbing_club.md @@ -119,13 +119,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/climbing_gym.md b/Docs/Layers/climbing_gym.md index 1e1886b81..81d97216d 100644 --- a/Docs/Layers/climbing_gym.md +++ b/Docs/Layers/climbing_gym.md @@ -168,13 +168,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/defibrillator.md b/Docs/Layers/defibrillator.md index d9b07721c..0d6cdd745 100644 --- a/Docs/Layers/defibrillator.md +++ b/Docs/Layers/defibrillator.md @@ -425,7 +425,7 @@ This tagrendering has no question and is thus read-only id | question | osmTags ---- | ---------- | --------- has_image.0 | With and without images (default) | -has_image.1 | Has at least one image | image~.+\|image:0~.+|image:1~.+|image:2~.+|image:3~.+|mapillary~.+ +has_image.1 | Has at least one image | image~.+\|image:0~.+\|image:1~.+\|image:2~.+\|image:3~.+\|mapillary~.+ has_image.2 | Probably does not have an image | diff --git a/Docs/Layers/dentist.md b/Docs/Layers/dentist.md index 2d8be241f..d52b4b840 100644 --- a/Docs/Layers/dentist.md +++ b/Docs/Layers/dentist.md @@ -131,13 +131,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/doctors.md b/Docs/Layers/doctors.md index 359a8be78..81d4a1973 100644 --- a/Docs/Layers/doctors.md +++ b/Docs/Layers/doctors.md @@ -154,13 +154,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/dogfoodb.md b/Docs/Layers/dogfoodb.md index a6797e465..159eb7f82 100644 --- a/Docs/Layers/dogfoodb.md +++ b/Docs/Layers/dogfoodb.md @@ -206,13 +206,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer @@ -720,7 +720,7 @@ open_now.0 | Open now | _isOpen=yes id | question | osmTags ---- | ---------- | --------- -reservation.0 | Reservation not required | reservation=no\|reservation=optional| +reservation.0 | Reservation not required | reservation=no\|reservation=optional\| @@ -736,7 +736,7 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only|diet:vegan=yes|diet:vegan=only +vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only diff --git a/Docs/Layers/dogshop.md b/Docs/Layers/dogshop.md index 08c1e0bf4..4018d415b 100644 --- a/Docs/Layers/dogshop.md +++ b/Docs/Layers/dogshop.md @@ -328,13 +328,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/drinking_water.md b/Docs/Layers/drinking_water.md index 489d4aa23..9942749b8 100644 --- a/Docs/Layers/drinking_water.md +++ b/Docs/Layers/drinking_water.md @@ -27,6 +27,7 @@ A layer showing drinking water fountains + - [climbing](https://mapcomplete.org/climbing) - [cyclofix](https://mapcomplete.org/cyclofix) - [drinking_water](https://mapcomplete.org/drinking_water) - [nature](https://mapcomplete.org/nature) diff --git a/Docs/Layers/elevator.md b/Docs/Layers/elevator.md index f0085962e..1da424300 100644 --- a/Docs/Layers/elevator.md +++ b/Docs/Layers/elevator.md @@ -55,6 +55,8 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/elevator:width#values) [elevator:width](https://wiki.openstreetmap.org/wiki/Key:elevator:width) | [pfloat](../SpecialInputElements.md#pfloat) | [](https://taginfo.openstreetmap.org/keys/elevator:depth#values) [elevator:depth](https://wiki.openstreetmap.org/wiki/Key:elevator:depth) | [pfloat](../SpecialInputElements.md#pfloat) | [](https://taginfo.openstreetmap.org/keys/hearing_loop#values) [hearing_loop](https://wiki.openstreetmap.org/wiki/Key:hearing_loop) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:hearing_loop%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:hearing_loop%3Dno) +[](https://taginfo.openstreetmap.org/keys/tactile_writing:braille#values) [tactile_writing:braille](https://wiki.openstreetmap.org/wiki/Key:tactile_writing:braille) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:tactile_writing:braille%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:tactile_writing:braille%3Dno) +[](https://taginfo.openstreetmap.org/keys/speech_output#values) [speech_output](https://wiki.openstreetmap.org/wiki/Key:speech_output) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:speech_output%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:speech_output%3Dno) @@ -194,6 +196,22 @@ The question is *Does this place have an audio induction loop for people with r +### tactile_writing_available + + + +The question is *Has this elevator tactile writing?* + + + + + + - *This elevator has tactile writing in Braille* corresponds with `tactile_writing:braille=yes` + - *This elevator does not have tactile writing* corresponds with `tactile_writing:braille=no` + + + + ### tactile_writing_language @@ -202,6 +220,24 @@ This tagrendering has no question and is thus read-only +This tagrendering is only visible in the popup if the following condition is met: `tactile_writing:braille=yes` + + + +### speech_output_available + + + +The question is *Has this elevator speech output?* + + + + + + - *This elevator has speech output* corresponds with `speech_output=yes` + - *This elevator does not have speech output* corresponds with `speech_output=no` + + ### speech_output @@ -212,6 +248,8 @@ This tagrendering has no question and is thus read-only +This tagrendering is only visible in the popup if the following condition is met: `speech_output=yes` + ### leftover-questions diff --git a/Docs/Layers/elongated_coin.md b/Docs/Layers/elongated_coin.md index 75d7656fc..8c0a80cbe 100644 --- a/Docs/Layers/elongated_coin.md +++ b/Docs/Layers/elongated_coin.md @@ -50,10 +50,11 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [24/7](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D24/7) [](https://taginfo.openstreetmap.org/keys/coin:design_count#values) [coin:design_count](https://wiki.openstreetmap.org/wiki/Key:coin:design_count) | [pnat](../SpecialInputElements.md#pnat) | [1](https://wiki.openstreetmap.org/wiki/Tag:coin:design_count%3D1) [2](https://wiki.openstreetmap.org/wiki/Tag:coin:design_count%3D2) [3](https://wiki.openstreetmap.org/wiki/Tag:coin:design_count%3D3) [4](https://wiki.openstreetmap.org/wiki/Tag:coin:design_count%3D4) -[](https://taginfo.openstreetmap.org/keys/coin:type#values) [coin:type](https://wiki.openstreetmap.org/wiki/Key:coin:type) | [string](../SpecialInputElements.md#string) | [2cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D2cent) [5cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D5cent) [10cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D10cent) [25cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D25cent) [50cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D50cent) +[](https://taginfo.openstreetmap.org/keys/fee#values) [fee](https://wiki.openstreetmap.org/wiki/Key:fee) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:fee%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:fee%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:fee%3Dno) +[](https://taginfo.openstreetmap.org/keys/coin:type#values) [coin:type](https://wiki.openstreetmap.org/wiki/Key:coin:type) | [string](../SpecialInputElements.md#string) | [2cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D2cent) [5cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D5cent) [10cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D10cent) [25cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D25cent) [50cent](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D50cent) [10centimes](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D10centimes) [20centimes](https://wiki.openstreetmap.org/wiki/Tag:coin:type%3D20centimes) [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | -[](https://taginfo.openstreetmap.org/keys/charge#values) [charge](https://wiki.openstreetmap.org/wiki/Key:charge) | [string](../SpecialInputElements.md#string) | [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:charge%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:charge%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) +[](https://taginfo.openstreetmap.org/keys/charge#values) [charge](https://wiki.openstreetmap.org/wiki/Key:charge) | [string](../SpecialInputElements.md#string) | [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:charge%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:charge%3D2 EUR) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:charge%3D2 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:charge%3D1 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) [](https://taginfo.openstreetmap.org/keys/indoor#values) [indoor](https://wiki.openstreetmap.org/wiki/Key:indoor) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dno) [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/check_date#values) [check_date](https://wiki.openstreetmap.org/wiki/Key:check_date) | [date](../SpecialInputElements.md#date) | [](https://wiki.openstreetmap.org/wiki/Tag:check_date%3D) @@ -133,6 +134,23 @@ This is rendered with `This penny press has {coin:design_count} designs availab +### fee + + + +The question is *Does it cost money to press a penny?* + + + + + + - *It costs money to press a penny.* corresponds with `` + - *It costs money to press a penny.* corresponds with `fee=yes` + - *It is free to press a penny.* corresponds with `fee=no` + + + + ### payment-options-split @@ -182,6 +200,8 @@ This is rendered with `This penny press uses a {coin:type} coin for pressing.` - *This penny press uses a 10 cent coin for pressing.* corresponds with `coin:type=10cent` - *This penny press uses a 25 cent coin for pressing.* corresponds with `coin:type=25cent` - *This penny press uses a 50 cent coin for pressing.* corresponds with `coin:type=50cent` + - *This penny press uses a 10 centimes coin for pressing.* corresponds with `coin:type=10centimes` + - *This penny press uses a 20 centimes coin for pressing.* corresponds with `coin:type=20centimes` @@ -224,6 +244,8 @@ This is rendered with `It costs {charge} to press a penny.` - *It costs 1 euro to press a penny.* corresponds with `charge=1 EUR` - *It costs 2 euros to press a penny.* corresponds with `charge=2 EUR` + - *It costs 2 Swiss francs to press a penny.* corresponds with `charge=2 CHF` + - *It costs 1 Swiss franc to press a penny.* corresponds with `charge=1 CHF` @@ -246,9 +268,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/Layers/fitness_centre.md b/Docs/Layers/fitness_centre.md index 55df45502..be4b04f6a 100644 --- a/Docs/Layers/fitness_centre.md +++ b/Docs/Layers/fitness_centre.md @@ -138,13 +138,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/food.md b/Docs/Layers/food.md index 8f17a34c1..90c4b597d 100644 --- a/Docs/Layers/food.md +++ b/Docs/Layers/food.md @@ -210,13 +210,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer @@ -724,7 +724,7 @@ open_now.0 | Open now | _isOpen=yes id | question | osmTags ---- | ---------- | --------- -reservation.0 | Reservation not required | reservation=no\|reservation=optional| +reservation.0 | Reservation not required | reservation=no\|reservation=optional\| @@ -740,7 +740,7 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only|diet:vegan=yes|diet:vegan=only +vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only diff --git a/Docs/Layers/friture.md b/Docs/Layers/friture.md index 4d0c08348..8d326c30f 100644 --- a/Docs/Layers/friture.md +++ b/Docs/Layers/friture.md @@ -206,13 +206,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer @@ -720,7 +720,7 @@ open_now.0 | Open now | _isOpen=yes id | question | osmTags ---- | ---------- | --------- -reservation.0 | Reservation not required | reservation=no\|reservation=optional| +reservation.0 | Reservation not required | reservation=no\|reservation=optional\| @@ -736,7 +736,7 @@ food-category.2 | Only restaurants | amenity=restaurant id | question | osmTags ---- | ---------- | --------- -vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only|diet:vegan=yes|diet:vegan=only +vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only diff --git a/Docs/Layers/ghost_bike.md b/Docs/Layers/ghost_bike.md index b452a9957..42c94fc94 100644 --- a/Docs/Layers/ghost_bike.md +++ b/Docs/Layers/ghost_bike.md @@ -125,7 +125,7 @@ The question is *On what webpage can one find more info about the ghost bike or This rendering asks information about the property [source](https://wiki.openstreetmap.org/wiki/Key:source) -This is rendered with `More info available` +This is rendered with `{link(More info available,&LBRACEsource&RBRACE,,)}` diff --git a/Docs/Layers/governments.md b/Docs/Layers/governments.md index 08ff61b04..972356e0a 100644 --- a/Docs/Layers/governments.md +++ b/Docs/Layers/governments.md @@ -116,13 +116,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/hackerspace.md b/Docs/Layers/hackerspace.md index 11eb941dd..3daf2b5ef 100644 --- a/Docs/Layers/hackerspace.md +++ b/Docs/Layers/hackerspace.md @@ -181,13 +181,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/hospital.md b/Docs/Layers/hospital.md index 25406d3bc..72080c912 100644 --- a/Docs/Layers/hospital.md +++ b/Docs/Layers/hospital.md @@ -137,13 +137,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/hotel.md b/Docs/Layers/hotel.md index d4bbce832..49abe4609 100644 --- a/Docs/Layers/hotel.md +++ b/Docs/Layers/hotel.md @@ -147,13 +147,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/kindergarten_childcare.md b/Docs/Layers/kindergarten_childcare.md index c59c08e2f..aa512c1a6 100644 --- a/Docs/Layers/kindergarten_childcare.md +++ b/Docs/Layers/kindergarten_childcare.md @@ -137,13 +137,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/medical-shops.md b/Docs/Layers/medical-shops.md index 7cbdc595f..36e0bce0d 100644 --- a/Docs/Layers/medical-shops.md +++ b/Docs/Layers/medical-shops.md @@ -328,13 +328,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/nature_reserve.md b/Docs/Layers/nature_reserve.md index 74c09dbb4..6a25bb9ae 100644 --- a/Docs/Layers/nature_reserve.md +++ b/Docs/Layers/nature_reserve.md @@ -221,7 +221,7 @@ The question is *What email adress can one send to with questions and problems This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` @@ -235,7 +235,7 @@ The question is *What phone number can one call to with questions and problems This rendering asks information about the property [phone](https://wiki.openstreetmap.org/wiki/Key:phone) -This is rendered with `{phone}` +This is rendered with `{phone}` diff --git a/Docs/Layers/parking_ticket_machine.md b/Docs/Layers/parking_ticket_machine.md index 47e28a1f5..84760f725 100644 --- a/Docs/Layers/parking_ticket_machine.md +++ b/Docs/Layers/parking_ticket_machine.md @@ -48,8 +48,8 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) [10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 CHF) [20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 CHF) [50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 CHF) [100 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 CHF) [200 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 CHF) [1000 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D1000 CHF) [](https://taginfo.openstreetmap.org/keys/ref#values) [ref](https://wiki.openstreetmap.org/wiki/Key:ref) | [string](../SpecialInputElements.md#string) | @@ -134,9 +134,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` @@ -157,9 +164,15 @@ The question is *what notes can you use to pay here?* - *100 euro notes are accepted* corresponds with `payment:notes:denominations=100 EUR` - *200 euro notes are accepted* corresponds with `payment:notes:denominations=200 EUR` - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` + - *10 francs notes are accepted* corresponds with `payment:notes:denominations=10 CHF` + - *20 francs notes are accepted* corresponds with `payment:notes:denominations=20 CHF` + - *50 francs notes are accepted* corresponds with `payment:notes:denominations=50 CHF` + - *100 francs notes are accepted* corresponds with `payment:notes:denominations=100 CHF` + - *200 francs notes are accepted* corresponds with `payment:notes:denominations=200 CHF` + - *1000 francs notes are accepted* corresponds with `payment:notes:denominations=1000 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/Layers/pharmacy.md b/Docs/Layers/pharmacy.md index 7a4e11ce4..e25906e56 100644 --- a/Docs/Layers/pharmacy.md +++ b/Docs/Layers/pharmacy.md @@ -148,13 +148,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/physiotherapist.md b/Docs/Layers/physiotherapist.md index 7e11e0c54..d8b177c5d 100644 --- a/Docs/Layers/physiotherapist.md +++ b/Docs/Layers/physiotherapist.md @@ -152,13 +152,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/public_bookcase.md b/Docs/Layers/public_bookcase.md index 7b88f3de0..2583464a3 100644 --- a/Docs/Layers/public_bookcase.md +++ b/Docs/Layers/public_bookcase.md @@ -258,7 +258,7 @@ The question is *Is there a website with more information about this public boo This rendering asks information about the property [website](https://wiki.openstreetmap.org/wiki/Key:website) -This is rendered with `More info on the website` +This is rendered with `{link(More info on the website,&LBRACEwebsite&RBRACE,,)}` @@ -362,7 +362,7 @@ inside.2 | Located outdoors | indoor=no\| id | question | osmTags ---- | ---------- | --------- has_image.0 | With and without images (default) | -has_image.1 | Has at least one image | image~.+\|image:0~.+|image:1~.+|image:2~.+|image:3~.+|mapillary~.+ +has_image.1 | Has at least one image | image~.+\|image:0~.+\|image:1~.+\|image:2~.+\|image:3~.+\|mapillary~.+ has_image.2 | Probably does not have an image | diff --git a/Docs/Layers/recycling.md b/Docs/Layers/recycling.md index 278f03510..4157a5849 100644 --- a/Docs/Layers/recycling.md +++ b/Docs/Layers/recycling.md @@ -262,13 +262,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/school.md b/Docs/Layers/school.md index 3b2549274..1841a44f6 100644 --- a/Docs/Layers/school.md +++ b/Docs/Layers/school.md @@ -229,13 +229,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/shops.md b/Docs/Layers/shops.md index 12e85245e..551096faf 100644 --- a/Docs/Layers/shops.md +++ b/Docs/Layers/shops.md @@ -333,13 +333,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/shops_with_climbing_shoe_repair.md b/Docs/Layers/shops_with_climbing_shoe_repair.md index 61b967d2a..879540277 100644 --- a/Docs/Layers/shops_with_climbing_shoe_repair.md +++ b/Docs/Layers/shops_with_climbing_shoe_repair.md @@ -45,7 +45,7 @@ this quick overview is incomplete attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/service:repair:climbing_shoes#values) [service:repair:climbing_shoes](https://wiki.openstreetmap.org/wiki/Key:service:repair:climbing_shoes) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:service:repair:climbing_shoes%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:service:repair:climbing_shoes%3Dno) +[](https://taginfo.openstreetmap.org/keys/service:climbing_shoes:repair#values) [service:climbing_shoes:repair](https://wiki.openstreetmap.org/wiki/Key:service:climbing_shoes:repair) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:service:climbing_shoes:repair%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:service:climbing_shoes:repair%3Dno) [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/shop#values) [shop](https://wiki.openstreetmap.org/wiki/Key:shop) | [string](../SpecialInputElements.md#string) | [agrarian](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dagrarian) [alcohol](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dalcohol) [anime](https://wiki.openstreetmap.org/wiki/Tag:shop%3Danime) [antiques](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dantiques) [appliance](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dappliance) [art](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dart) [baby_goods](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbaby_goods) [bag](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbag) [bakery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbakery) [bathroom_furnishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbathroom_furnishing) [beauty](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeauty) [bed](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbed) [beverages](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeverages) [bicycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbicycle) [boat](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dboat) [bookmaker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbookmaker) [books](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbooks) [brewing_supplies](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbrewing_supplies) [butcher](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbutcher) [camera](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcamera) [candles](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcandles) [cannabis](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcannabis) [car](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar) [car_parts](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_parts) [car_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_repair) [caravan](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcaravan) [carpet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcarpet) [catalogue](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcatalogue) [charity](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcharity) [cheese](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcheese) [chemist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dchemist) [chocolate](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dchocolate) [clothes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dclothes) [coffee](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcoffee) [collector](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcollector) [computer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcomputer) [confectionery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dconfectionery) [convenience](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dconvenience) [copyshop](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcopyshop) [cosmetics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcosmetics) [country_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcountry_store) [craft](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcraft) [curtain](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcurtain) [dairy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddairy) [deli](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddeli) [department_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddepartment_store) [doityourself](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoityourself) [doors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoors) [dry_cleaning](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddry_cleaning) [e-cigarette](https://wiki.openstreetmap.org/wiki/Tag:shop%3De-cigarette) [electrical](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectrical) [electronics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectronics) [erotic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Derotic) [fabric](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfabric) [farm](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfarm) [fashion_accessories](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfashion_accessories) [fireplace](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfireplace) [fishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfishing) [flooring](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflooring) [florist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflorist) [frame](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dframe) [frozen_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfrozen_food) [fuel](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuel) [funeral_directors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuneral_directors) [furniture](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfurniture) [games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgames) [garden_centre](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgarden_centre) [gas](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgas) [general](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgeneral) [gift](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgift) [greengrocer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgreengrocer) [hairdresser](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser) [hairdresser_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser_supply) [hardware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhardware) [health_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhealth_food) [hearing_aids](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhearing_aids) [herbalist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dherbalist) [hifi](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhifi) [hobby](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhobby) [household_linen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhousehold_linen) [houseware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhouseware) [hunting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhunting) [interior_decoration](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dinterior_decoration) [jewelry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Djewelry) [kiosk](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkiosk) [kitchen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkitchen) [laundry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlaundry) [leather](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dleather) [lighting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlighting) [locksmith](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlocksmith) [lottery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlottery) [mall](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmall) [massage](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmassage) [medical_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmedical_supply) [military_surplus](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmilitary_surplus) [mobile_phone](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmobile_phone) [model](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmodel) [money_lender](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmoney_lender) [motorcycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle) [motorcycle_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle_repair) [music](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusic) [musical_instrument](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusical_instrument) [newsagent](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnewsagent) [nutrition_supplements](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnutrition_supplements) [optician](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doptician) [outdoor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutdoor) [outpost](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutpost) [paint](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpaint) [party](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dparty) [pastry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpastry) [pawnbroker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpawnbroker) [perfumery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dperfumery) [pet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet) [pet_grooming](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet_grooming) [photo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dphoto) [pottery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpottery) [printer_ink](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dprinter_ink) [psychic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpsychic) [pyrotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpyrotechnics) [radiotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dradiotechnics) [religion](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dreligion) [rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Drental) [repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Drepair) [scuba_diving](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dscuba_diving) [seafood](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dseafood) [second_hand](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsecond_hand) [sewing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsewing) [shoe_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoe_repair) [shoes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoes) [spices](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dspices) [sports](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsports) [stationery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstationery) [storage_rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstorage_rental) [supermarket](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsupermarket) [swimming_pool](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dswimming_pool) [tailor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtailor) [tattoo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtattoo) [tea](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtea) [telecommunication](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtelecommunication) [ticket](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dticket) [tiles](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtiles) [tobacco](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtobacco) [tool_hire](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtool_hire) [toys](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtoys) [trade](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrade) [travel_agency](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtravel_agency) [trophy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrophy) [tyres](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtyres) [vacuum_cleaner](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvacuum_cleaner) [variety_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvariety_store) [video](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo) [video_games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo_games) [watches](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwatches) [water](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwater) [water_sports](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwater_sports) [weapons](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dweapons) [wholesale](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwholesale) [wigs](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwigs) [window_blind](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwindow_blind) [wine](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwine) @@ -72,8 +72,8 @@ The question is *Does this shoe repair shop repair climbing shoes?* - - *This shop repairs climbing shoes* corresponds with `service:repair:climbing_shoes=yes` - - *This shop does not repair climbing shoes* corresponds with `service:repair:climbing_shoes=no` + - *This shop repairs climbing shoes* corresponds with `service:climbing_shoes:repair=yes` + - *This shop does not repair climbing shoes* corresponds with `service:climbing_shoes:repair=no` @@ -345,13 +345,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/shower.md b/Docs/Layers/shower.md index 9850c9080..b5551bce7 100644 --- a/Docs/Layers/shower.md +++ b/Docs/Layers/shower.md @@ -299,7 +299,7 @@ This tagrendering has no question and is thus read-only id | question | osmTags ---- | ---------- | --------- -free.0 | Free to use | fee=no\|fee=0|charge=0 +free.0 | Free to use | fee=no\|fee=0\|charge=0 diff --git a/Docs/Layers/sport_pitch.md b/Docs/Layers/sport_pitch.md index d747dd6aa..65a8f01ac 100644 --- a/Docs/Layers/sport_pitch.md +++ b/Docs/Layers/sport_pitch.md @@ -219,7 +219,7 @@ The question is *What is the email address of the operator?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` @@ -314,7 +314,7 @@ This tagrendering has no question and is thus read-only id | question | osmTags ---- | ---------- | --------- -accessibility.0 | Publicly accessible | access=yes\|access=public| +accessibility.0 | Publicly accessible | access=yes\|access=public\| diff --git a/Docs/Layers/sport_shops.md b/Docs/Layers/sport_shops.md index aeaf47c2d..17c61c236 100644 --- a/Docs/Layers/sport_shops.md +++ b/Docs/Layers/sport_shops.md @@ -328,13 +328,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/sports_centre.md b/Docs/Layers/sports_centre.md index 93dfc3bee..c77e31cd6 100644 --- a/Docs/Layers/sports_centre.md +++ b/Docs/Layers/sports_centre.md @@ -153,13 +153,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/tertiary_education.md b/Docs/Layers/tertiary_education.md index 211481f6b..c23a00117 100644 --- a/Docs/Layers/tertiary_education.md +++ b/Docs/Layers/tertiary_education.md @@ -174,13 +174,13 @@ The question is *What is the email address of {title()}?* This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` +This is rendered with `{email}` - - *{contact:email}* corresponds with `contact:email~.+` + - *{contact:email}* corresponds with `contact:email~.+` - This option cannot be chosen as answer diff --git a/Docs/Layers/ticket_machine.md b/Docs/Layers/ticket_machine.md index 72f3d8bea..50ab62d9b 100644 --- a/Docs/Layers/ticket_machine.md +++ b/Docs/Layers/ticket_machine.md @@ -49,8 +49,8 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | [Nederlandse Spoorwegen](https://wiki.openstreetmap.org/wiki/Tag:operator%3DNederlandse Spoorwegen) -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) [10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 CHF) [20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 CHF) [50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 CHF) [100 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 CHF) [200 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 CHF) [1000 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D1000 CHF) @@ -178,9 +178,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` @@ -201,9 +208,15 @@ The question is *what notes can you use to pay here?* - *100 euro notes are accepted* corresponds with `payment:notes:denominations=100 EUR` - *200 euro notes are accepted* corresponds with `payment:notes:denominations=200 EUR` - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` + - *10 francs notes are accepted* corresponds with `payment:notes:denominations=10 CHF` + - *20 francs notes are accepted* corresponds with `payment:notes:denominations=20 CHF` + - *50 francs notes are accepted* corresponds with `payment:notes:denominations=50 CHF` + - *100 francs notes are accepted* corresponds with `payment:notes:denominations=100 CHF` + - *200 francs notes are accepted* corresponds with `payment:notes:denominations=200 CHF` + - *1000 francs notes are accepted* corresponds with `payment:notes:denominations=1000 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/Layers/toilet.md b/Docs/Layers/toilet.md index 8617f2b2c..c18ea698c 100644 --- a/Docs/Layers/toilet.md +++ b/Docs/Layers/toilet.md @@ -25,6 +25,7 @@ A layer showing (public) toilets + - [climbing](https://mapcomplete.org/climbing) - [nature](https://mapcomplete.org/nature) - [onwheels](https://mapcomplete.org/onwheels) - [personal](https://mapcomplete.org/personal) @@ -454,7 +455,7 @@ changing_table.0 | Has a changing table | changing_table=yes id | question | osmTags ---- | ---------- | --------- -free.0 | Free to use | fee=no\|fee=0|charge=0 +free.0 | Free to use | fee=no\|fee=0\|charge=0 diff --git a/Docs/Layers/toilet_at_amenity.md b/Docs/Layers/toilet_at_amenity.md index 7ed2edfdc..761d80e99 100644 --- a/Docs/Layers/toilet_at_amenity.md +++ b/Docs/Layers/toilet_at_amenity.md @@ -405,7 +405,7 @@ changing_table.0 | Has a changing table | changing_table=yes id | question | osmTags ---- | ---------- | --------- -free.0 | Free to use | toilets:fee=no\|toilets:fee=0|toilets:charge=0 +free.0 | Free to use | toilets:fee=no\|toilets:fee=0\|toilets:charge=0 diff --git a/Docs/Layers/vending_machine.md b/Docs/Layers/vending_machine.md index 5bcb48214..a5bd5c977 100644 --- a/Docs/Layers/vending_machine.md +++ b/Docs/Layers/vending_machine.md @@ -49,8 +49,8 @@ attribute | type | values which are supported by this layer [](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [](https://taginfo.openstreetmap.org/keys/vending#values) [vending](https://wiki.openstreetmap.org/wiki/Key:vending) | [string](../SpecialInputElements.md#string) | [drinks](https://wiki.openstreetmap.org/wiki/Tag:vending%3Ddrinks) [sweets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dsweets) [food](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dfood) [cigarettes](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcigarettes) [condoms](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcondoms) [coffee](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcoffee) [water](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dwater) [newspapers](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dnewspapers) [bicycle_tube](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dbicycle_tube) [milk](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dmilk) [bread](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dbread) [eggs](https://wiki.openstreetmap.org/wiki/Tag:vending%3Deggs) [cheese](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dcheese) [honey](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dhoney) [potatoes](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dpotatoes) [flowers](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dflowers) [parking_tickets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dparking_tickets) [elongated_coin](https://wiki.openstreetmap.org/wiki/Tag:vending%3Delongated_coin) [public_transport_tickets](https://wiki.openstreetmap.org/wiki/Tag:vending%3Dpublic_transport_tickets) [](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [24/7](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D24/7) -[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) -[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) +[](https://taginfo.openstreetmap.org/keys/payment:coins:denominations#values) [payment:coins:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:coins:denominations) | Multiple choice | [0.01 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.01 EUR) [0.02 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.02 EUR) [0.05 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 EUR) [0.10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 EUR) [0.20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 EUR) [0.50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 EUR) [1 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 EUR) [2 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 EUR) [0.05 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.05 CHF) [0.10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.10 CHF) [0.20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.20 CHF) [0.50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D0.50 CHF) [1 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D1 CHF) [2 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D2 CHF) [5 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:coins:denominations%3D5 CHF) +[](https://taginfo.openstreetmap.org/keys/payment:notes:denominations#values) [payment:notes:denominations](https://wiki.openstreetmap.org/wiki/Key:payment:notes:denominations) | Multiple choice | [5 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D5 EUR) [10 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 EUR) [20 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 EUR) [50 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 EUR) [100 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 EUR) [200 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 EUR) [500 EUR](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D500 EUR) [10 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D10 CHF) [20 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D20 CHF) [50 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D50 CHF) [100 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D100 CHF) [200 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D200 CHF) [1000 CHF](https://wiki.openstreetmap.org/wiki/Tag:payment:notes:denominations%3D1000 CHF) [](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | [](https://taginfo.openstreetmap.org/keys/indoor#values) [indoor](https://wiki.openstreetmap.org/wiki/Key:indoor) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:indoor%3Dno) [](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) @@ -193,9 +193,16 @@ The question is *What coins can you use to pay here?* - *50 cent coins are accepted* corresponds with `payment:coins:denominations=0.50 EUR` - *1 euro coins are accepted* corresponds with `payment:coins:denominations=1 EUR` - *2 euro coins are accepted* corresponds with `payment:coins:denominations=2 EUR` + - *5 centimes coins are accepted* corresponds with `payment:coins:denominations=0.05 CHF` + - *10 centimes coins are accepted* corresponds with `payment:coins:denominations=0.10 CHF` + - *20 centimes coins are accepted* corresponds with `payment:coins:denominations=0.20 CHF` + - *½ franc coins are accepted* corresponds with `payment:coins:denominations=0.50 CHF` + - *1 franc coins are accepted* corresponds with `payment:coins:denominations=1 CHF` + - *2 francs coins are accepted* corresponds with `payment:coins:denominations=2 CHF` + - *5 francs coins are accepted* corresponds with `payment:coins:denominations=5 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` @@ -216,9 +223,15 @@ The question is *what notes can you use to pay here?* - *100 euro notes are accepted* corresponds with `payment:notes:denominations=100 EUR` - *200 euro notes are accepted* corresponds with `payment:notes:denominations=200 EUR` - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` + - *10 francs notes are accepted* corresponds with `payment:notes:denominations=10 CHF` + - *20 francs notes are accepted* corresponds with `payment:notes:denominations=20 CHF` + - *50 francs notes are accepted* corresponds with `payment:notes:denominations=50 CHF` + - *100 francs notes are accepted* corresponds with `payment:notes:denominations=100 CHF` + - *200 francs notes are accepted* corresponds with `payment:notes:denominations=200 CHF` + - *1000 francs notes are accepted* corresponds with `payment:notes:denominations=1000 CHF` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency=EUR` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$` diff --git a/Docs/SpecialInputElements.md b/Docs/SpecialInputElements.md index 905cbab38..43fad305f 100644 --- a/Docs/SpecialInputElements.md +++ b/Docs/SpecialInputElements.md @@ -108,11 +108,11 @@ key | the value of this tag will initialize search (default: name) options | A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`. subarg \| doc --------- | ----- -removePrefixes | remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes -removePostfixes | remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes. -instanceOf | A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans -notInstanceof | A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results +-------- \| ----- +removePrefixes \| remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes +removePostfixes \| remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes. +instanceOf \| A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans +notInstanceof \| A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results @@ -226,9 +226,9 @@ name | doc options | A JSON-object of type `{ prefix: string, postfix: string }`. subarg \| doc --------- | ----- -prefix | Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse. -postfix | Piece of text that will always be added to the end of the generated opening hours +-------- \| ----- +prefix \| Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse. +postfix \| Piece of text that will always be added to the end of the generated opening hours diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index a9785a7b9..6743c425f 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -638,17 +638,12 @@ way_to_conflate | _undefined_ | The key, of which the corresponding value is the name | default | description ------ | --------- | ------------- -mode | expandable | Indicates how this component is initialized. Options are: - -- `open`: always show and load the pictures -- `collapsable`: show the pictures, but a user can collapse them -- `expandable`: shown by default; but a user can collapse them. -mapillary | true | If 'true', includes a link to mapillary on this location. +mode | closed | Either `open` or `closed`. If `open`, then the image carousel will always be shown #### Example usage of nearby_images - `{nearby_images(expandable,true)}` + `{nearby_images(closed)}` @@ -982,18 +977,19 @@ button_text | _undefined_ | The text shown on the button in the UI ### link - Construct a link. By using the 'special' visualisation notation, translation should be easier + Construct a link. By using the 'special' visualisation notation, translations should be easier name | default | description ------ | --------- | ------------- text | _undefined_ | Text to be shown href | _undefined_ | The URL to link to class | _undefined_ | CSS-classes to add to the element +download | _undefined_ | If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename #### Example usage of link - `{link(,,)}` + `{link(,,,)}` diff --git a/Docs/TagInfo/mapcomplete_atm.json b/Docs/TagInfo/mapcomplete_atm.json index f879246bc..b953fc22c 100644 --- a/Docs/TagInfo/mapcomplete_atm.json +++ b/Docs/TagInfo/mapcomplete_atm.json @@ -79,37 +79,37 @@ }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=5 EUR with a fixed text, namely '5 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=5 EUR with a fixed text, namely '5 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "5 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=10 EUR with a fixed text, namely '10 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=10 EUR with a fixed text, namely '10 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "10 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=20 EUR with a fixed text, namely '20 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=20 EUR with a fixed text, namely '20 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "20 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=50 EUR with a fixed text, namely '50 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=50 EUR with a fixed text, namely '50 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "50 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=100 EUR with a fixed text, namely '100 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=100 EUR with a fixed text, namely '100 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "100 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=200 EUR with a fixed text, namely '200 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=200 EUR with a fixed text, namely '200 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "200 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=500 EUR with a fixed text, namely '500 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=500 EUR with a fixed text, namely '500 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'ATM Machines') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "500 EUR" }, { diff --git a/Docs/TagInfo/mapcomplete_bicycle_rental.json b/Docs/TagInfo/mapcomplete_bicycle_rental.json index 6e46ac7d0..07703a993 100644 --- a/Docs/TagInfo/mapcomplete_bicycle_rental.json +++ b/Docs/TagInfo/mapcomplete_bicycle_rental.json @@ -102,7 +102,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Bicycle rental')" + "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Bicycle rental')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_bicyclelib.json b/Docs/TagInfo/mapcomplete_bicyclelib.json index d8affca0c..5cffef932 100644 --- a/Docs/TagInfo/mapcomplete_bicyclelib.json +++ b/Docs/TagInfo/mapcomplete_bicyclelib.json @@ -61,7 +61,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Bicycle libraries')" + "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Bicycle libraries')" }, { "key": "opening_hours", diff --git a/Docs/TagInfo/mapcomplete_blind_osm.json b/Docs/TagInfo/mapcomplete_blind_osm.json index 212a2a59f..6e351511e 100644 --- a/Docs/TagInfo/mapcomplete_blind_osm.json +++ b/Docs/TagInfo/mapcomplete_blind_osm.json @@ -1066,6 +1066,26 @@ "description": "Layer 'Elevator' shows hearing_loop=no with a fixed text, namely 'This place does not have an audio induction loop' and allows to pick this as a default answer (in the mapcomplete.org theme 'OSM for the blind')", "value": "no" }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=yes with a fixed text, namely 'This elevator has tactile writing in Braille' and allows to pick this as a default answer (in the mapcomplete.org theme 'OSM for the blind')", + "value": "yes" + }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=no with a fixed text, namely 'This elevator does not have tactile writing' and allows to pick this as a default answer (in the mapcomplete.org theme 'OSM for the blind')", + "value": "no" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=yes with a fixed text, namely 'This elevator has speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'OSM for the blind')", + "value": "yes" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=no with a fixed text, namely 'This elevator does not have speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'OSM for the blind')", + "value": "no" + }, { "key": "highway", "description": "The MapComplete theme OSM for the blind has a layer Stairs showing features with this tag", diff --git a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json index 03de41202..d56bae9ef 100644 --- a/Docs/TagInfo/mapcomplete_cafes_and_pubs.json +++ b/Docs/TagInfo/mapcomplete_cafes_and_pubs.json @@ -136,7 +136,7 @@ }, { "key": "contact:email", - "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cafés and pubs')" + "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cafés and pubs')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_climbing.json b/Docs/TagInfo/mapcomplete_climbing.json index bf95400ca..0198298f8 100644 --- a/Docs/TagInfo/mapcomplete_climbing.json +++ b/Docs/TagInfo/mapcomplete_climbing.json @@ -50,7 +50,7 @@ }, { "key": "contact:email", - "description": "Layer 'Climbing club' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + "description": "Layer 'Climbing club' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" }, { "key": "phone", @@ -149,7 +149,7 @@ }, { "key": "contact:email", - "description": "Layer 'Climbing gyms' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + "description": "Layer 'Climbing gyms' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" }, { "key": "charge", @@ -772,7 +772,7 @@ "description": "Layer 'Climbing opportunities?' shows values with key 'access:description' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" }, { - "key": "service:repair:climbing_shoes", + "key": "service:climbing_shoes:repair", "description": "The MapComplete theme Climbing gyms, clubs and spots has a layer Shop showing features with this tag", "value": "yes" }, @@ -787,13 +787,13 @@ "value": "shoemaker" }, { - "key": "service:repair:climbing_shoes", - "description": "Layer 'Shop' shows service:repair:climbing_shoes=yes with a fixed text, namely 'This shop repairs climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "key": "service:climbing_shoes:repair", + "description": "Layer 'Shop' shows service:climbing_shoes:repair=yes with a fixed text, namely 'This shop repairs climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", "value": "yes" }, { - "key": "service:repair:climbing_shoes", - "description": "Layer 'Shop' shows service:repair:climbing_shoes=no with a fixed text, namely 'This shop does not repair climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "key": "service:climbing_shoes:repair", + "description": "Layer 'Shop' shows service:climbing_shoes:repair=no with a fixed text, namely 'This shop does not repair climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", "value": "no" }, { @@ -1642,7 +1642,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" }, { "key": "phone", @@ -1800,13 +1800,13 @@ "value": "key_cutter" }, { - "key": "service:repair:climbing_shoes", - "description": "Layer 'Shop' shows service:repair:climbing_shoes=yes with a fixed text, namely 'This shop repairs climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "key": "service:climbing_shoes:repair", + "description": "Layer 'Shop' shows service:climbing_shoes:repair=yes with a fixed text, namely 'This shop repairs climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", "value": "yes" }, { - "key": "service:repair:climbing_shoes", - "description": "Layer 'Shop' shows service:repair:climbing_shoes=no with a fixed text, namely 'This shop does not repair climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "key": "service:climbing_shoes:repair", + "description": "Layer 'Shop' shows service:climbing_shoes:repair=no with a fixed text, namely 'This shop does not repair climbing shoes' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", "value": "no" }, { @@ -2655,7 +2655,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" }, { "key": "phone", @@ -2797,6 +2797,303 @@ "key": "organic", "description": "Layer 'Shop' shows organic=no with a fixed text, namely 'This shop does not offer organic products' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if shop=supermarket|shop=convenience|shop=farm|shop=greengrocer|shop=health_food|shop=clothes|shop=shoes|shop=butcher|shop=cosmetics|shop=deli|shop=bakery|shop=alcohol|shop=seafood|shop=beverages|shop=florist)", "value": "no" + }, + { + "key": "amenity", + "description": "The MapComplete theme Climbing gyms, clubs and spots has a layer Drinking water showing features with this tag", + "value": "drinking_water" + }, + { + "key": "drinking_water", + "description": "The MapComplete theme Climbing gyms, clubs and spots has a layer Drinking water showing features with this tag", + "value": "yes" + }, + { + "key": "id", + "description": "Layer 'Drinking water' shows id~.+ with a fixed text, namely 'You just created this element! Thanks for sharing this info with the world and helping people worldwide.' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if _backend~.+&_last_edit:passed_time<300&|_version_number=1)" + }, + { + "key": "image", + "description": "The layer 'Drinking water allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Drinking water allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Drinking water allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Drinking water allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "operational_status", + "description": "Layer 'Drinking water' shows and asks freeform values for key 'operational_status' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + }, + { + "key": "operational_status", + "description": "Layer 'Drinking water' shows with a fixed text, namely 'This drinking water works' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') Picking this answer will delete the key operational_status.", + "value": "" + }, + { + "key": "operational_status", + "description": "Layer 'Drinking water' shows operational_status=broken with a fixed text, namely 'This drinking water is broken' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "broken" + }, + { + "key": "operational_status", + "description": "Layer 'Drinking water' shows operational_status=closed with a fixed text, namely 'This drinking water is closed' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "closed" + }, + { + "key": "bottle", + "description": "Layer 'Drinking water' shows bottle=yes with a fixed text, namely 'It is easy to refill water bottles' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "yes" + }, + { + "key": "bottle", + "description": "Layer 'Drinking water' shows bottle=no with a fixed text, namely 'Water bottles may not fit' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "no" + }, + { + "key": "amenity", + "description": "The MapComplete theme Climbing gyms, clubs and spots has a layer Toilets showing features with this tag", + "value": "toilets" + }, + { + "key": "id", + "description": "Layer 'Toilets' shows id~.+ with a fixed text, namely 'You just created this element! Thanks for sharing this info with the world and helping people worldwide.' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if _backend~.+&_last_edit:passed_time<300&|_version_number=1)" + }, + { + "key": "image", + "description": "The layer 'Toilets allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Toilets allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Toilets allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Toilets allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "level", + "description": "Layer 'Toilets' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + }, + { + "key": "location", + "description": "Layer 'Toilets' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "underground" + }, + { + "key": "level", + "description": "Layer 'Toilets' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "0" + }, + { + "key": "level", + "description": "Layer 'Toilets' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') Picking this answer will delete the key level.", + "value": "" + }, + { + "key": "level", + "description": "Layer 'Toilets' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "1" + }, + { + "key": "level", + "description": "Layer 'Toilets' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "-1" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows and asks freeform values for key 'access' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows access=yes with a fixed text, namely 'Public access' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "yes" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows access=customers with a fixed text, namely 'Only access to customers' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "customers" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows access=no with a fixed text, namely 'Not accessible' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "no" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows access=key with a fixed text, namely 'Accessible, but one has to ask a key to enter' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "key" + }, + { + "key": "access", + "description": "Layer 'Toilets' shows access=public with a fixed text, namely 'Public access' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "public" + }, + { + "key": "fee", + "description": "Layer 'Toilets' shows fee=yes with a fixed text, namely 'These are paid toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if access!=no)", + "value": "yes" + }, + { + "key": "fee", + "description": "Layer 'Toilets' shows fee=no with a fixed text, namely 'Free to use' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if access!=no)", + "value": "no" + }, + { + "key": "charge", + "description": "Layer 'Toilets' shows and asks freeform values for key 'charge' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)" + }, + { + "key": "payment:cash", + "description": "Layer 'Toilets' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:cards", + "description": "Layer 'Toilets' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:qr_code", + "description": "Layer 'Toilets' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:coins", + "description": "Layer 'Toilets' shows payment:coins=yes with a fixed text, namely 'Coins are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:notes", + "description": "Layer 'Toilets' shows payment:notes=yes with a fixed text, namely 'Bank notes are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:debit_cards", + "description": "Layer 'Toilets' shows payment:debit_cards=yes with a fixed text, namely 'Debit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "payment:credit_cards", + "description": "Layer 'Toilets' shows payment:credit_cards=yes with a fixed text, namely 'Credit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if fee=yes)", + "value": "yes" + }, + { + "key": "opening_hours", + "description": "Layer 'Toilets' shows and asks freeform values for key 'opening_hours' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if access!=no)" + }, + { + "key": "opening_hours", + "description": "Layer 'Toilets' shows opening_hours=24/7 with a fixed text, namely '24/7 opened (including holidays)' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if access!=no)", + "value": "24/7" + }, + { + "key": "wheelchair", + "description": "Layer 'Toilets' shows wheelchair=yes with a fixed text, namely 'There is a dedicated toilet for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "yes" + }, + { + "key": "wheelchair", + "description": "Layer 'Toilets' shows wheelchair=no with a fixed text, namely 'No wheelchair access' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "no" + }, + { + "key": "wheelchair", + "description": "Layer 'Toilets' shows wheelchair=designated with a fixed text, namely 'There is only a dedicated toilet for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "designated" + }, + { + "key": "door:width", + "description": "Layer 'Toilets' shows and asks freeform values for key 'door:width' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if wheelchair=yes|wheelchair=designated)" + }, + { + "key": "toilets:position", + "description": "Layer 'Toilets' shows toilets:position=seated with a fixed text, namely 'There are only seated toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "seated" + }, + { + "key": "toilets:position", + "description": "Layer 'Toilets' shows toilets:position=urinal with a fixed text, namely 'There are only urinals here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "urinal" + }, + { + "key": "toilets:position", + "description": "Layer 'Toilets' shows toilets:position=squat with a fixed text, namely 'There are only squat toilets here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "squat" + }, + { + "key": "toilets:position", + "description": "Layer 'Toilets' shows toilets:position=seated;urinal with a fixed text, namely 'Both seated toilets and urinals are available here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "seated;urinal" + }, + { + "key": "changing_table", + "description": "Layer 'Toilets' shows changing_table=yes with a fixed text, namely 'A changing table is available' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "yes" + }, + { + "key": "changing_table", + "description": "Layer 'Toilets' shows changing_table=no with a fixed text, namely 'No changing table is available' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "no" + }, + { + "key": "changing_table:location", + "description": "Layer 'Toilets' shows and asks freeform values for key 'changing_table:location' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if changing_table=yes)" + }, + { + "key": "changing_table:location", + "description": "Layer 'Toilets' shows changing_table:location=female_toilet with a fixed text, namely 'The changing table is in the toilet for women. ' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if changing_table=yes)", + "value": "female_toilet" + }, + { + "key": "changing_table:location", + "description": "Layer 'Toilets' shows changing_table:location=male_toilet with a fixed text, namely 'The changing table is in the toilet for men. ' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if changing_table=yes)", + "value": "male_toilet" + }, + { + "key": "changing_table:location", + "description": "Layer 'Toilets' shows changing_table:location=wheelchair_toilet with a fixed text, namely 'The changing table is in the toilet for wheelchair users. ' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if changing_table=yes)", + "value": "wheelchair_toilet" + }, + { + "key": "changing_table:location", + "description": "Layer 'Toilets' shows changing_table:location=dedicated_room with a fixed text, namely 'The changing table is in a dedicated room. ' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if changing_table=yes)", + "value": "dedicated_room" + }, + { + "key": "toilets:handwashing", + "description": "Layer 'Toilets' shows toilets:handwashing=yes with a fixed text, namely 'This toilets have a sink to wash your hands' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "yes" + }, + { + "key": "toilets:handwashing", + "description": "Layer 'Toilets' shows toilets:handwashing=no with a fixed text, namely 'This toilets don't have a sink to wash your hands' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')", + "value": "no" + }, + { + "key": "toilets:paper_supplied", + "description": "Layer 'Toilets' shows toilets:paper_supplied=yes with a fixed text, namely 'This toilet is equipped with toilet paper' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if toilets:position!=urinal)", + "value": "yes" + }, + { + "key": "toilets:paper_supplied", + "description": "Layer 'Toilets' shows toilets:paper_supplied=no with a fixed text, namely 'You have to bring your own toilet paper to this toilet' and allows to pick this as a default answer (in the mapcomplete.org theme 'Climbing gyms, clubs and spots') (This is only shown if toilets:position!=urinal)", + "value": "no" + }, + { + "key": "description", + "description": "Layer 'Toilets' shows and asks freeform values for key 'description' (in the mapcomplete.org theme 'Climbing gyms, clubs and spots')" } ] } \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_cyclofix.json b/Docs/TagInfo/mapcomplete_cyclofix.json index df02011d4..e2c867d7f 100644 --- a/Docs/TagInfo/mapcomplete_cyclofix.json +++ b/Docs/TagInfo/mapcomplete_cyclofix.json @@ -126,7 +126,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike cafe' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" + "description": "Layer 'Bike cafe' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" }, { "key": "opening_hours", @@ -208,7 +208,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike repair/shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" + "description": "Layer 'Bike repair/shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" }, { "key": "opening_hours", @@ -505,7 +505,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" + "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" }, { "key": "phone", @@ -682,7 +682,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" + "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" }, { "key": "opening_hours", @@ -1197,7 +1197,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike-related object' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" + "description": "Layer 'Bike-related object' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Cyclofix - a map for cyclists')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_education.json b/Docs/TagInfo/mapcomplete_education.json index 40c8a3665..867661812 100644 --- a/Docs/TagInfo/mapcomplete_education.json +++ b/Docs/TagInfo/mapcomplete_education.json @@ -100,7 +100,7 @@ }, { "key": "contact:email", - "description": "Layer 'Colleges and universities' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" + "description": "Layer 'Colleges and universities' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" }, { "key": "phone", @@ -253,7 +253,7 @@ }, { "key": "contact:email", - "description": "Layer 'Primary and secondary schools' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" + "description": "Layer 'Primary and secondary schools' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" }, { "key": "amenity", @@ -302,7 +302,7 @@ }, { "key": "contact:email", - "description": "Layer 'Kindergartens and childcare' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" + "description": "Layer 'Kindergartens and childcare' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Education')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_elongated_coin.json b/Docs/TagInfo/mapcomplete_elongated_coin.json index eed6b6e45..2db149d70 100644 --- a/Docs/TagInfo/mapcomplete_elongated_coin.json +++ b/Docs/TagInfo/mapcomplete_elongated_coin.json @@ -73,39 +73,54 @@ "description": "Layer 'Penny Presses' shows coin:design_count=4 with a fixed text, namely 'This penny press has four designs available.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", "value": "4" }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows with a fixed text, namely 'It costs money to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') Picking this answer will delete the key fee.", + "value": "" + }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows fee=yes with a fixed text, namely 'It costs money to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "value": "yes" + }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows fee=no with a fixed text, namely 'It is free to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "value": "no" + }, { "key": "payment:cash", - "description": "Layer 'Penny Presses' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:cards", - "description": "Layer 'Penny Presses' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:qr_code", - "description": "Layer 'Penny Presses' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:coins", - "description": "Layer 'Penny Presses' shows payment:coins=yes with a fixed text, namely 'Coins are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:coins=yes with a fixed text, namely 'Coins are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:notes", - "description": "Layer 'Penny Presses' shows payment:notes=yes with a fixed text, namely 'Bank notes are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:notes=yes with a fixed text, namely 'Bank notes are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:debit_cards", - "description": "Layer 'Penny Presses' shows payment:debit_cards=yes with a fixed text, namely 'Debit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:debit_cards=yes with a fixed text, namely 'Debit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:credit_cards", - "description": "Layer 'Penny Presses' shows payment:credit_cards=yes with a fixed text, namely 'Credit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows payment:credit_cards=yes with a fixed text, namely 'Credit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "yes" }, { @@ -137,6 +152,16 @@ "description": "Layer 'Penny Presses' shows coin:type=50cent with a fixed text, namely 'This penny press uses a 50 cent coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", "value": "50cent" }, + { + "key": "coin:type", + "description": "Layer 'Penny Presses' shows coin:type=10centimes with a fixed text, namely 'This penny press uses a 10 centimes coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "value": "10centimes" + }, + { + "key": "coin:type", + "description": "Layer 'Penny Presses' shows coin:type=20centimes with a fixed text, namely 'This penny press uses a 20 centimes coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "value": "20centimes" + }, { "key": "website", "description": "Layer 'Penny Presses' shows and asks freeform values for key 'website' (in the mapcomplete.org theme 'Penny Presses')" @@ -147,58 +172,103 @@ }, { "key": "charge", - "description": "Layer 'Penny Presses' shows and asks freeform values for key 'charge' (in the mapcomplete.org theme 'Penny Presses')" + "description": "Layer 'Penny Presses' shows and asks freeform values for key 'charge' (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)" }, { "key": "charge", - "description": "Layer 'Penny Presses' shows charge=1 EUR with a fixed text, namely 'It costs 1 euro to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows charge=1 EUR with a fixed text, namely 'It costs 1 euro to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "1 EUR" }, { "key": "charge", - "description": "Layer 'Penny Presses' shows charge=2 EUR with a fixed text, namely 'It costs 2 euros to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", + "description": "Layer 'Penny Presses' shows charge=2 EUR with a fixed text, namely 'It costs 2 euros to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", "value": "2 EUR" }, + { + "key": "charge", + "description": "Layer 'Penny Presses' shows charge=2 CHF with a fixed text, namely 'It costs 2 Swiss francs to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", + "value": "2 CHF" + }, + { + "key": "charge", + "description": "Layer 'Penny Presses' shows charge=1 CHF with a fixed text, namely 'It costs 1 Swiss franc to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if fee=yes|)", + "value": "1 CHF" + }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "indoor", "description": "Layer 'Penny Presses' shows indoor=yes with a fixed text, namely 'This penny press is located indoors.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Penny Presses')", diff --git a/Docs/TagInfo/mapcomplete_food.json b/Docs/TagInfo/mapcomplete_food.json index b89992ec4..315523e95 100644 --- a/Docs/TagInfo/mapcomplete_food.json +++ b/Docs/TagInfo/mapcomplete_food.json @@ -101,7 +101,7 @@ }, { "key": "contact:email", - "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Restaurants and fast food')" + "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Restaurants and fast food')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_fritures.json b/Docs/TagInfo/mapcomplete_fritures.json index 334e1f42f..d6e655e01 100644 --- a/Docs/TagInfo/mapcomplete_fritures.json +++ b/Docs/TagInfo/mapcomplete_fritures.json @@ -105,7 +105,7 @@ }, { "key": "contact:email", - "description": "Layer 'Fries shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Fries shops')" + "description": "Layer 'Fries shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Fries shops')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_ghostbikes.json b/Docs/TagInfo/mapcomplete_ghostbikes.json index 2ec514e85..f2a4a8c45 100644 --- a/Docs/TagInfo/mapcomplete_ghostbikes.json +++ b/Docs/TagInfo/mapcomplete_ghostbikes.json @@ -2,7 +2,7 @@ "data_format": 1, "project": { "name": "MapComplete Ghost bikes", - "description": "A ", + "description": "A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location", "project_url": "https://mapcomplete.org/ghostbikes", "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", "icon_url": "https://mapcomplete.org/assets/themes/ghostbikes/logo.svg", diff --git a/Docs/TagInfo/mapcomplete_hackerspaces.json b/Docs/TagInfo/mapcomplete_hackerspaces.json index 552c4f1c8..4e06af13f 100644 --- a/Docs/TagInfo/mapcomplete_hackerspaces.json +++ b/Docs/TagInfo/mapcomplete_hackerspaces.json @@ -92,7 +92,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hackerspace' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Hackerspaces')" + "description": "Layer 'Hackerspace' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Hackerspaces')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_healthcare.json b/Docs/TagInfo/mapcomplete_healthcare.json index a674b6c9f..8d5bd173e 100644 --- a/Docs/TagInfo/mapcomplete_healthcare.json +++ b/Docs/TagInfo/mapcomplete_healthcare.json @@ -66,7 +66,7 @@ }, { "key": "contact:email", - "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "website", @@ -156,7 +156,7 @@ }, { "key": "contact:email", - "description": "Layer 'Physiotherapist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Physiotherapist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "website", @@ -209,7 +209,7 @@ }, { "key": "contact:email", - "description": "Layer 'Dentist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Dentist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "website", @@ -265,7 +265,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "website", @@ -326,7 +326,7 @@ }, { "key": "contact:email", - "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "website", @@ -1226,7 +1226,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "phone", @@ -2229,7 +2229,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Healthcare')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_hotels.json b/Docs/TagInfo/mapcomplete_hotels.json index 59d7bbcfc..f77384909 100644 --- a/Docs/TagInfo/mapcomplete_hotels.json +++ b/Docs/TagInfo/mapcomplete_hotels.json @@ -53,7 +53,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Hotels')" + "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Hotels')" }, { "key": "website", diff --git a/Docs/TagInfo/mapcomplete_indoors.json b/Docs/TagInfo/mapcomplete_indoors.json index 981e2ef7c..594b6fb25 100644 --- a/Docs/TagInfo/mapcomplete_indoors.json +++ b/Docs/TagInfo/mapcomplete_indoors.json @@ -179,6 +179,26 @@ "description": "Layer 'Elevator' shows hearing_loop=no with a fixed text, namely 'This place does not have an audio induction loop' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", "value": "no" }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=yes with a fixed text, namely 'This elevator has tactile writing in Braille' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "yes" + }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=no with a fixed text, namely 'This elevator does not have tactile writing' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "no" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=yes with a fixed text, namely 'This elevator has speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "yes" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=no with a fixed text, namely 'This elevator does not have speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')", + "value": "no" + }, { "key": "entrance", "description": "The MapComplete theme Indoors has a layer Entrance showing features with this tag" diff --git a/Docs/TagInfo/mapcomplete_onwheels.json b/Docs/TagInfo/mapcomplete_onwheels.json index 16e048ece..97ce2f8c7 100644 --- a/Docs/TagInfo/mapcomplete_onwheels.json +++ b/Docs/TagInfo/mapcomplete_onwheels.json @@ -136,7 +136,7 @@ }, { "key": "contact:email", - "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "phone", @@ -611,7 +611,7 @@ }, { "key": "contact:email", - "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "phone", @@ -2100,7 +2100,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "phone", @@ -2528,7 +2528,7 @@ }, { "key": "contact:email", - "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "website", @@ -2609,7 +2609,7 @@ }, { "key": "contact:email", - "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "website", @@ -2685,7 +2685,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "website", @@ -2863,6 +2863,26 @@ "description": "Layer 'Elevator' shows hearing_loop=no with a fixed text, namely 'This place does not have an audio induction loop' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", "value": "no" }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=yes with a fixed text, namely 'This elevator has tactile writing in Braille' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "yes" + }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=no with a fixed text, namely 'This elevator does not have tactile writing' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "no" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=yes with a fixed text, namely 'This elevator has speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "yes" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=no with a fixed text, namely 'This elevator does not have speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", + "value": "no" + }, { "key": "tourism", "description": "The MapComplete theme OnWheels has a layer Hotels showing features with this tag", @@ -2906,7 +2926,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "website", @@ -3024,7 +3044,7 @@ }, { "key": "contact:email", - "description": "Layer 'governments' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" + "description": "Layer 'governments' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'OnWheels')" }, { "key": "website", diff --git a/Docs/TagInfo/mapcomplete_parkings.json b/Docs/TagInfo/mapcomplete_parkings.json index e8df35430..901dfe0c2 100644 --- a/Docs/TagInfo/mapcomplete_parkings.json +++ b/Docs/TagInfo/mapcomplete_parkings.json @@ -309,79 +309,144 @@ }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "5 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "10 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "20 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "50 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "100 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "200 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "500 EUR" }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 CHF with a fixed text, namely '10 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "10 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 CHF with a fixed text, namely '20 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "20 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 CHF with a fixed text, namely '50 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "50 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 CHF with a fixed text, namely '100 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "100 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 CHF with a fixed text, namely '200 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "200 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=1000 CHF with a fixed text, namely '1000 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Parking') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1000 CHF" + }, { "key": "ref", "description": "Layer 'Parking Ticket Machines' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Parking')" diff --git a/Docs/TagInfo/mapcomplete_personal.json b/Docs/TagInfo/mapcomplete_personal.json index 071f91349..6c25eb3f1 100644 --- a/Docs/TagInfo/mapcomplete_personal.json +++ b/Docs/TagInfo/mapcomplete_personal.json @@ -637,37 +637,37 @@ }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=5 EUR with a fixed text, namely '5 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=5 EUR with a fixed text, namely '5 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "5 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=10 EUR with a fixed text, namely '10 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=10 EUR with a fixed text, namely '10 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "10 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=20 EUR with a fixed text, namely '20 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=20 EUR with a fixed text, namely '20 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "20 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=50 EUR with a fixed text, namely '50 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=50 EUR with a fixed text, namely '50 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "50 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=100 EUR with a fixed text, namely '100 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=100 EUR with a fixed text, namely '100 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "100 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=200 EUR with a fixed text, namely '200 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=200 EUR with a fixed text, namely '200 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "200 EUR" }, { "key": "cash_out:notes:denominations", - "description": "Layer 'ATMs' shows cash_out:notes:denominations=500 EUR with a fixed text, namely '500 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk)", + "description": "Layer 'ATMs' shows cash_out:notes:denominations=500 EUR with a fixed text, namely '500 euro notes can be withdrawn' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if |cash_out=yes&|_currency~^(.*EUR.*)$)", "value": "500 EUR" }, { @@ -1189,7 +1189,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Bicycle library' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "opening_hours", @@ -1330,7 +1330,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Bicycle rental' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -1692,7 +1692,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike cafe' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Bike cafe' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "opening_hours", @@ -2192,7 +2192,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike repair/shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Bike repair/shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "opening_hours", @@ -2480,7 +2480,7 @@ }, { "key": "contact:email", - "description": "Layer 'Bike-related object' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Bike-related object' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -2777,7 +2777,7 @@ }, { "key": "contact:email", - "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Cafés and pubs' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -4338,7 +4338,7 @@ }, { "key": "contact:email", - "description": "Layer 'Climbing club' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Climbing club' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -4408,7 +4408,7 @@ }, { "key": "contact:email", - "description": "Layer 'Climbing gyms' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Climbing gyms' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "charge", @@ -5920,7 +5920,7 @@ }, { "key": "contact:email", - "description": "Layer 'Dentist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Dentist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -5990,7 +5990,7 @@ }, { "key": "contact:email", - "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Doctors' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -6238,6 +6238,26 @@ "description": "Layer 'Elevator' shows hearing_loop=no with a fixed text, namely 'This place does not have an audio induction loop' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "value": "no" }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=yes with a fixed text, namely 'This elevator has tactile writing in Braille' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "yes" + }, + { + "key": "tactile_writing:braille", + "description": "Layer 'Elevator' shows tactile_writing:braille=no with a fixed text, namely 'This elevator does not have tactile writing' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "no" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=yes with a fixed text, namely 'This elevator has speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "yes" + }, + { + "key": "speech_output", + "description": "Layer 'Elevator' shows speech_output=no with a fixed text, namely 'This elevator does not have speech output' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "no" + }, { "key": "amenity", "description": "The MapComplete theme Personal theme has a layer Penny Presses showing features with this tag", @@ -6301,39 +6321,54 @@ "description": "Layer 'Penny Presses' shows coin:design_count=4 with a fixed text, namely 'This penny press has four designs available.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "value": "4" }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows with a fixed text, namely 'It costs money to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key fee.", + "value": "" + }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows fee=yes with a fixed text, namely 'It costs money to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "yes" + }, + { + "key": "fee", + "description": "Layer 'Penny Presses' shows fee=no with a fixed text, namely 'It is free to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "no" + }, { "key": "payment:cash", - "description": "Layer 'Penny Presses' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:cash=yes with a fixed text, namely 'Cash is accepted here' (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:cards", - "description": "Layer 'Penny Presses' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:cards=yes with a fixed text, namely 'Payment cards are accepted here' (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:qr_code", - "description": "Layer 'Penny Presses' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:coins", - "description": "Layer 'Penny Presses' shows payment:coins=yes with a fixed text, namely 'Coins are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:coins=yes with a fixed text, namely 'Coins are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:notes", - "description": "Layer 'Penny Presses' shows payment:notes=yes with a fixed text, namely 'Bank notes are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:notes=yes with a fixed text, namely 'Bank notes are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:debit_cards", - "description": "Layer 'Penny Presses' shows payment:debit_cards=yes with a fixed text, namely 'Debit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:debit_cards=yes with a fixed text, namely 'Debit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { "key": "payment:credit_cards", - "description": "Layer 'Penny Presses' shows payment:credit_cards=yes with a fixed text, namely 'Credit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows payment:credit_cards=yes with a fixed text, namely 'Credit cards are accepted here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "yes" }, { @@ -6365,6 +6400,16 @@ "description": "Layer 'Penny Presses' shows coin:type=50cent with a fixed text, namely 'This penny press uses a 50 cent coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "value": "50cent" }, + { + "key": "coin:type", + "description": "Layer 'Penny Presses' shows coin:type=10centimes with a fixed text, namely 'This penny press uses a 10 centimes coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "10centimes" + }, + { + "key": "coin:type", + "description": "Layer 'Penny Presses' shows coin:type=20centimes with a fixed text, namely 'This penny press uses a 20 centimes coin for pressing.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "value": "20centimes" + }, { "key": "website", "description": "Layer 'Penny Presses' shows and asks freeform values for key 'website' (in the mapcomplete.org theme 'Personal theme')" @@ -6375,58 +6420,103 @@ }, { "key": "charge", - "description": "Layer 'Penny Presses' shows and asks freeform values for key 'charge' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Penny Presses' shows and asks freeform values for key 'charge' (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)" }, { "key": "charge", - "description": "Layer 'Penny Presses' shows charge=1 EUR with a fixed text, namely 'It costs 1 euro to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows charge=1 EUR with a fixed text, namely 'It costs 1 euro to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "1 EUR" }, { "key": "charge", - "description": "Layer 'Penny Presses' shows charge=2 EUR with a fixed text, namely 'It costs 2 euros to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", + "description": "Layer 'Penny Presses' shows charge=2 EUR with a fixed text, namely 'It costs 2 euros to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", "value": "2 EUR" }, + { + "key": "charge", + "description": "Layer 'Penny Presses' shows charge=2 CHF with a fixed text, namely 'It costs 2 Swiss francs to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", + "value": "2 CHF" + }, + { + "key": "charge", + "description": "Layer 'Penny Presses' shows charge=1 CHF with a fixed text, namely 'It costs 1 Swiss franc to press a penny.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if fee=yes|)", + "value": "1 CHF" + }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Penny Presses' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "indoor", "description": "Layer 'Penny Presses' shows indoor=yes with a fixed text, namely 'This penny press is located indoors.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", @@ -6929,7 +7019,7 @@ }, { "key": "contact:email", - "description": "Layer 'Fitness Centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Fitness Centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -7276,7 +7366,7 @@ }, { "key": "contact:email", - "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Restaurants and fast food' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -7768,7 +7858,7 @@ }, { "key": "contact:email", - "description": "Layer 'governments' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'governments' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -7864,7 +7954,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hackerspace' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Hackerspace' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -7993,7 +8083,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Hospitals' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -8050,7 +8140,7 @@ }, { "key": "contact:email", - "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Hotels' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -8447,7 +8537,7 @@ }, { "key": "contact:email", - "description": "Layer 'Kindergartens and childcare' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Kindergartens and childcare' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -9375,79 +9465,144 @@ }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "5 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "10 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "20 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "50 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "100 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "200 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "500 EUR" }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=10 CHF with a fixed text, namely '10 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "10 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=20 CHF with a fixed text, namely '20 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "20 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=50 CHF with a fixed text, namely '50 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "50 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=100 CHF with a fixed text, namely '100 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "100 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=200 CHF with a fixed text, namely '200 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "200 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Parking Ticket Machines' shows payment:notes:denominations=1000 CHF with a fixed text, namely '1000 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1000 CHF" + }, { "key": "ref", "description": "Layer 'Parking Ticket Machines' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Personal theme')" @@ -9524,7 +9679,7 @@ }, { "key": "contact:email", - "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -9605,7 +9760,7 @@ }, { "key": "contact:email", - "description": "Layer 'Physiotherapist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Physiotherapist' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "website", @@ -10524,7 +10679,7 @@ }, { "key": "contact:email", - "description": "Layer 'Recycling' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme') (This is only shown if recycling_type=centre)" + "description": "Layer 'Recycling' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme') (This is only shown if recycling_type=centre)" }, { "key": "phone", @@ -10714,7 +10869,7 @@ }, { "key": "contact:email", - "description": "Layer 'Primary and secondary schools' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Primary and secondary schools' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "amenity", @@ -11624,7 +11779,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -12164,7 +12319,7 @@ }, { "key": "contact:email", - "description": "Layer 'Sports centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Sports centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "wheelchair", @@ -12750,7 +12905,7 @@ }, { "key": "contact:email", - "description": "Layer 'Colleges and universities' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" + "description": "Layer 'Colleges and universities' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Personal theme')" }, { "key": "phone", @@ -12865,79 +13020,144 @@ }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Ticket Machines' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "5 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "10 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "20 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "50 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "100 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "200 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "500 EUR" }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=10 CHF with a fixed text, namely '10 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "10 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=20 CHF with a fixed text, namely '20 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "20 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=50 CHF with a fixed text, namely '50 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "50 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=100 CHF with a fixed text, namely '100 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "100 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=200 CHF with a fixed text, namely '200 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "200 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Ticket Machines' shows payment:notes:denominations=1000 CHF with a fixed text, namely '1000 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1000 CHF" + }, { "key": "amenity", "description": "The MapComplete theme Personal theme has a layer Toilets showing features with this tag", @@ -13908,79 +14128,144 @@ }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "5 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "10 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "20 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "50 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "100 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "200 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "500 EUR" }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 CHF with a fixed text, namely '10 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "10 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 CHF with a fixed text, namely '20 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "20 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 CHF with a fixed text, namely '50 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "50 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 CHF with a fixed text, namely '100 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "100 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 CHF with a fixed text, namely '200 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "200 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=1000 CHF with a fixed text, namely '1000 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1000 CHF" + }, { "key": "operator", "description": "Layer 'Vending Machines' shows and asks freeform values for key 'operator' (in the mapcomplete.org theme 'Personal theme')" diff --git a/Docs/TagInfo/mapcomplete_pets.json b/Docs/TagInfo/mapcomplete_pets.json index 8d9b9c261..22b0186f7 100644 --- a/Docs/TagInfo/mapcomplete_pets.json +++ b/Docs/TagInfo/mapcomplete_pets.json @@ -2,7 +2,7 @@ "data_format": 1, "project": { "name": "MapComplete Veterinarians, dog parks and other pet-amenities", - "description": "On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, ", + "description": "On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants,", "project_url": "https://mapcomplete.org/pets", "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", "icon_url": "https://mapcomplete.org/assets/layers/dogpark/dog-park.svg", @@ -175,7 +175,7 @@ }, { "key": "contact:email", - "description": "Layer 'Dog friendly eateries' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" + "description": "Layer 'Dog friendly eateries' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" }, { "key": "phone", @@ -1443,7 +1443,7 @@ }, { "key": "contact:email", - "description": "Layer 'Dog-friendly shops' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" + "description": "Layer 'Dog-friendly shops' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_shops.json b/Docs/TagInfo/mapcomplete_shops.json index 1791e8a0e..ad174eb6c 100644 --- a/Docs/TagInfo/mapcomplete_shops.json +++ b/Docs/TagInfo/mapcomplete_shops.json @@ -870,7 +870,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Shops')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Shops')" }, { "key": "phone", @@ -1060,7 +1060,7 @@ }, { "key": "contact:email", - "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Shops')" + "description": "Layer 'Pharmacies' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Shops')" }, { "key": "website", diff --git a/Docs/TagInfo/mapcomplete_sports.json b/Docs/TagInfo/mapcomplete_sports.json index a412a1b80..0c1a81714 100644 --- a/Docs/TagInfo/mapcomplete_sports.json +++ b/Docs/TagInfo/mapcomplete_sports.json @@ -237,7 +237,7 @@ }, { "key": "contact:email", - "description": "Layer 'Fitness Centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" + "description": "Layer 'Fitness Centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" }, { "key": "website", @@ -523,7 +523,7 @@ }, { "key": "contact:email", - "description": "Layer 'Sports centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" + "description": "Layer 'Sports centres' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" }, { "key": "wheelchair", @@ -1396,7 +1396,7 @@ }, { "key": "contact:email", - "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" + "description": "Layer 'Shop' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Sports')" }, { "key": "phone", diff --git a/Docs/TagInfo/mapcomplete_vending_machine.json b/Docs/TagInfo/mapcomplete_vending_machine.json index 84e9fee60..cf55db233 100644 --- a/Docs/TagInfo/mapcomplete_vending_machine.json +++ b/Docs/TagInfo/mapcomplete_vending_machine.json @@ -180,79 +180,144 @@ }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.01 EUR with a fixed text, namely '1 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.01 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.02 EUR with a fixed text, namely '2 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.02 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 EUR with a fixed text, namely '5 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.05 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 EUR with a fixed text, namely '10 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.10 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 EUR with a fixed text, namely '20 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.20 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 EUR with a fixed text, namely '50 cent coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "0.50 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 EUR with a fixed text, namely '1 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "1 EUR" }, { "key": "payment:coins:denominations", - "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 EUR with a fixed text, namely '2 euro coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "2 EUR" }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.05 CHF with a fixed text, namely '5 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.05 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.10 CHF with a fixed text, namely '10 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.10 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.20 CHF with a fixed text, namely '20 centimes coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.20 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=0.50 CHF with a fixed text, namely '½ franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "0.50 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=1 CHF with a fixed text, namely '1 franc coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=2 CHF with a fixed text, namely '2 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "2 CHF" + }, + { + "key": "payment:coins:denominations", + "description": "Layer 'Vending Machines' shows payment:coins:denominations=5 CHF with a fixed text, namely '5 francs coins are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:coins=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "5 CHF" + }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=5 EUR with a fixed text, namely '5 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "5 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 EUR with a fixed text, namely '10 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "10 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 EUR with a fixed text, namely '20 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "20 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 EUR with a fixed text, namely '50 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "50 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 EUR with a fixed text, namely '100 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "100 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 EUR with a fixed text, namely '200 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "200 EUR" }, { "key": "payment:notes:denominations", - "description": "Layer 'Vending Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency=EUR)", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=500 EUR with a fixed text, namely '500 euro notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", "value": "500 EUR" }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=10 CHF with a fixed text, namely '10 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "10 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=20 CHF with a fixed text, namely '20 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "20 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=50 CHF with a fixed text, namely '50 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "50 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=100 CHF with a fixed text, namely '100 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "100 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=200 CHF with a fixed text, namely '200 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "200 CHF" + }, + { + "key": "payment:notes:denominations", + "description": "Layer 'Vending Machines' shows payment:notes:denominations=1000 CHF with a fixed text, namely '1000 francs notes are accepted' and allows to pick this as a default answer (in the mapcomplete.org theme 'Vending Machines') (This is only shown if payment:notes=yes|payment:cash=yes&_currency~^(.*EUR.*)$|_currency~^(.*CHF.*)$)", + "value": "1000 CHF" + }, { "key": "operator", "description": "Layer 'Vending Machines' shows and asks freeform values for key 'operator' (in the mapcomplete.org theme 'Vending Machines')" diff --git a/Docs/TagInfo/mapcomplete_waste.json b/Docs/TagInfo/mapcomplete_waste.json index 54668a0f7..7aaf9b8a7 100644 --- a/Docs/TagInfo/mapcomplete_waste.json +++ b/Docs/TagInfo/mapcomplete_waste.json @@ -312,7 +312,7 @@ }, { "key": "contact:email", - "description": "Layer 'Recycling' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Waste') (This is only shown if recycling_type=centre)" + "description": "Layer 'Recycling' shows contact:email~.+ with a fixed text, namely '{contact:email}' (in the mapcomplete.org theme 'Waste') (This is only shown if recycling_type=centre)" }, { "key": "phone", diff --git a/Docs/Themes/climbing.md b/Docs/Themes/climbing.md index 9c018bcc9..32f4a6379 100644 --- a/Docs/Themes/climbing.md +++ b/Docs/Themes/climbing.md @@ -18,6 +18,8 @@ This theme contains the following layers: - [climbing_opportunity](../Layers/climbing_opportunity.md) - [shops_with_climbing_shoe_repair](../Layers/shops_with_climbing_shoe_repair.md) - [shops](../Layers/shops.md) + - [drinking_water](../Layers/drinking_water.md) + - [toilet](../Layers/toilet.md) - [selected_element](../Layers/selected_element.md) - [gps_location](../Layers/gps_location.md) - [gps_location_history](../Layers/gps_location_history.md) diff --git a/Docs/Themes/mapcomplete-changes.md b/Docs/Themes/mapcomplete-changes.md index 3600da31b..52a9923aa 100644 --- a/Docs/Themes/mapcomplete-changes.md +++ b/Docs/Themes/mapcomplete-changes.md @@ -27,6 +27,10 @@ Available languages: - en + - ca + - de + - fr + - nl This document is autogenerated from [assets/themes/mapcomplete-changes/mapcomplete-changes.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/mapcomplete-changes/mapcomplete-changes.json) diff --git a/Docs/wikiIndex.txt b/Docs/wikiIndex.txt index 8ab861c30..3ecddb446 100644 --- a/Docs/wikiIndex.txt +++ b/Docs/wikiIndex.txt @@ -56,6 +56,15 @@ |genre= POI, editor, cafes_and_pubs }} {{service_item +|name= [https://mapcomplete.org/shops shops] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}}, {{#language:zh_Hant|en}} +|descr= A MapComplete theme: An editable map with basic shop information +|material= {{yes|[https://mapcomplete.org/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, shops +}} +{{service_item |name= [https://mapcomplete.org/playgrounds playgrounds] |region= Worldwide |lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:id|en}}, {{#language:hu|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:pl|en}} @@ -275,7 +284,7 @@ |name= [https://mapcomplete.org/ghostbikes ghostbikes] |region= Worldwide |lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A +|descr= A MapComplete theme: A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location |material= {{yes|[https://mapcomplete.org/ Yes]}} |image= MapComplete_Screenshot.png |genre= POI, editor, ghostbikes @@ -410,7 +419,7 @@ |name= [https://mapcomplete.org/pets pets] |region= Worldwide |lang= {{#language:en|en}}, {{#language:da|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, +|descr= A MapComplete theme: On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, |material= {{yes|[https://mapcomplete.org/ Yes]}} |image= MapComplete_Screenshot.png |genre= POI, editor, pets @@ -434,15 +443,6 @@ |genre= POI, editor, rainbow_crossings }} {{service_item -|name= [https://mapcomplete.org/shops shops] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}}, {{#language:zh_Hant|en}} -|descr= A MapComplete theme: An editable map with basic shop information -|material= {{yes|[https://mapcomplete.org/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, shops -}} -{{service_item |name= [https://mapcomplete.org/sport_pitches sport_pitches] |region= Worldwide |lang= {{#language:nl|en}}, {{#language:fr|en}}, {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 705e3e60f..174b24c58 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -49,9 +49,9 @@ "then": { "en": "Charging station for electrical bicycles", "nl": "Oplaadpunt voor elektrische fietsen", + "ca": "Estació de càrrega per a bicicletes elèctriques", "de": "Ladestation für Elektrofahrräder", - "es": "Estación de carga para bicicletas eléctricas", - "ca": "Estació de càrrega per a bicicletes elèctriques" + "es": "Estación de carga para bicicletas eléctricas" } }, { @@ -69,9 +69,9 @@ "then": { "en": "Charging station for cars", "nl": "Oplaadpunt voor elektrische auto's", + "ca": "Estació de càrrega per a cotxes", "de": "Ladestation für Autos", - "es": "Estación de carga para coches", - "ca": "Estació de càrrega per a cotxes" + "es": "Estación de carga para coches" } } ] @@ -889,7 +889,7 @@ "then": { "en": "Tesla Supercharger CCS (a branded type2_css)", "nl": "Tesla Supercharger CCS (een type2 CCS met Tesla-logo)", - "ca": "CSS Supercarregador Tesla (tipus2_css de la marca)", + "ca": "CSS Supercarregador Tesla (un tipus2_css de la marca)", "da": "Tesla Supercharger CCS (en mærkevare type2_css)", "de": "Tesla Supercharger CCS (Typ 2 CSS von Tesla)", "es": "CCS Supercargador Tesla (un tipo2_css con marca)" @@ -2762,9 +2762,9 @@ "then": { "en": "Type 2 (mennekes) outputs at most 16 A", "nl": "Type 2 (mennekes) levert een stroom van maximaal 16 A", + "ca": "Tipus 2 (menneks) surt com a màxim a 16 A", "da": "Type 2 (mennekes) udgange højst 16 A", - "de": "Typ 2 (Mennekes) liefert maximal 16 A", - "ca": "Tipus 2 (menneks) surt com a màxim a 16 A" + "de": "Typ 2 (Mennekes) liefert maximal 16 A" }, "icon": { "path": "./assets/layers/charging_station/Type2_socket.svg", @@ -2776,9 +2776,9 @@ "then": { "en": "Type 2 (mennekes) outputs at most 32 A", "nl": "Type 2 (mennekes) levert een stroom van maximaal 32 A", + "ca": "Tipus 2 (menneks) surt com a màxim a 32 A", "da": "Type 2 (mennekes) udgange højst 32 A", - "de": "Typ 2 (Mennekes) liefert maximal 32 A", - "ca": "Tipus 2 (menneks) surt com a màxim a 32 A" + "de": "Typ 2 (Mennekes) liefert maximal 32 A" }, "icon": { "path": "./assets/layers/charging_station/Type2_socket.svg", @@ -3529,9 +3529,9 @@ "question": { "en": "What current do the plugs with
Tesla Supercharger (Destination) (A Type 2 with cable branded as Tesla)
offer?", "nl": "Welke stroom levert de stekker van type
Tesla Supercharger (Destination) (Een Type 2 met kabel en Tesla-logo)
?", + "ca": "Quin corrent donen els endolls amb
Tesla Supercharger (destinació) (un tipus 2 amb cable amb la marca Tesla)
oferta?", "da": "Hvilken strømstyrke har stikkene med
Tesla Supercharger (Destination) (A Type 2 med kabel med Tesla-mærket)
?", - "de": "Welche Stromstärke liefern die Stecker mit
Tesla Supercharger (Destination) (Typ 2 mit Kabel von Tesla)
?", - "ca": "Quin corrent donen els endolls amb
Tesla Supercharger (destinació) (un tipus 2 amb cable amb la marca Tesla)
oferta?" + "de": "Welche Stromstärke liefern die Stecker mit
Tesla Supercharger (Destination) (Typ 2 mit Kabel von Tesla)
?" }, "render": { "en": "
Tesla Supercharger (Destination) (A Type 2 with cable branded as Tesla)
outputs at most {socket:tesla_destination:current}A", @@ -3548,9 +3548,9 @@ "then": { "en": "Tesla Supercharger (Destination) (A Type 2 with cable branded as tesla) outputs at most 16 A", "nl": "Tesla supercharger (Destination (Een Type 2 met kabel en Tesla-logo) levert een stroom van maximaal 16 A", + "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla) surt com a màxim a 16 A", "da": "Tesla Supercharger (Destination) (A Type 2 med kabel mærket som tesla) yder højst 16 A", - "de": "Tesla Supercharger (Destination) (Typ 2 mit Kabel) liefert maximal 16 A", - "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla) surt com a màxim a 16 A" + "de": "Tesla Supercharger (Destination) (Typ 2 mit Kabel) liefert maximal 16 A" }, "icon": { "path": "./assets/layers/charging_station/Type2_tethered.svg", @@ -3562,9 +3562,9 @@ "then": { "en": "Tesla Supercharger (Destination) (A Type 2 with cable branded as Tesla) outputs at most 32 A", "nl": "Tesla Supercharger (Destination (Een Type 2 met kabel en Tesla-logo) levert een stroom van maximaal 32 A", + "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla) surt com a màxim a 32 A", "da": "Tesla Supercharger (Destination) (A Type 2 med kabel af Tesla-mærket) yder højst 32 A", - "de": "Tesla Supercharger (Destination) (Typ 2 mit Kabel von Tesla) liefert maximal 32 A", - "ca": "Supercarregador Tesla (destinació) (Un Tipus 2 amb un cable de marca Tesla) surt com a màxim a 32 A" + "de": "Tesla Supercharger (Destination) (Typ 2 mit Kabel von Tesla) liefert maximal 32 A" }, "icon": { "path": "./assets/layers/charging_station/Type2_tethered.svg", @@ -4480,8 +4480,8 @@ "render": { "en": "More info on {website}", "nl": "Meer informatie op {website}", - "de": "Weitere Informationen unter {website}", - "ca": "Més info a {website}" + "ca": "Més info a {website}", + "de": "Weitere Informationen unter {website}" }, "freeform": { "key": "website", @@ -4639,7 +4639,7 @@ "then": { "en": "No additional parking cost while charging", "nl": "Geen extra parkeerkost tijdens het opladen", - "ca": "No hi ha costos d'aparcament addicionals mentre es carrega", + "ca": "No cal pagar una taxa addicional mentres es carrega", "da": "Ingen ekstra parkeringsomkostninger under opladning", "de": "Keine zusätzlichen Parkkosten während des Ladens", "es": "No hay costes de aparcamiento adicionales mientras se carga", @@ -4675,6 +4675,7 @@ "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}", + "ca": "

Preguntes tècniques

Les preguntes següents són molt tècniques. No dubteu a ignorar-les
{questions(technical)}", "de": "

Technische Frage

Die nächsten Fragen sind sehr technisch. Du kannst diese auch überspringen.
{questions(technical)}" } } diff --git a/assets/layers/ticket_machine/ticket_machine.json b/assets/layers/ticket_machine/ticket_machine.json index 2d4abc356..de507975e 100644 --- a/assets/layers/ticket_machine/ticket_machine.json +++ b/assets/layers/ticket_machine/ticket_machine.json @@ -3,12 +3,14 @@ "name": { "en": "Ticket Machines", "de": "Fahrkartenautomaten", - "nl": "Kaartautomaten" + "nl": "Kaartautomaten", + "ca": "Màquines de bitllets" }, "description": { "en": "Find ticket machines for public transport tickets", "de": "Fahrkartenautomaten für ÖPNV-Tickets finden", - "nl": "Zoek kaartautomaten voor het openbaar vervoer" + "nl": "Zoek kaartautomaten voor het openbaar vervoer", + "ca": "Troba màquines de bitllets per a bitllets de transport públic" }, "source": { "osmTags": { @@ -23,7 +25,8 @@ "render": { "en": "Ticket Machine", "de": "Fahrkartenautomat", - "nl": "Kaartautomaat" + "nl": "Kaartautomaat", + "ca": "Màquina de bitllets" } }, "tagRenderings": [ @@ -40,7 +43,8 @@ "render": { "en": "This ticket machine is operated by {operator}", "de": "Dieser Fahrkartenautomat wird betrieben von {operator}", - "nl": "Deze kaartautomaat wordt beheerd door {operator}" + "nl": "Deze kaartautomaat wordt beheerd door {operator}", + "ca": "{operator} opera aquesta màquina de bitllets" }, "freeform": { "key": "operator", @@ -48,7 +52,8 @@ "placeholder": { "en": "Name of the operator", "de": "Name des Betreibers", - "nl": "Naam van de beheerder" + "nl": "Naam van de beheerder", + "ca": "Nom de l'operador" } }, "mappings": [ @@ -57,7 +62,8 @@ "then": { "en": "Dutch Railways (NS)", "nl": "Nederlandse Spoorwegen (NS)", - "de": "Niederländische Eisenbahn (NS)" + "de": "Niederländische Eisenbahn (NS)", + "ca": "Ferrocarrils holandesos (NS)" }, "addExtraTags": [ "operator:wikidata=Q23076" diff --git a/assets/layers/ticket_validator/ticket_validator.json b/assets/layers/ticket_validator/ticket_validator.json index 351d65b5b..b0b93552a 100644 --- a/assets/layers/ticket_validator/ticket_validator.json +++ b/assets/layers/ticket_validator/ticket_validator.json @@ -2,11 +2,13 @@ "id": "ticket_validator", "name": { "en": "Ticket Validators", - "de": "Fahrkartenentwerter" + "de": "Fahrkartenentwerter", + "ca": "Validadors de bitllets" }, "description": { "en": "Find ticket validators to validate public transport tickets", - "de": "Finden Sie Fahrkartenentwerter, um Tickets für den öffentlichen Nahverkehr zu entwerten" + "de": "Finden Sie Fahrkartenentwerter, um Tickets für den öffentlichen Nahverkehr zu entwerten", + "ca": "Trobeu validadors de bitllets per validar bitllets de transport públic" }, "source": { "osmTags": "amenity=ticket_validator" @@ -15,7 +17,8 @@ "title": { "render": { "en": "Ticket Validator", - "de": "Fahrkartenentwerter" + "de": "Fahrkartenentwerter", + "ca": "Validador de bitllets" } }, "tagRenderings": [ @@ -26,14 +29,16 @@ "condition": "barrier~*", "render": { "en": "This ticket validator is part of a barrier of type {barrier}", - "de": "Dieser Fahrkartenentwerter ist Teil einer Zugangsbarriere vom Typ {barrier}" + "de": "Dieser Fahrkartenentwerter ist Teil einer Zugangsbarriere vom Typ {barrier}", + "ca": "Aquest validador de bitllets forma part d'una barrera de tipus {barrier}" }, "mappings": [ { "if": "barrier=gate", "then": { "en": "This ticket validator is part of a gate", - "de": "Dieser Fahrscheinentwerter ist Teil einer Zugangsbarriere" + "de": "Dieser Fahrscheinentwerter ist Teil einer Zugangsbarriere", + "ca": "Aquest validador de bitllets forma part d'una porta" } } ] @@ -42,11 +47,13 @@ "id": "validator-operator", "question": { "en": "Who is the operator of this ticket validator?", - "de": "Wer ist der Betreiber dieses Fahrkartenentwerters?" + "de": "Wer ist der Betreiber dieses Fahrkartenentwerters?", + "ca": "Qui és l'operador d'aquest validador de bitllets?" }, "render": { "en": "This ticket validator is operated by {operator}", - "de": "Dieser Fahrkartenentwerter wird betrieben von {operator}" + "de": "Dieser Fahrkartenentwerter wird betrieben von {operator}", + "ca": "{operator} opera aquest validador de bitllets" }, "freeform": { "key": "operator", @@ -54,7 +61,8 @@ "placeholder": { "en": "Name of the operator", "de": "Name des Betreibers", - "nl": "Naam van de beheerder" + "nl": "Naam van de beheerder", + "ca": "Nom de l'operador" } }, "mappings": [ @@ -63,7 +71,8 @@ "then": { "en": "Dutch Railways (NS)", "nl": "Nederlandse Spoorwegen (NS)", - "de": "Niederländische Eisenbahn (NS)" + "de": "Niederländische Eisenbahn (NS)", + "ca": "Ferrocarrils holandesos (NS)" }, "addExtraTags": [ "operator:wikidata=Q23076" @@ -81,7 +90,8 @@ "ifnot": "payment:OV-Chipkaart=no", "then": { "en": "This ticket validator accepts OV-Chipkaart", - "de": "Dieser Fahrkartenentwerter akzeptiert die OV-Chipkaart" + "de": "Dieser Fahrkartenentwerter akzeptiert die OV-Chipkaart", + "ca": "Aquest validador de bitllets accepta OV-Chipkaart" }, "hideInAnswer": true }, @@ -90,7 +100,8 @@ "ifnot": "payment:ov-chipkaart=no", "then": { "en": "This ticket validator accepts OV-Chipkaart", - "de": "Dieser Ticketentwerter akzeptiert die OV-Chipkaart" + "de": "Dieser Ticketentwerter akzeptiert die OV-Chipkaart", + "ca": "Aquest validador de bitllets accepta OV-Chipkaart" }, "hideInAnswer": "_country!=nl" } @@ -105,11 +116,13 @@ ], "title": { "en": "a ticket validator", - "de": "einen Fahrkartenentwerter" + "de": "einen Fahrkartenentwerter", + "ca": "un validador de bitllets" }, "description": { "en": "A ticket validator to validate a public transport ticket. This can be either a digital reader, reading a card or ticket, or a machine stamping or punching a ticket.", - "de": "Ein Fahrkartenentwerter zur Entwertung einer Fahrkarte für öffentliche Verkehrsmittel. Dabei kann es sich entweder um ein digitales Lesegerät handeln, das eine Karte oder einen Fahrschein liest, oder um eine Maschine, die einen Fahrschein stempelt oder locht." + "de": "Ein Fahrkartenentwerter zur Entwertung einer Fahrkarte für öffentliche Verkehrsmittel. Dabei kann es sich entweder um ein digitales Lesegerät handeln, das eine Karte oder einen Fahrschein liest, oder um eine Maschine, die einen Fahrschein stempelt oder locht.", + "ca": "Un validador de bitllets per validar un bitllet de transport públic. Pot ser un lector digital, llegir una targeta o un bitllet, o una màquina que estampa o perfora un bitllet." } } ], diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 2432bd198..4870dc58b 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -68,7 +68,8 @@ "fr": "Toilettes avec au moins un WC accessible aux personnes à mobilité réduite", "nl": "Deze toiletten hebben op zijn minst één rolstoeltoegankelijke WC", "it": "Servizi igienici che hanno almeno una toilette accessibile a persone in sedia a rotelle", - "da": "Et toilet, der har mindst et kørestolsvenligt toilet" + "da": "Et toilet, der har mindst et kørestolsvenligt toilet", + "ca": "Un lavabo que tingui almenys un lavabo accessible per a cadira de rodes" } } ], @@ -167,7 +168,8 @@ "it": "Accesso pubblico", "ru": "Свободный доступ", "es": "De acceso público", - "da": "Offentlig adgang" + "da": "Offentlig adgang", + "ca": "Accés públic" }, "hideInAnswer": true } @@ -245,7 +247,8 @@ "placeholder": { "en": "e.g. 0.50 EUR", "nl": "bijv. 0.50 EUR", - "de": "z.B. 0.50 EUR" + "de": "z.B. 0.50 EUR", + "ca": "p. ex. 0.50 eur" } }, "id": "toilet-charge" @@ -265,7 +268,8 @@ "nl": "Wanneer zijn deze toiletten open?", "de": "Wann sind diese Toiletten geöffnet?", "fr": "Quand ces toilettes sont-elles ouvertes ?", - "da": "Hvornår åbnes disse toiletter?" + "da": "Hvornår åbnes disse toiletter?", + "ca": "Quan obrin aquests lavabos?" } } }, @@ -341,7 +345,8 @@ "nl": "De deur naar de rolstoeltoegankelijke toilet is {canonical(door:width)} wide", "fr": "La porte des toilettes accessibles aux fauteuils roulants a une large de {canonical(door:width)}", "de": "Die Tür zur rollstuhlgerechten Toilette ist {canonical(door:width)} breit", - "da": "Døren til det kørestolsvenlige toilet er {canonical(door:width)} bred" + "da": "Døren til det kørestolsvenlige toilet er {canonical(door:width)} bred", + "ca": "La porta del vàter accessible amb cadira de rodes té {canonical(door:width)} d'ample" }, "freeform": { "key": "door:width", @@ -461,7 +466,8 @@ "nl": "Waar bevindt de luiertafel zich?", "it": "Dove si trova il fasciatoio?", "es": "¿Dónde está el cambiador?", - "da": "Hvor er puslebordet placeret?" + "da": "Hvor er puslebordet placeret?", + "ca": "On està el canviador?" }, "render": { "en": "The changing table is located at {changing_table:location}", @@ -470,7 +476,8 @@ "nl": "De luiertafel bevindt zich in {changing_table:location}", "it": "Il fasciatoio si trova presso {changing_table:location}", "es": "El cambiador está en {changing_table:location}", - "da": "Puslebordet er placeret på {changing_table:location}" + "da": "Puslebordet er placeret på {changing_table:location}", + "ca": "El cambiador està a {changing_table:location}" }, "condition": "changing_table=yes", "freeform": { @@ -485,7 +492,8 @@ "fr": "La table à langer est dans les toilettes pour femmes. ", "nl": "De luiertafel bevindt zich in de vrouwentoiletten ", "it": "Il fasciatoio è nei servizi igienici femminili. ", - "da": "Puslebordet er på toilettet til kvinder. " + "da": "Puslebordet er på toilettet til kvinder. ", + "ca": "El canviador està al lavabo per a dones. " }, "if": "changing_table:location=female_toilet" }, @@ -495,7 +503,8 @@ "de": "Der Wickeltisch befindet sich in der Herrentoilette. ", "fr": "La table à langer est dans les toilettes pour hommes. ", "nl": "De luiertafel bevindt zich in de herentoiletten ", - "it": "Il fasciatoio è nei servizi igienici maschili. " + "it": "Il fasciatoio è nei servizi igienici maschili. ", + "ca": "El canviador està al lavabo per a homes. " }, "if": "changing_table:location=male_toilet" }, @@ -507,7 +516,8 @@ "fr": "La table à langer est dans les toilettes pour personnes à mobilité réduite. ", "nl": "De luiertafel bevindt zich in de rolstoeltoegankelijke toilet ", "it": "Il fasciatoio è nei servizi igienici per persone in sedia a rotelle. ", - "da": "Puslebordet er på toilettet for kørestolsbrugere. " + "da": "Puslebordet er på toilettet for kørestolsbrugere. ", + "ca": "El canviador està al lavabo per a usuaris de cadira de rodes. " } }, { @@ -519,7 +529,8 @@ "nl": "De luiertafel bevindt zich in een daartoe voorziene kamer ", "it": "Il fasciatoio è in una stanza dedicata. ", "es": "El cambiador está en una habitación dedicada ", - "da": "Vuggestuen står i et særligt rum. " + "da": "Vuggestuen står i et særligt rum. ", + "ca": "El canviador està en una habitació dedicada. " } } ], @@ -620,7 +631,8 @@ "de": "Barrierefrei", "es": "Accesible con sillas de ruedas", "fr": "Accessible aux fauteuils roulants", - "da": "Tilgængelig for kørestolsbrugere" + "da": "Tilgængelig for kørestolsbrugere", + "ca": "Accessible amb cadira de rodes" }, "osmTags": { "or": [ @@ -641,7 +653,8 @@ "de": "Mit Wickeltisch", "es": "Tiene un cambiador", "fr": "A une table à langer", - "da": "Har et puslebord" + "da": "Har et puslebord", + "ca": "Té un canviador" }, "osmTags": "changing_table=yes" } @@ -706,7 +719,8 @@ "de": "Eine Ebene mit (öffentlichen) Toiletten", "es": "Una capa que muestra baños (públicos)", "fr": "Un calque montrant les toilettes (publiques)", - "da": "Et lag, der viser (offentlige) toiletter" + "da": "Et lag, der viser (offentlige) toiletter", + "ca": "Una capa que mostra banys (publics)" }, "units": [ { @@ -725,7 +739,8 @@ "fr": "mètre", "de": "Meter", "da": "meter", - "pa_PK": "میٹر" + "pa_PK": "میٹر", + "ca": "metre" } }, { @@ -740,7 +755,8 @@ "fr": "centimètre", "de": "Zentimeter", "da": "centimeter", - "pa_PK": "سینٹیمیٹر" + "pa_PK": "سینٹیمیٹر", + "ca": "centimetre" } } ] diff --git a/assets/layers/toilet_at_amenity/toilet_at_amenity.json b/assets/layers/toilet_at_amenity/toilet_at_amenity.json index bb99fab71..ba1664fcd 100644 --- a/assets/layers/toilet_at_amenity/toilet_at_amenity.json +++ b/assets/layers/toilet_at_amenity/toilet_at_amenity.json @@ -44,7 +44,8 @@ "nl": "Toegankelijkheid is {toilets:access}", "it": "L'accesso è {toilets:access}", "es": "El acceso es {toilets:access}", - "da": "Adgang er {toilets:access}" + "da": "Adgang er {toilets:access}", + "ca": "L'accés és {toilets:access}" }, "freeform": { "key": "toilets:access", @@ -63,7 +64,8 @@ "it": "Accesso pubblico", "ru": "Свободный доступ", "es": "Acceso públic", - "da": "Offentlig adgang" + "da": "Offentlig adgang", + "ca": "Accés públic" } }, { @@ -82,7 +84,8 @@ "en": "Not accessible, even for customers of the amenity", "de": "Nicht zugänglich, auch nicht für Kunden der Einrichtung", "nl": "Niet toegankelijk, ook niet voor klanten van de voorziening", - "fr": "Non accessibles, même pour les clients du lieu" + "fr": "Non accessibles, même pour les clients du lieu", + "ca": "No accessible, inclús per als clients de la instal·lació" } }, { @@ -94,7 +97,8 @@ "nl": "Toegankelijk na het vragen van de sleutel", "it": "Accessibile, ma occorre chiedere una chiave per accedere", "es": "Accesible, pero hay que pedir la llave para entrar", - "da": "Tilgængelig, men man skal bede om en nøgle for at komme ind" + "da": "Tilgængelig, men man skal bede om en nøgle for at komme ind", + "ca": "Accessible, però hi ha que demanar la clau per a entrar" } }, { @@ -107,7 +111,8 @@ "it": "Accesso pubblico", "ru": "Свободный доступ", "es": "De acceso público", - "da": "Offentlig adgang" + "da": "Offentlig adgang", + "ca": "Accés públic" }, "hideInAnswer": true } @@ -164,7 +169,8 @@ "it": "Quanto costa l'accesso a questi servizi igienici?", "ru": "Сколько стоит посещение туалета?", "es": "¿Cuánto hay que pagar para estos baños?", - "da": "Hvor meget skal man betale for disse toiletter?" + "da": "Hvor meget skal man betale for disse toiletter?", + "ca": "Quant s'ha de pagar per aquests lavabos?" }, "render": { "en": "The fee is {toilets:charge}", @@ -174,7 +180,8 @@ "it": "La tariffa è {toilets:charge}", "ru": "Стоимость {toilets:charge}", "es": "La tasa es {toilets:charge}", - "da": "Gebyret er {toilets:charge}" + "da": "Gebyret er {toilets:charge}", + "ca": "La taxa és {toilets:charge}" }, "condition": "toilets:fee=yes", "freeform": { @@ -298,7 +305,8 @@ "de": "Barrierefrei", "es": "Accesible con sillas de ruedas", "fr": "Accessible aux fauteuils roulants", - "da": "Tilgængelig for kørestolsbrugere" + "da": "Tilgængelig for kørestolsbrugere", + "ca": "Accessible amb cadira de rodes" }, "osmTags": { "or": [ @@ -426,7 +434,8 @@ "description": { "en": "A layer showing (public) toilets located at different places.", "de": "Eine Ebene mit (öffentlichen) Toiletten an verschiedenen Orten.", - "nl": "Een laag die (publiek toegankelijke) toiletten toont in verschillende andere voorzieningen." + "nl": "Een laag die (publiek toegankelijke) toiletten toont in verschillende andere voorzieningen.", + "ca": "Una capa que mostra banys (públics) ubicats en diferents llocs" }, "units": [ { diff --git a/assets/layers/transit_routes/transit_routes.json b/assets/layers/transit_routes/transit_routes.json index 358d50a15..c24b25497 100644 --- a/assets/layers/transit_routes/transit_routes.json +++ b/assets/layers/transit_routes/transit_routes.json @@ -157,7 +157,8 @@ "en": "What is the colour for this bus line?", "de": "Welche Farbe hat diese Buslinie?", "nl": "Wat is de kleur van deze buslijn?", - "da": "Hvad er farven på denne buslinje?" + "da": "Hvad er farven på denne buslinje?", + "ca": "Quin és el color d'aquest línia d'autobús?" } }, { @@ -199,7 +200,8 @@ "en": "What company operates this bus line?", "de": "Welches Unternehmen betreibt die Buslinie?", "nl": "Welk bedrijf exploiteert deze buslijn?", - "da": "Hvilket selskab driver denne buslinje?" + "da": "Hvilket selskab driver denne buslinje?", + "ca": "Quina companyia opera aquesta línia d'autobús?" } } ] diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index 744cdc62d..59f8c07cc 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -88,7 +88,8 @@ "fr": "Quelle est l'espèce de cet arbre ?", "da": "Hvilken art er dette træ?", "pt": "Que espécie é esta árvore?", - "pt_BR": "Que espécie é esta árvore?" + "pt_BR": "Que espécie é esta árvore?", + "ca": "Quina espècie és aquest arbre?" }, "render": { "*": "{wikipedia(species:wikidata):max-height: 25rem}" @@ -343,7 +344,8 @@ "fr": "Cet arbre est-il un feuillu ou un résineux ?", "de": "Ist dies ein Laub- oder Nadelbaum?", "es": "¿Es un árbol de hoja ancha o de hoja aguja?", - "pt": "Esta é uma árvore de folhas largas ou acículas?" + "pt": "Esta é uma árvore de folhas largas ou acículas?", + "ca": "És un arbre de fulla ampla o d'agulla?" }, "mappings": [ { @@ -405,7 +407,8 @@ "es": "Permanentemente sin hojas", "id": "Tanpa daun permanen", "da": "Permanent bladløs", - "pt": "permanentemente sem folhas" + "pt": "permanentemente sem folhas", + "ca": "Permanentment sense fulles" }, "hideInAnswer": true, "icon": { @@ -494,7 +497,8 @@ "eo": "Nomo: {name}", "es": "Nombre: {name}", "da": "Navn: {name}", - "pt": "Nome: {name}" + "pt": "Nome: {name}", + "ca": "Nom : {name}" }, "question": { "nl": "Heeft de boom een naam?", @@ -506,7 +510,8 @@ "es": "¿Tiene nombre este árbol?", "id": "Apakah pohon ini memiliki nama?", "da": "Har træet et navn?", - "pt": "A árvore tem nome?" + "pt": "A árvore tem nome?", + "ca": "Té nom aquest arbre?" }, "freeform": { "key": "name", @@ -529,10 +534,11 @@ "fr": "L'arbre n'a pas de nom.", "ru": "У этого дерева нет названия.", "de": "Der Baum hat keinen Namen.", - "es": "No identificas la especie.", + "es": "El árbol no tiene nombre.", "id": "Pohon ini tidak memiliki nama.", "da": "Træet har ikke et navn.", - "pt": "A árvore não tem nome." + "pt": "A árvore não tem nome.", + "ca": "L'arbre no té nom." } } ], @@ -556,7 +562,8 @@ "es": "¿Este árbol es patrimonio registrado?", "id": "Apakah pohon ini merupakan warisan yang terdaftar?", "da": "Er dette træ registreret som kulturarv?", - "pt": "Esta árvore é patrimônio tombado?" + "pt": "Esta árvore é patrimônio tombado?", + "ca": "Aquest arbre és patrimoni registrat?" }, "mappings": [ { @@ -597,7 +604,8 @@ "es": "Registrado como patrimonio por la Dirección de Patrimonio Cultural de Bruselas", "id": "Terdaftar sebagai warisan budaya oleh Direction du Patrimoine culturel Brussels", "da": "Registreret som kulturarv af Direction du Patrimoine culturel Bruxelles", - "pt": "Registrado como patrimônio por Direction du Patrimoine culturel Brussels" + "pt": "Registrado como patrimônio por Direction du Patrimoine culturel Brussels", + "ca": "Registrat com a patrimoni per la Direction du Patrimoine culturel Brussel·les" } }, { @@ -655,7 +663,8 @@ "es": "Registrado como patrimonio por un organización diferente", "id": "Terdaftar sebagai warisan oleh organisasi yang berbeda", "da": "Registreret som kulturarv af en anden organisation", - "pt": "Registrado como patrimônio por uma organização diferente" + "pt": "Registrado como patrimônio por uma organização diferente", + "ca": "Registrat com a patrimoni per una organització diferent" }, "hideInAnswer": true } @@ -675,7 +684,8 @@ "ru": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", "fr": "\"\"/ Identifiant Onroerend Erfgoed : {ref:OnroerendErfgoed}", "de": "\"\"/ Onroerend Erfgoed Kennung: {ref:OnroerendErfgoed}", - "da": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}" + "da": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", + "ca": "\"\"/Identifiació Onroerend Erfgoed: {ref:OnroerendErfgoed}" }, "question": { "nl": "Wat is het ID uitgegeven door Onroerend Erfgoed Vlaanderen?", @@ -685,7 +695,8 @@ "de": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?", "es": "¿Cuál es el número de identificación emitido por Onroerend Erfgoed Flandes?", "da": "Hvad er ID udstedt af Onroerend Erfgoed Flanders?", - "pt": "Qual é o ID emitido por Onroerend Erfgoed Flanders?" + "pt": "Qual é o ID emitido por Onroerend Erfgoed Flanders?", + "ca": "Quina és la identificació emesa per Onroerend Erfgoed Flanders?" }, "freeform": { "key": "ref:OnroerendErfgoed", @@ -708,7 +719,8 @@ "fr": "\"\"/ Wikidata : {wikidata}", "de": "\"\"/ Wikidata: {wikidata}", "es": "\"\"/ Wikidata: {wikidata}", - "da": "\"\"/ Wikidata: {wikidata}" + "da": "\"\"/ Wikidata: {wikidata}", + "ca": "\"\"/Wikidata{wikidata}" }, "question": { "nl": "Wat is het Wikidata-ID van deze boom?", @@ -718,7 +730,8 @@ "de": "Was ist das passende Wikidata Element zu diesem Baum?", "es": "¿Cuál es el ID de Wikidata para este árbol?", "da": "Hvad er Wikidata-id'et for dette træ?", - "pt": "Qual é o ID do Wikidata para esta árvore?" + "pt": "Qual é o ID do Wikidata para esta árvore?", + "ca": "Quin és l'identificador de Wikidata d'aquest arbre?" }, "freeform": { "key": "wikidata", diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index 0f15f3756..13d583563 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -4,7 +4,8 @@ "en": "A special layer which is not meant to be shown on a map, but which is used to set user settings", "de": "Eine spezielle Ebene, die nicht für die Darstellung auf einer Karte gedacht ist, sondern für die Festlegung von Benutzereinstellungen verwendet wird", "nl": "Een speciale lag die niet getoond wordt op de kaart, maar die de instellingen van de gebruiker weergeeft", - "pt": "Uma camada especial que não deve ser mostrada em um mapa, mas que é usada para definir as configurações do usuário" + "pt": "Uma camada especial que não deve ser mostrada em um mapa, mas que é usada para definir as configurações do usuário", + "ca": "Una capa especial que no està pensada per mostrar-se en un mapa, però que s'utilitza per configurar la configuració de l'usuari" }, "title": { "render": { @@ -12,7 +13,8 @@ "nl": "Instellingen", "de": "Einstellungen", "fr": "Paramètres", - "da": "Indstillinger" + "da": "Indstillinger", + "ca": "Configuració" } }, "source": "special", @@ -41,7 +43,8 @@ "icon": "./assets/layers/usersettings/translate_disabled.svg", "then": { "en": "The language was set via an URL-parameter and cannot be set by the user.²", - "de": "Die Sprache wurde über einen URL-Parameter gesetzt und kann nicht vom Benutzer eingestellt werden.²" + "de": "Die Sprache wurde über einen URL-Parameter gesetzt und kann nicht vom Benutzer eingestellt werden.²", + "ca": "L'idioma es va establir mitjançant un paràmetre d'URL i l'usuari no pot definir-lo.²" } } ] @@ -58,7 +61,8 @@ "text": { "en": "Open your inbox", "nl": "Ga naar je inbox", - "de": "Deinen Posteingang öffnen" + "de": "Deinen Posteingang öffnen", + "ca": "Obre la teva safata d'entrada" } } } @@ -70,7 +74,8 @@ "type": "link", "text": { "en": "You have {_unreadMessages}
Open your inbox", - "de": "Du hast {_unreadMessages}
Öffne Deinen Posteingang" + "de": "Du hast {_unreadMessages}
Öffne Deinen Posteingang", + "ca": "Tens {_unreadMessages}
Open your inbox" }, "href": "{_backend}/messages/inbox" } @@ -85,7 +90,8 @@ "type": "link", "text": { "en": "Open your settings on OpenStreetMap.org", - "de": "Öffne Deine Einstellungen auf OpenStreetMap.org" + "de": "Öffne Deine Einstellungen auf OpenStreetMap.org", + "ca": "Obriu la vostra configuració a OpenStreetMap.org" }, "href": "{_backend}/account/edit" } @@ -164,32 +170,37 @@ "question": { "en": "Show the raw OpenStreetMap-tags?", "de": "Rohe OpenStreetMap-Tags anzeigen?", - "fr": "Afficher les attributs OpenStreetMap bruts ?" + "fr": "Afficher les attributs OpenStreetMap bruts ?", + "ca": "Mostra les etiquetes d'OpenStreetMap en brut?" }, "questionHint": { "en": "Tags are attributes that every element has. This is the technical data that is stored in the database. You don't need this information to edit with MapComplete, but advanced users might want to use this as reference.", - "de": "Tags sind die Eigenschaften, die jedes Objekt hat. Das sind die technischen Daten, die in der Datenbank gespeichert werden. Du brauchst diese Informationen nicht, um mit MapComplete Änderungen zu machen, aber fortgeschrittenen Nutzer*innen kann es als Referenz dienen." + "de": "Tags sind die Eigenschaften, die jedes Objekt hat. Das sind die technischen Daten, die in der Datenbank gespeichert werden. Du brauchst diese Informationen nicht, um mit MapComplete Änderungen zu machen, aber fortgeschrittenen Nutzer*innen kann es als Referenz dienen.", + "ca": "Les etiquetes són atributs que té cada element. Aquestes són les dades tècniques que s'emmagatzemen a la base de dades. No necessiteu aquesta informació per editar amb MapComplete, però és possible que els usuaris avançats la vulguin fer servir com a referència." }, "mappings": [ { "if": "mapcomplete-show_tags=no", "then": { "en": "Never show the tags.", - "de": "Tags nie anzeigen." + "de": "Tags nie anzeigen.", + "ca": "No mostris mai les etiquetes." } }, { "if": "mapcomplete-show_tags=", "then": { "en": "Show the tags that will be applied once I have made {__userjourney_tagsVisibleAt} changesets", - "de": "Tags anzeigen sobald ich {__userjourney_tagsVisibleAt} Changesets erstellt habe" + "de": "Tags anzeigen sobald ich {__userjourney_tagsVisibleAt} Changesets erstellt habe", + "ca": "Mostra les etiquetes que s'aplicaran un cop hagi fet {__userjourney_tagsVisibleAt} conjunts de canvis" } }, { "if": "mapcomplete-show_tags=yes", "then": { "en": "Show the tags that will be applied when making a change", - "de": "Tags anzeigen, die bei der Änderung hinzugefügt werden" + "de": "Tags anzeigen, die bei der Änderung hinzugefügt werden", + "ca": "Mostra les etiquetes que s'aplicaran en fer un canvi" } }, { @@ -208,7 +219,8 @@ "en": "Should questions for unknown data fields appear one-by-one or together?", "de": "Sollen Fragen für unbekannte Datenfelder einzeln oder zusammen angezeigt werden?", "fr": "Est-ce que les questions pour les champs sans donnée doivent apparaître une à une ou toutes ensembles ?", - "pt": "As perguntas para campos de dados desconhecidos devem aparecer uma a uma ou juntas?" + "pt": "As perguntas para campos de dados desconhecidos devem aparecer uma a uma ou juntas?", + "ca": "Les preguntes amb camps de dades desconeguts haurien d'aparèixer una per una o juntes?" }, "mappings": [ { @@ -238,7 +250,8 @@ "id": "fixate-north", "question": { "en": "Should north always be up?", - "de": "Soll Norden immer oben sein?" + "de": "Soll Norden immer oben sein?", + "ca": "El nord hauria d'estar sempre amunt?" }, "mappings": [ { @@ -256,7 +269,8 @@ "then": { "en": "Always keep north pointing up", "de": "Norden immer nach oben zeigen lassen", - "fr": "Toujours garder le nord en haut" + "fr": "Toujours garder le nord en haut", + "ca": "Mantingueu sempre el nord apuntant cap amunt" } } ] @@ -271,13 +285,15 @@ "text": { "en": "Download the private key for your Mangrove Account", "de": "Laden Sie den privaten Schlüssel für Ihr Mangrove-Konto herunter", - "da": "Hent den private nøgle til din Mangrove-konto" + "da": "Hent den private nøgle til din Mangrove-konto", + "ca": "Baixeu la clau privada del vostre compte de Mangrove" } }, "after": { "en": "Anyone possessing this file can make reviews with your identity", "de": "Jeder, der diese Datei besitzt, kann mit Ihrer Identität Rezensionen vornehmen", - "da": "Enhver, der har denne fil, kan lave ændringer med din identitet" + "da": "Enhver, der har denne fil, kan lave ændringer med din identitet", + "ca": "Qualsevol persona que tingui aquest fitxer pot fer ressenyes amb la vostra identitat" } } }, @@ -447,7 +463,8 @@ "then": { "en": "We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", "de": "Wir haben einen Link gefunden, der aussieht wie ein Mastodon-Konto, aber nicht verifiziert ist. Bearbeiten Sie Ihre Profilbeschreibung und fügen Sie dort Folgendes ein: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", - "nl": "Je profielbeschrijving bevat een link die vermoedelijk naar je Mastodon gaat, maar deze link is niet verifieerdbaar voor Mastodon.Pas je profielbeschrijving aan en plaats er de volgende code: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>" + "nl": "Je profielbeschrijving bevat een link die vermoedelijk naar je Mastodon gaat, maar deze link is niet verifieerdbaar voor Mastodon.Pas je profielbeschrijving aan en plaats er de volgende code: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>", + "ca": "Hem trobat un enllaç al que sembla ser un compte de mastodon, però no està verificat. Editeu la descripció del vostre perfil i col·loqueu-hi el següent: <a href=\"{_mastodon_candidate}\" rel=\"me\">Mastodon</a>" }, "icon": "invalid" } @@ -541,7 +558,8 @@ "de": "Keine Debug-Informationen anzeigen", "cs": "Nezobrazovat informace o ladění", "pt": "Não mostrar informações de depuração", - "da": "Vis ikke debug-informationer" + "da": "Vis ikke debug-informationer", + "ca": "No mostris informació de depuració" }, "hideInAnswer": true } diff --git a/assets/layers/vending_machine/vending_machine.json b/assets/layers/vending_machine/vending_machine.json index 3d305dae9..f3bdcef84 100644 --- a/assets/layers/vending_machine/vending_machine.json +++ b/assets/layers/vending_machine/vending_machine.json @@ -4,13 +4,15 @@ "en": "Vending Machines", "nl": "Verkoopautomaten", "de": "Verkaufsautomaten", - "fr": "Distributeurs" + "fr": "Distributeurs", + "ca": "Màquines expenedores" }, "description": { "en": "Layer showing vending machines", "nl": "Laag met verkoopautomaten", "de": "Ebene mit Verkaufsautomaten", - "fr": "Couche affichant les distributeurs" + "fr": "Couche affichant les distributeurs", + "ca": "Capa que mostra màquines expenedores" }, "source": { "osmTags": { @@ -25,7 +27,8 @@ "en": "Vending machine", "nl": "Verkoopautomaat", "de": "Verkaufsautomat", - "fr": "Distributeur" + "fr": "Distributeur", + "ca": "Màquina expenedora" }, "mappings": [ { @@ -34,7 +37,8 @@ "en": "Vending machine {name}", "nl": "Verkoopautomaat {name}", "de": "Verkaufsautomat {name}", - "fr": "Distributeur {name}" + "fr": "Distributeur {name}", + "ca": "Maquina expenedora {name}" } }, { @@ -43,7 +47,8 @@ "en": "Vending machine {brand}", "nl": "Verkoopautomaat {brand}", "de": "Verkaufsautomat {brand}", - "fr": "Distributeur {brand}" + "fr": "Distributeur {brand}", + "ca": "Màquina expenedora {brand}" } } ] @@ -56,7 +61,8 @@ "en": "What does this vending machine sell?", "nl": "Wat verkoopt deze verkoopautomaat?", "de": "Was wird in diesem Automaten verkauft?", - "fr": "Que vent ce distributeur ?" + "fr": "Que vent ce distributeur ?", + "ca": "Que ven aquesta màquina expenedora?" }, "freeform": { "key": "vending", @@ -68,7 +74,8 @@ "en": "This vending machine sells {vending}", "nl": "Deze verkoopautomaat verkoopt {vending}", "de": "Dieser Automat verkauft {vending}", - "fr": "Ce distributeur vent {vending}" + "fr": "Ce distributeur vent {vending}", + "ca": "Aquesta màquina expenedora ven {vending}" }, "mappings": [ { @@ -77,7 +84,8 @@ "en": "Drinks are sold", "nl": "Dranken worden verkocht", "de": "Getränke werden verkauft", - "fr": "Vent des boissons" + "fr": "Vent des boissons", + "ca": "Es venen begudes" }, "icon": "./assets/layers/id_presets/temaki-bottles.svg" }, @@ -87,7 +95,8 @@ "en": "Sweets are sold", "nl": "Snoep wordt verkocht", "de": "Süßigkeiten werden verkauft", - "fr": "Vent des confiseries" + "fr": "Vent des confiseries", + "ca": "Es venen llaminadures" }, "icon": "./assets/layers/id_presets/maki-confectionery.svg" }, @@ -97,7 +106,8 @@ "en": "Food is sold", "nl": "Eten wordt verkocht", "de": "Lebensmittel werden verkauft", - "fr": "Vent de la nourriture" + "fr": "Vent de la nourriture", + "ca": "Es ven menjar" }, "icon": "./assets/layers/vending_machine/utensils.svg" }, @@ -107,7 +117,8 @@ "en": "Cigarettes are sold", "nl": "Sigaretten worden verkocht", "de": "Zigaretten werden verkauft", - "fr": "Vent des cigarettes" + "fr": "Vent des cigarettes", + "ca": "Es ven tabaco" }, "icon": "./assets/layers/vending_machine/smoking.svg" }, @@ -117,7 +128,8 @@ "en": "Condoms are sold", "nl": "Condooms worden verkocht", "de": "Kondome werden verkauft", - "fr": "Vent des préservatifs" + "fr": "Vent des préservatifs", + "ca": "Es venen preservatius" } }, { @@ -126,7 +138,8 @@ "en": "Coffee is sold", "nl": "Koffie wordt verkocht", "de": "Kaffee wird verkauft", - "fr": "Vent du café" + "fr": "Vent du café", + "ca": "Es ven cafè" }, "icon": "./assets/layers/vending_machine/mug-saucer.svg" }, @@ -136,7 +149,8 @@ "en": "Drinking water is sold", "nl": "Drinkwater wordt verkocht", "de": "Trinkwasser wird verkauft", - "fr": "Vent de l'eau" + "fr": "Vent de l'eau", + "ca": "Es ven aigua" }, "icon": "./assets/layers/id_presets/temaki-water_bottle.svg" }, @@ -146,7 +160,8 @@ "en": "Newspapers are sold", "nl": "Kranten worden verkocht", "de": "Zeitungen werden verkauft", - "fr": "Vent des journaux" + "fr": "Vent des journaux", + "ca": "Es venen diaris" }, "icon": "./assets/layers/id_presets/fas-newspaper.svg" }, @@ -156,7 +171,8 @@ "en": "Bicycle inner tubes are sold", "nl": "Binnenbanden voor fietsen worden verkocht", "de": "Fahrradschläuche werden verkauft", - "fr": "Vent des chambres à air pour vélo" + "fr": "Vent des chambres à air pour vélo", + "ca": "Es venen càmeres interiors de bicicletes" } }, { @@ -165,7 +181,8 @@ "en": "Milk is sold", "nl": "Melk wordt verkocht", "de": "Milch wird verkauft", - "fr": "Vent du lait" + "fr": "Vent du lait", + "ca": "Es ven llet" }, "icon": "./assets/layers/vending_machine/cow.svg" }, @@ -175,7 +192,8 @@ "en": "Bread is sold", "nl": "Brood wordt verkocht", "de": "Brot wird verkauft", - "fr": "Vent du pain" + "fr": "Vent du pain", + "ca": "Es ven pa" }, "icon": "./assets/layers/id_presets/maki-bakery.svg" }, @@ -185,7 +203,8 @@ "en": "Eggs are sold", "nl": "Eieren worden verkocht", "de": "Eier werden verkauft", - "fr": "Vent des œufs" + "fr": "Vent des œufs", + "ca": "Es venen ous" }, "icon": "./assets/layers/vending_machine/egg.svg" }, @@ -195,7 +214,8 @@ "en": "Cheese is sold", "nl": "Kaas wordt verkocht", "de": "Käse wird verkauft", - "fr": "Vent du fromage" + "fr": "Vent du fromage", + "ca": "Es ven formatge" }, "icon": "./assets/layers/id_presets/fas-cheese.svg" }, @@ -205,7 +225,8 @@ "en": "Honey is sold", "nl": "Honing wordt verkocht", "de": "Honig wird verkauft", - "fr": "Vent du miel" + "fr": "Vent du miel", + "ca": "Es ven mel" }, "icon": "./assets/layers/vending_machine/honey.svg" }, @@ -215,7 +236,8 @@ "en": "Potatoes are sold", "nl": "Aardappelen worden verkocht", "de": "Kartoffeln werden verkauft", - "fr": "Vent des pommes de terre" + "fr": "Vent des pommes de terre", + "ca": "Es venen papes" }, "icon": "./assets/layers/vending_machine/potato.svg" }, @@ -225,7 +247,8 @@ "en": "Flowers are sold", "nl": "Bloemen worden verkocht", "de": "Blumen werden verkauft", - "fr": "Vent des fleurs" + "fr": "Vent des fleurs", + "ca": "Es venen flors" }, "icon": "./assets/layers/id_presets/maki-florist.svg" }, @@ -234,14 +257,16 @@ "then": { "en": "Parking tickets are sold", "nl": "Parkeerkaarten worden verkocht", - "de": "Parkscheine werden verkauft" + "de": "Parkscheine werden verkauft", + "ca": "Es venen tiquets d'aparcament" }, "icon": "./assets/layers/parking_ticket_machine/parking_tickets.svg" }, { "if": "vending=elongated_coin", "then": { - "en": "Pressed pennies are sold" + "en": "Pressed pennies are sold", + "ca": "Es venen cèntims premsats" }, "icon": "./assets/themes/elongated_coin/penny.svg" }, @@ -250,7 +275,8 @@ "then": { "en": "Public transport tickets are sold", "nl": "Openbaar vervoerkaartjes worden verkocht", - "de": "Fahrscheine werden verkauft" + "de": "Fahrscheine werden verkauft", + "ca": "Es venen bitllets de transport públic" }, "icon": "./assets/themes/stations/public_transport_tickets.svg" } @@ -267,7 +293,8 @@ "en": "Who operates this vending machine?", "nl": "Wie beheert deze verkoopautomaat?", "de": "Wer betreibt diesen Verkaufsautomaten?", - "fr": "Qui exploite ce distributeur ?" + "fr": "Qui exploite ce distributeur ?", + "ca": "Qui opera aquesta màquina expenedora?" }, "freeform": { "key": "operator", @@ -275,14 +302,16 @@ "en": "Name of operator", "nl": "Naam van beheerder", "de": "Name des Betreibers", - "fr": "Nom de l'exploitant" + "fr": "Nom de l'exploitant", + "ca": "Nom de l'operadora" } }, "render": { "en": "This vending machine is operated by {operator}", "nl": "Deze verkoopautomaat wordt beheerd door {operator}", "de": "Dieser Verkaufsautomat wird betrieben von {operator}", - "fr": "Ce distributeur est exploité par {operator}" + "fr": "Ce distributeur est exploité par {operator}", + "ca": "{operator} gestiona aquesta màquina expenedora" } }, { @@ -291,7 +320,8 @@ "en": "Is this vending machine indoors?", "nl": "Is deze verkoopautomaat binnen?", "de": "Ist dieser Automat in einem Gebäude untergebracht?", - "fr": "Est-ce que ce distributeur est en intérieur ?" + "fr": "Est-ce que ce distributeur est en intérieur ?", + "ca": "Aquesta màquina expenedora està a l'exterior?" }, "mappings": [ { @@ -300,7 +330,8 @@ "en": "This vending machine is outdoors", "nl": "Deze verkoopautomaat is buiten", "de": "Dieser Automat befindet sich im Freien", - "fr": "Ce distributeur est à l'extérieur" + "fr": "Ce distributeur est à l'extérieur", + "ca": "Aquesta màquina expenedora està a l'exterior" }, "hideInAnswer": true }, @@ -310,7 +341,8 @@ "en": "This vending machine is indoors", "nl": "Deze verkoopautomaat is binnen", "de": "Dieser Verkaufsautomat befindet sich im Innenbereich", - "fr": "Ce distributeur est à l'intérieur" + "fr": "Ce distributeur est à l'intérieur", + "ca": "Aquesta màquina expenedora està a l'interior" } }, { @@ -319,7 +351,8 @@ "en": "This vending machine is outdoors", "nl": "Deze verkoopautomaat is buiten", "de": "Dieser Automat befindet sich im Freien", - "fr": "Ce distributeur est à l'extérieur" + "fr": "Ce distributeur est à l'extérieur", + "ca": "Aquesta màquina expenedora està a l'exterior" } } ] @@ -335,7 +368,8 @@ "en": "a vending machine", "nl": "een verkoopautomaat", "de": "ein Verkaufsautomat", - "fr": "un distributeur" + "fr": "un distributeur", + "ca": "una màquina expenedora" }, "tags": [ "amenity=vending_machine" @@ -580,7 +614,8 @@ "en": "All vending machines", "nl": "Alle verkoopautomaten", "de": "Alle Verkaufsautomaten", - "fr": "Tous les distributeurs" + "fr": "Tous les distributeurs", + "ca": "Totes les màquines expenedores" } }, { @@ -588,7 +623,8 @@ "en": "Sale of drinks", "nl": "Verkoop van dranken", "de": "Verkauf von Getränken", - "fr": "Vente de boissons" + "fr": "Vente de boissons", + "ca": "Venda de begudes" }, "osmTags": "vending~i~.*drinks.*" }, @@ -597,7 +633,8 @@ "en": "Sale of sweets", "nl": "Verkoop van snoep", "de": "Verkauf von Süßigkeiten", - "fr": "Ventre de confiseries" + "fr": "Ventre de confiseries", + "ca": "Venda de llaminadures" }, "osmTags": "vending~i~.*sweets.*" }, @@ -606,7 +643,8 @@ "en": "Sale of food", "nl": "Verkoop van eten", "de": "Verkauf von Lebensmitteln", - "fr": "Ventre de nourriture" + "fr": "Ventre de nourriture", + "ca": "Venda de menjar" }, "osmTags": "vending~i~.*food.*" }, @@ -615,7 +653,8 @@ "en": "Sale of cigarettes", "nl": "Verkoop van sigaretten", "de": "Verkauf von Zigaretten", - "fr": "Vente de cigarettes" + "fr": "Vente de cigarettes", + "ca": "Venda de tabaco" }, "osmTags": "vending~i~.*cigarettes.*" }, @@ -624,7 +663,8 @@ "en": "Sale of condoms", "nl": "Verkoop van condooms", "de": "Verkauf von Kondomen", - "fr": "Vente de préservatifs" + "fr": "Vente de préservatifs", + "ca": "Venda de preservatius" }, "osmTags": "vending~i~.*condoms.*" }, @@ -633,7 +673,8 @@ "en": "Sale of coffee", "nl": "Verkoop van koffie", "de": "Verkauf von Kaffee", - "fr": "Vente de café" + "fr": "Vente de café", + "ca": "Venda de cafè" }, "osmTags": "vending~i~.*coffee.*" }, @@ -642,7 +683,8 @@ "en": "Sale of water", "nl": "Verkoop van water", "de": "Verkauf von Trinkwasser", - "fr": "Vente d'eau" + "fr": "Vente d'eau", + "ca": "Venda d'aigua" }, "osmTags": "vending~i~.*water.*" }, @@ -651,7 +693,8 @@ "en": "Sale of newspapers", "nl": "Verkoop van kranten", "de": "Verkauf von Zeitungen", - "fr": "Vente de journaux" + "fr": "Vente de journaux", + "ca": "Venda de diaris" }, "osmTags": "vending~i~.*newspapers.*" }, @@ -660,7 +703,8 @@ "en": "Sale of bicycle inner tubes", "nl": "Verkoop van fietsbinnenbanden", "de": "Verkauf von Fahrradschläuchen", - "fr": "Vente de chambres à air pour vélo" + "fr": "Vente de chambres à air pour vélo", + "ca": "Venda de càmeres interiors de bicicletes" }, "osmTags": "vending~i~.*bicycle_tube.*" }, @@ -669,7 +713,8 @@ "en": "Sale of milk", "nl": "Verkoop van melk", "de": "Verkauf von Milch", - "fr": "Vente de lait" + "fr": "Vente de lait", + "ca": "Venda de llet" }, "osmTags": "vending~i~.*milk.*" }, @@ -678,7 +723,8 @@ "en": "Sale of bread", "nl": "Verkoop van brood", "de": "Verkauf von Brot", - "fr": "Vente de pain" + "fr": "Vente de pain", + "ca": "Venda de pa" }, "osmTags": "vending~i~.*bread.*" }, @@ -687,7 +733,8 @@ "en": "Sale of eggs", "nl": "Verkoop van eieren", "de": "Verkauf von Eiern", - "fr": "Vente d'œufs" + "fr": "Vente d'œufs", + "ca": "Venda d'ous" }, "osmTags": "vending~i~.*eggs.*" }, @@ -696,7 +743,8 @@ "en": "Sale of cheese", "nl": "Verkoop van kaas", "de": "Verkauf von Käse", - "fr": "Vente de fromage" + "fr": "Vente de fromage", + "ca": "Venda de formatge" }, "osmTags": "vending~i~.*cheese.*" }, @@ -705,7 +753,8 @@ "en": "Sale of honey", "nl": "Verkoop van honing", "de": "Verkauf von Honig", - "fr": "Vente de miel" + "fr": "Vente de miel", + "ca": "Venda de mel" }, "osmTags": "vending~i~.*honey.*" }, @@ -714,7 +763,8 @@ "en": "Sale of potatoes", "nl": "Verkoop van aardappelen", "de": "Verkauf von Kartoffeln", - "fr": "Vente de pommes de terre" + "fr": "Vente de pommes de terre", + "ca": "Venda de patates" }, "osmTags": "vending~i~.*potatoes.*" }, @@ -723,7 +773,8 @@ "en": "Sale of flowers", "nl": "Verkoop van bloemen", "de": "Verkauf von Blumen", - "fr": "Vente de fleurs" + "fr": "Vente de fleurs", + "ca": "Venda de flors" }, "osmTags": "vending~i~.*flowers.*" } diff --git a/assets/layers/veterinary/veterinary.json b/assets/layers/veterinary/veterinary.json index f0006470c..3f28260fd 100644 --- a/assets/layers/veterinary/veterinary.json +++ b/assets/layers/veterinary/veterinary.json @@ -17,7 +17,8 @@ "de": "ein Tierarzt", "es": "un veterinario", "nl": "een dierenarts", - "fr": "un vétérinaire" + "fr": "un vétérinaire", + "ca": "un veterinari" }, "tags": [ "amenity=veterinary", @@ -29,7 +30,8 @@ "de": "ein Tierarzt, der Hunde behandelt", "es": "un veterinario, que trata a perros", "nl": "een dierenarts die honden verzorgt", - "fr": "un vétérinaire, qui soigne les chiens" + "fr": "un vétérinaire, qui soigne les chiens", + "ca": "un veterinari, que tracta gossos" } } ], @@ -49,7 +51,8 @@ "de": "Tierarzt", "id": "Dokter hewan", "es": "Veterinario", - "nl": "Dierenarts" + "nl": "Dierenarts", + "ca": "Veterinari" }, "mappings": [ { @@ -101,7 +104,8 @@ "de": "Wie lautet der Name dieses Tierarztes?", "es": "¿Cual es el nombre de este veterinario?", "nl": "Wat is de naam van deze dierenartspraktijk?", - "fr": "Quel est le nom de ce vétérinaire ?" + "fr": "Quel est le nom de ce vétérinaire ?", + "ca": "Qui és el nom d'aquest veterinari?" }, "render": { "en": "The name of this veterinarian is {name}", @@ -109,7 +113,8 @@ "es": "El nombre de este veterinario es {name}", "nl": "Deze dierenartspraktijk heet {name}", "da": "Navnet på denne dyrlæge er {name}", - "fr": "Le nom de ce vétérinaire est {name}" + "fr": "Le nom de ce vétérinaire est {name}", + "ca": "El nom d'aquest veterinari és {name}" }, "freeform": { "key": "name" diff --git a/assets/layers/viewpoint/viewpoint.json b/assets/layers/viewpoint/viewpoint.json index c7b0644fa..ebff0b906 100644 --- a/assets/layers/viewpoint/viewpoint.json +++ b/assets/layers/viewpoint/viewpoint.json @@ -18,8 +18,9 @@ "de": "Ein schöner Aussichtspunkt oder eine schöne Aussicht. Ideal zum Hinzufügen eines Bildes, wenn keine andere Kategorie passt", "fr": "Un beau point de vue ou une belle vue. Idéal pour ajouter une image si aucune autre catégorie ne convient", "it": "Un punto panoramico che offre una bella vista. L'ideale è aggiungere un'immagine, se nessun'altra categoria è appropriata", - "es": "Un buen punto de vista o una buena vista. Ideal para añadir una imagen si no encaja en ninguna otra categoría", - "da": "Et dejligt synspunkt eller flot udsigt. Ideel til at tilføje et billede, hvis ingen anden kategori passer" + "es": "Un buen mirador o una buena vista. Ideal para añadir una imagen si no encaja en ninguna otra categoría", + "da": "Et dejligt synspunkt eller flot udsigt. Ideel til at tilføje et billede, hvis ingen anden kategori passer", + "ca": "Un bon mirador o una bonica vista. Ideal per afegir una imatge si no hi caben cap altra categoria" }, "source": { "osmTags": "tourism=viewpoint" @@ -69,7 +70,8 @@ "it": "Vuoi aggiungere una descrizione?", "id": "Apakah Anda ingin menambahkan deskripsi?", "es": "¿Quieres añadir una descripción?", - "da": "Ønsker du at tilføje en beskrivelse?" + "da": "Ønsker du at tilføje en beskrivelse?", + "ca": "Vols afegir una descripció?" }, "render": "{description}", "freeform": { diff --git a/assets/layers/visitor_information_centre/visitor_information_centre.json b/assets/layers/visitor_information_centre/visitor_information_centre.json index e45f3927d..8347acf95 100644 --- a/assets/layers/visitor_information_centre/visitor_information_centre.json +++ b/assets/layers/visitor_information_centre/visitor_information_centre.json @@ -6,7 +6,8 @@ "de": "Besucherinformationszentrum", "id": "Pusat Informasi Pengunjung", "es": "Centro de Información al Visitante", - "da": "Informationscenter for besøgende" + "da": "Informationscenter for besøgende", + "ca": "Centre d'Informació al Visitant" }, "minzoom": 12, "source": { @@ -70,7 +71,8 @@ "en": "A visitor center offers information about a specific attraction or place of interest where it is located.", "nl": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.", "de": "Ein Besucherzentrum bietet Informationen über eine bestimmte Attraktion oder Sehenswürdigkeit, an der es sich befindet.", - "es": "Un centro de visitantes ofrece información sobre una atracción específica o lugar de interese donde se sitúa." + "es": "Un centro de visitantes ofrece información sobre una atracción específica o lugar de interese donde se sitúa.", + "ca": "Un centre de visitants ofereix informació sobre una atracció o lloc d'interès específic on es troba." }, "tagRenderings": [], "presets": [], diff --git a/assets/layers/waste_basket/waste_basket.json b/assets/layers/waste_basket/waste_basket.json index 9088fbb2c..7a86453de 100644 --- a/assets/layers/waste_basket/waste_basket.json +++ b/assets/layers/waste_basket/waste_basket.json @@ -219,7 +219,8 @@ "it": "Questo cestino non ha un distributore di sacchetti per escrementi dei cani", "id": "Keranjang sampah ini tidak memiliki dispenser untuk kantong kotoran (anjing)", "da": "Denne affaldskurv har ikke en dispenser til poser til (hunde)ekskrementer", - "fr": "Cette poubelle n'a pas de distributeur de sac pour ramasser les déjections canines" + "fr": "Cette poubelle n'a pas de distributeur de sac pour ramasser les déjections canines", + "ca": "Aquesta paperera no té un dispensador per a bosses d'excrements (gossos)" }, "hideInAnswer": true } diff --git a/assets/layers/waste_disposal/waste_disposal.json b/assets/layers/waste_disposal/waste_disposal.json index cdc6324a5..b380e7aba 100644 --- a/assets/layers/waste_disposal/waste_disposal.json +++ b/assets/layers/waste_disposal/waste_disposal.json @@ -16,7 +16,8 @@ "de": "Entsorgungsbehälter, mittlerer bis großer Behälter zur Entsorgung von (Haushalts-)Abfällen", "it": "Cestino per lo smaltimento dei rifiuti, contenitore di dimensioni medio grandi per lo smaltimento dei rifiuti (domestici)", "da": "Affaldsspande, mellemstor til stor skraldespand til bortskaffelse af (husholdnings)affald", - "fr": "Réceptacle destiné à recevoir des déchets (domestiques) enveloppés dans des sacs en plastique" + "fr": "Réceptacle destiné à recevoir des déchets (domestiques) enveloppés dans des sacs en plastique", + "ca": "Contenidor de fem, contenidor mitjà o gran per a l'eliminació de residus (domèstics)" }, "source": { "osmTags": "amenity=waste_disposal" @@ -30,7 +31,8 @@ "de": "Mülltonne", "it": "Smaltimento rifiuti", "id": "Pembuangan Limbah", - "da": "Bortskaffelse af affald" + "da": "Bortskaffelse af affald", + "ca": "Contenidor de fem" } }, "mapRendering": [ diff --git a/assets/layers/windturbine/windturbine.json b/assets/layers/windturbine/windturbine.json index 95d783426..93a751618 100644 --- a/assets/layers/windturbine/windturbine.json +++ b/assets/layers/windturbine/windturbine.json @@ -68,7 +68,8 @@ "hu": "A szélerőmű teljesítménye: {generator:output:electricity}.", "de": "Das Windrad erzeugt {generator:output:electricity} Strom.", "nl": "Deze windturbine genereert {generator:output:electricity}", - "da": "Denne vindmølle har en effekt på {generator:output:electricity}." + "da": "Denne vindmølle har en effekt på {generator:output:electricity}.", + "ca": "La potència de sortida d'aquest aerogenerador és {generator:output:electricity}." }, "question": { "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)", @@ -79,7 +80,8 @@ "hu": "Mekkora a teljesítménye ennek a szélturbinának? (pl. 2.3 MW)", "de": "Wieviel Strom erzeugt das Windrad? (z.B. 2.3 MW)", "nl": "Wat is de output", - "da": "Hvad er effekten af denne vindmølle? (f.eks. 2,3 MW)" + "da": "Hvad er effekten af denne vindmølle? (f.eks. 2,3 MW)", + "ca": "Quina és la potència de sortida d'aquest aerogenerador? (p. ex. 2,3 MW)" }, "freeform": { "key": "generator:output:electricity", @@ -97,7 +99,8 @@ "hu": "Ennek a szélerőműnek az üzemeltetője: {operator}.", "de": "Die Windturbine wird betrieben von {operator}.", "da": "Denne vindmølle drives af {operator}.", - "nl": "Deze windturbine wordt beheerd door {operator}." + "nl": "Deze windturbine wordt beheerd door {operator}.", + "ca": "{operator} gestiona aquest aerogenerador." }, "question": { "en": "Who operates this wind turbine?", @@ -108,7 +111,8 @@ "de": "Wer betreibt das Windrad?", "id": "Siapa yang mengoperasikan turbin angin ini?", "da": "Hvem driver denne vindmølle?", - "nl": "Wie beheert deze windturbine?" + "nl": "Wie beheert deze windturbine?", + "ca": "Qui opera aquest aerogenerador?" }, "freeform": { "key": "operator" @@ -124,7 +128,8 @@ "hu": "A szélerőmű teljes (rotor sugarával együtt számított) magassága: {height} méter.", "de": "Die Gesamthöhe dieses Windrads beträgt (einschließlich Rotorradius) {height} Meter.", "da": "Den samlede højde (inklusive rotorradius) for denne vindmølle er {height} meter.", - "nl": "De totale hoogte (inclusief rotor-radius) van deze windturbine is {height} meter" + "nl": "De totale hoogte (inclusief rotor-radius) van deze windturbine is {height} meter", + "ca": "L'alçada total (inclòs el radi del rotor) d'aquest aerogenerador és de {height} metres." }, "question": { "en": "What is the total height of this wind turbine (including rotor radius), in metres?", @@ -134,7 +139,8 @@ "hu": "Hány méter a szélerőmű teljes magassága (a rotor sugarával együtt)?", "de": "Wie hoch ist das Windrad (inklusive Rotorradius) in Metern?", "da": "Hvad er den samlede højde af denne vindmølle (inklusive rotorradius), i meter?", - "nl": "Wat is de totale hoogte in meter van deze windturbine (inclusief rotor-radius)?" + "nl": "Wat is de totale hoogte in meter van deze windturbine (inclusief rotor-radius)?", + "ca": "Quina és l'alçada total d'aquest aerogenerador (inclòs el radi del rotor), en metres?" }, "freeform": { "key": "height", @@ -181,7 +187,8 @@ "hu": "A szélerőmű üzembe helyezése: {start_date}.", "de": "Das Windrad wurde am {start_date} in Betrieb genommen.", "nl": "Deze windturbine werd op {start_date} in gebruik genomen", - "da": "Denne vindmølle blev sat i drift den/den {start_date}." + "da": "Denne vindmølle blev sat i drift den/den {start_date}.", + "ca": "Aquest aerogenerador va entrar en funcionament el dia {start_date}." }, "question": { "en": "When did this wind turbine go into operation?", @@ -192,7 +199,8 @@ "hu": "Mikor helyezték üzembe ezt a szélerőművet?", "de": "Wann wurde das Windrad in Betrieb genommen?", "nl": "Wanneer werd deze windturbine in gebruik genomen?", - "da": "Hvornår blev denne vindmølle taget i brug?" + "da": "Hvornår blev denne vindmølle taget i brug?", + "ca": "Quan va entrar en funcionament aquest aerogenerador?" }, "freeform": { "key": "start_date", @@ -214,7 +222,8 @@ "question": { "en": "Is there something wrong with how this is mapped, that you weren't able to fix here? (leave a note to OpenStreetMap experts)", "nl": "Is er iets mis met de informatie over deze windturbine dat je hier niet opgelost kreeg? (laat hier een berichtje achter voor OpenStreetMap experts)", - "de": "Gibt es einen Fehler in der Kartierung, den Sie hier nicht beheben konnten? (hinterlassen Sie eine Nachricht an OpenStreetMap-Experten)" + "de": "Gibt es einen Fehler in der Kartierung, den Sie hier nicht beheben konnten? (hinterlassen Sie eine Nachricht an OpenStreetMap-Experten)", + "ca": "Hi ha alguna cosa malament en la manera que està mapejat això que no heu pogut solucionar aquí? (deixeu una nota als experts d'OpenStreetMap)" }, "freeform": { "key": "fixme", diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 6e516d8de..2c38dae55 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -102,7 +102,7 @@ }, "render": { "en": "Change with theme {theme}", - "ca": "Canvi amb el tema {theme}", + "ca": "Canvi amb el tema {theme}", "de": "Geändert mit Thema {theme}", "fr": "Modifié avec le thème {theme}" } @@ -130,7 +130,7 @@ "id": "host", "render": { "en": "Change made with {host}", - "ca": "Canviat amb {host}", + "ca": "Canviat fet amb {host}", "de": "Änderung über {host}", "fr": "Modification faite avec {host}", "nl": "Wijziging gemaakt met {host}" diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 79048f0ad..3d8ccb9d3 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,16 +35,6 @@ "1": { "title": "un mupi" }, - "10": { - "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" - }, "2": { "title": "un mupi sobre la paret" }, @@ -71,6 +61,16 @@ }, "9": { "title": "un tòtem" + }, + "10": { + "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Açò és un tauló d'anunis" }, - "10": { - "then": "Açò és una paret pintada" - }, "2": { "then": "Açò és una columna" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Açò és un tòtem" + }, + "10": { + "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,9 +205,6 @@ "1": { "then": "Tauló d'anuncis" }, - "10": { - "then": "Paret Pintada" - }, "2": { "then": "Mupi" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Tòtem" + }, + "10": { + "then": "Paret Pintada" } } } @@ -312,15 +312,6 @@ "1": { "then": "Mural" }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" - }, "2": { "then": "Pintura" }, @@ -344,6 +335,15 @@ }, "9": { "then": "Relleu" + }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -1820,27 +1820,6 @@ "1": { "question": "Té un connector
Schuko sense pin de terra (CEE7/4 tipus F)
connector" }, - "10": { - "question": "Té un connector
Tipus 2 amb cable (mennekes)
" - }, - "11": { - "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" - }, - "12": { - "question": "Té un connector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" - }, - "14": { - "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" - }, - "15": { - "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" - }, - "16": { - "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" - }, "2": { "question": "Té un connector
endoll de paret Europeu amb un pin de terra (CEE7/4 tipus F)
" }, @@ -1864,6 +1843,27 @@ }, "9": { "question": "Té un connector
CCS Tipus 2 (mennekes)
" + }, + "10": { + "question": "Té un connector
Tipus 2 amb cable (mennekes)
" + }, + "11": { + "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" + }, + "12": { + "question": "Té un connector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" + }, + "14": { + "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" + }, + "15": { + "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" + }, + "16": { + "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" } } } @@ -1919,6 +1919,30 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, + "2": { + "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" + }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipus 1 amb cable (J1772)" + }, + "7": { + "then": "Tipus 1 amb cable (J1772)" + }, + "8": { + "then": "Tipus 1 sense cable (J1772)" + }, + "9": { + "then": "Tipus 1 sense cable (J1772)" + }, "10": { "then": "CSS 1Tipus 1 (també conegut com Tipus 1 combo)" }, @@ -1949,9 +1973,6 @@ "19": { "then": "Tipus 2 amb cable (mennekes)" }, - "2": { - "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" - }, "20": { "then": "CSS Supercarregador Tesla (un tipus2_css de la marca)" }, @@ -1982,32 +2003,11 @@ "29": { "then": "Bosch Active Connect amb 3 pins i cable" }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, "30": { "then": "Bosch Active Connect amb 5 pins i cable" }, "31": { "then": "Bosch Active Connect amb 5 pins i cable" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipus 1 amb cable (J1772)" - }, - "7": { - "then": "Tipus 1 amb cable (J1772)" - }, - "8": { - "then": "Tipus 1 sense cable (J1772)" - }, - "9": { - "then": "Tipus 1 sense cable (J1772)" } }, "question": "Quins tipus de connexions de càrrega estan disponibles aquí?" @@ -2255,7 +2255,7 @@ } }, "questions-technical": { - "render": "

Preguntes tècniques

Les preguntes següents són molt tècniques. No dubteu a ignorar-les
{questions(tehcnical)}" + "render": "

Preguntes tècniques

Les preguntes següents són molt tècniques. No dubteu a ignorar-les
{questions(technical)}" }, "ref": { "question": "Quin és el número de referència d'aquest punt de càrrega?" @@ -2865,9 +2865,6 @@ "1": { "then": "Aquest carril bici està pavimentat" }, - "10": { - "then": "Aquesta via ciclista està feta de gravilla" - }, "2": { "then": "Aquest carril bici està fet d'asfalt" }, @@ -2879,6 +2876,9 @@ }, "9": { "then": "Aquesta via ciclista està feta de grava" + }, + "10": { + "then": "Aquesta via ciclista està feta de gravilla" } }, "question": "De què està feta la superfície d'aquest carrer?", @@ -3997,21 +3997,6 @@ "1": { "then": "Això és una fregiduria" }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" - }, "2": { "then": "Principalment serveix pasta" }, @@ -4035,6 +4020,21 @@ }, "9": { "then": "Aquí es serveixen plats francesos" + }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin menjar es serveix aquí?", @@ -5760,9 +5760,6 @@ "1": { "then": "S'accepten monedes de 2 cèntims" }, - "10": { - "then": "S'accepten monedes de 20 cèntims" - }, "2": { "then": "S'accepten monedes de 5 cèntims" }, @@ -5786,6 +5783,9 @@ }, "9": { "then": "S'accepten monedes de 10 cèntims" + }, + "10": { + "then": "S'accepten monedes de 20 cèntims" } }, "question": "Quines monedes es poden utilitzar per a pagar aquí?" @@ -6171,6 +6171,30 @@ "1": { "question": "Reciclatge de piles" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" + }, "10": { "question": "Reciclatge de vidre" }, @@ -6201,35 +6225,11 @@ "19": { "question": "Reciclatge del rebuig" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, "20": { "question": "Reciclatge de cartutxos d'impressora" }, "21": { "question": "Reciclatge de bicicletes" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" } } }, @@ -6297,6 +6297,30 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" + }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -6327,9 +6351,6 @@ "19": { "then": "Aquí es poden reciclar sabates" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -6344,27 +6365,6 @@ }, "24": { "then": "Aquí es poden reciclar bicicletes" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -7023,12 +7023,6 @@ "1": { "then": "Aquest fanal utilitza LED" }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" - }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -7052,6 +7046,12 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" + }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -7896,27 +7896,6 @@ "1": { "question": "Venda de begudes" }, - "10": { - "question": "Venda de llet" - }, - "11": { - "question": "Venda de pa" - }, - "12": { - "question": "Venda d'ous" - }, - "13": { - "question": "Venda de formatge" - }, - "14": { - "question": "Venda de mel" - }, - "15": { - "question": "Venda de patates" - }, - "16": { - "question": "Venda de flors" - }, "2": { "question": "Venda de llaminadures" }, @@ -7940,6 +7919,27 @@ }, "9": { "question": "Venda de càmeres interiors de bicicletes" + }, + "10": { + "question": "Venda de llet" + }, + "11": { + "question": "Venda de pa" + }, + "12": { + "question": "Venda d'ous" + }, + "13": { + "question": "Venda de formatge" + }, + "14": { + "question": "Venda de mel" + }, + "15": { + "question": "Venda de patates" + }, + "16": { + "question": "Venda de flors" } } } @@ -7980,6 +7980,30 @@ "1": { "then": "Es venen llaminadures" }, + "2": { + "then": "Es ven menjar" + }, + "3": { + "then": "Es ven tabaco" + }, + "4": { + "then": "Es venen preservatius" + }, + "5": { + "then": "Es ven cafè" + }, + "6": { + "then": "Es ven aigua" + }, + "7": { + "then": "Es venen diaris" + }, + "8": { + "then": "Es venen càmeres interiors de bicicletes" + }, + "9": { + "then": "Es ven llet" + }, "10": { "then": "Es ven pa" }, @@ -8006,30 +8030,6 @@ }, "18": { "then": "Es venen bitllets de transport públic" - }, - "2": { - "then": "Es ven menjar" - }, - "3": { - "then": "Es ven tabaco" - }, - "4": { - "then": "Es venen preservatius" - }, - "5": { - "then": "Es ven cafè" - }, - "6": { - "then": "Es ven aigua" - }, - "7": { - "then": "Es venen diaris" - }, - "8": { - "then": "Es venen càmeres interiors de bicicletes" - }, - "9": { - "then": "Es ven llet" } }, "question": "Que ven aquesta màquina expenedora?", @@ -8366,4 +8366,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/es.json b/langs/layers/es.json index 7a0ca49a9..b05d4a0e5 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,16 +35,6 @@ "1": { "title": "un mupi" }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un lletrer" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" - }, "2": { "title": "un mupi sobre la pared" }, @@ -71,6 +61,16 @@ }, "9": { "title": "un tótem" + }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un lletrer" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Esto es un tablón de anuncios" }, - "10": { - "then": "Esto es una pared pintada" - }, "2": { "then": "Esto es una columna" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Esto es un tótem" + }, + "10": { + "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,9 +205,6 @@ "1": { "then": "Tablon de anuncios" }, - "10": { - "then": "Pared Pintada" - }, "2": { "then": "Mupi" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Tótem" + }, + "10": { + "then": "Pared Pintada" } } } @@ -312,15 +312,6 @@ "1": { "then": "Mural" }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, - "12": { - "then": "Tallado en madera" - }, "2": { "then": "Pintura" }, @@ -344,6 +335,15 @@ }, "9": { "then": "Relieve" + }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" + }, + "12": { + "then": "Tallado en madera" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1440,27 +1440,6 @@ "0": { "question": "Todos los conectores" }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" - }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E)
" }, @@ -1484,6 +1463,27 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" + }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1538,6 +1538,30 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" + }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1568,9 +1592,6 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1601,32 +1622,11 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -2021,12 +2021,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2041,6 +2035,12 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2086,9 +2086,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2100,6 +2097,9 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -2724,18 +2724,6 @@ "0": { "then": "Esto es una pizzería" }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" - }, "2": { "then": "Principalmente sirve pasta" }, @@ -2756,6 +2744,18 @@ }, "9": { "then": "Aquí se sirven platos Franceses" + }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué comida se sirve aquí?", @@ -3153,19 +3153,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las nostras de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } - }, "2": { "options": { "0": { @@ -3221,6 +3208,19 @@ "question": "Solo mostrar las notas abiertas" } } + }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las nostras de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } } }, "name": "Notas de OpenStreetMap", @@ -3836,6 +3836,21 @@ "1": { "question": "Reciclaje de baterías" }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "9": { + "question": "Reciclaje de botellas de cristal" + }, "10": { "question": "Reciclaje de cristal" }, @@ -3859,21 +3874,6 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" - }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "9": { - "question": "Reciclaje de botellas de cristal" } } } @@ -3916,6 +3916,24 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" + }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -3939,24 +3957,6 @@ }, "19": { "then": "Aquí se pueden reciclar zapatos" - }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -4260,6 +4260,11 @@ "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}" @@ -4300,12 +4305,6 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" - }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -4326,6 +4325,12 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" + }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -4900,4 +4905,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/themes/ca.json b/langs/themes/ca.json index 309b27932..8a1246094 100644 --- a/langs/themes/ca.json +++ b/langs/themes/ca.json @@ -991,33 +991,6 @@ "onwheels": { "description": "En aquest mapa, es mostren llocs públics accessibles per a cadira de rodes i es poden afegir fàcilment", "layers": { - "19": { - "override": { - "=title": { - "render": "Estadístiques" - } - } - }, - "20": { - "override": { - "+tagRenderings": { - "0": { - "render": { - "special": { - "text": "Importar" - } - } - }, - "1": { - "render": { - "special": { - "message": "Afegiu totes les etiquetes suggerides" - } - } - } - } - } - }, "4": { "override": { "filter": { @@ -1060,6 +1033,33 @@ "override": { "name": "Places d'aparcament per a minusvàlids" } + }, + "19": { + "override": { + "=title": { + "render": "Estadístiques" + } + } + }, + "20": { + "override": { + "+tagRenderings": { + "0": { + "render": { + "special": { + "text": "Importar" + } + } + }, + "1": { + "render": { + "special": { + "message": "Afegiu totes les etiquetes suggerides" + } + } + } + } + } } }, "title": "Sobre rodes" @@ -1220,6 +1220,10 @@ "stations": { "description": "Veure, editar i afegir detalls a una estació de tren", "layers": { + "3": { + "description": "Capa que mostra les estacions de tren", + "name": "Estació de tren" + }, "16": { "description": "Pantalles que mostren els trens que sortiran de l'estació", "name": "Taulers de sortides", @@ -1251,10 +1255,6 @@ "title": { "render": "Tauler de sortides" } - }, - "3": { - "description": "Capa que mostra les estacions de tren", - "name": "Estació de tren" } }, "title": "Estacions de tren" @@ -1350,4 +1350,4 @@ "shortDescription": "Un mapa amb papereres", "title": "Papepera" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9256578de..a343b6377 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mapcomplete", - "version": "0.31.4", + "version": "0.32.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mapcomplete", - "version": "0.31.4", + "version": "0.32.0", "license": "GPL-3.0-or-later", "dependencies": { "@rgossiaux/svelte-headlessui": "^1.0.2", @@ -4955,9 +4955,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "dev": true, "funding": [ { @@ -16988,9 +16988,9 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001538", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", + "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "dev": true }, "canvg": { @@ -23212,4 +23212,4 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } -} \ No newline at end of file +} diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index d51ae560e..b7e29282a 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -1,43 +1,43 @@ -import { DesugaringStep, Each, Fuse, On } from "./Conversion"; -import { LayerConfigJson } from "../Json/LayerConfigJson"; -import LayerConfig from "../LayerConfig"; -import { Utils } from "../../../Utils"; -import Constants from "../../Constants"; -import { Translation } from "../../../UI/i18n/Translation"; -import { LayoutConfigJson } from "../Json/LayoutConfigJson"; -import LayoutConfig from "../LayoutConfig"; -import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"; -import { TagUtils } from "../../../Logic/Tags/TagUtils"; -import { ExtractImages } from "./FixImages"; -import { And } from "../../../Logic/Tags/And"; -import Translations from "../../../UI/i18n/Translations"; -import Svg from "../../../Svg"; -import FilterConfigJson from "../Json/FilterConfigJson"; -import DeleteConfig from "../DeleteConfig"; -import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"; -import Validators from "../../../UI/InputElement/Validators"; -import TagRenderingConfig from "../TagRenderingConfig"; -import { parse as parse_html } from "node-html-parser"; +import { DesugaringStep, Each, Fuse, On } from "./Conversion" +import { LayerConfigJson } from "../Json/LayerConfigJson" +import LayerConfig from "../LayerConfig" +import { Utils } from "../../../Utils" +import Constants from "../../Constants" +import { Translation } from "../../../UI/i18n/Translation" +import { LayoutConfigJson } from "../Json/LayoutConfigJson" +import LayoutConfig from "../LayoutConfig" +import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" +import { TagUtils } from "../../../Logic/Tags/TagUtils" +import { ExtractImages } from "./FixImages" +import { And } from "../../../Logic/Tags/And" +import Translations from "../../../UI/i18n/Translations" +import Svg from "../../../Svg" +import FilterConfigJson from "../Json/FilterConfigJson" +import DeleteConfig from "../DeleteConfig" +import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" +import Validators from "../../../UI/InputElement/Validators" +import TagRenderingConfig from "../TagRenderingConfig" +import { parse as parse_html } from "node-html-parser" class ValidateLanguageCompleteness extends DesugaringStep { - private readonly _languages: string[]; + private readonly _languages: string[] constructor(...languages: string[]) { super( "Checks that the given object is fully translated in the specified languages", [], "ValidateLanguageCompleteness" - ); - this._languages = languages ?? ["en"]; + ) + this._languages = languages ?? ["en"] } convert( obj: any, context: string ): { result: LayerConfig; errors: string[]; warnings: string[] } { - const errors = []; - const warnings: string[] = []; - const translations = Translation.ExtractAllTranslationsFrom(obj); + const errors = [] + const warnings: string[] = [] + const translations = Translation.ExtractAllTranslationsFrom(obj) for (const neededLanguage of this._languages) { translations .filter( @@ -48,38 +48,38 @@ class ValidateLanguageCompleteness extends DesugaringStep { .forEach((missing) => { errors.push( context + - "A theme should be translation-complete for " + - neededLanguage + - ", but it lacks a translation for " + - missing.context + - ".\n\tThe known translation is " + - missing.tr.textFor("en") - ); - }); + "A theme should be translation-complete for " + + neededLanguage + + ", but it lacks a translation for " + + missing.context + + ".\n\tThe known translation is " + + missing.tr.textFor("en") + ) + }) } return { result: obj, errors, - warnings - }; + warnings, + } } } export class DoesImageExist extends DesugaringStep { - private readonly _knownImagePaths: Set; - private readonly _ignore?: Set; - private readonly doesPathExist: (path: string) => boolean = undefined; + private readonly _knownImagePaths: Set + private readonly _ignore?: Set + private readonly doesPathExist: (path: string) => boolean = undefined constructor( knownImagePaths: Set, checkExistsSync: (path: string) => boolean = undefined, ignore?: Set ) { - super("Checks if an image exists", [], "DoesImageExist"); - this._ignore = ignore; - this._knownImagePaths = knownImagePaths; - this.doesPathExist = checkExistsSync; + super("Checks if an image exists", [], "DoesImageExist") + this._ignore = ignore + this._knownImagePaths = knownImagePaths + this.doesPathExist = checkExistsSync } convert( @@ -87,53 +87,53 @@ export class DoesImageExist extends DesugaringStep { context: string ): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } { if (this._ignore?.has(image)) { - return { result: image }; + return { result: image } } - const errors = []; - const warnings = []; - const information = []; + const errors = [] + const warnings = [] + const information = [] if (image.indexOf("{") >= 0) { - information.push("Ignoring image with { in the path: " + image); - return { result: image }; + information.push("Ignoring image with { in the path: " + image) + return { result: image } } if (image === "assets/SocialImage.png") { - return { result: image }; + return { result: image } } if (image.match(/[a-z]*/)) { if (Svg.All[image + ".svg"] !== undefined) { // This is a builtin img, e.g. 'checkmark' or 'crosshair' - return { result: image }; + return { result: image } } } if (image.startsWith("<") && image.endsWith(">")) { // This is probably HTML, you're on your own here - return { result: image }; + return { result: image } } if (!this._knownImagePaths.has(image)) { if (this.doesPathExist === undefined) { errors.push( `Image with path ${image} not found or not attributed; it is used in ${context}` - ); + ) } else if (!this.doesPathExist(image)) { errors.push( `Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.` - ); + ) } else { errors.push( `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` - ); + ) } } return { result: image, errors, warnings, - information - }; + information, + } } } @@ -142,11 +142,11 @@ class ValidateTheme extends DesugaringStep { * The paths where this layer is originally saved. Triggers some extra checks * @private */ - private readonly _path?: string; - private readonly _isBuiltin: boolean; + private readonly _path?: string + private readonly _isBuiltin: boolean //private readonly _sharedTagRenderings: Map - private readonly _validateImage: DesugaringStep; - private readonly _extractImages: ExtractImages = undefined; + private readonly _validateImage: DesugaringStep + private readonly _extractImages: ExtractImages = undefined constructor( doesImageExist: DoesImageExist, @@ -154,12 +154,12 @@ class ValidateTheme extends DesugaringStep { isBuiltin: boolean, sharedTagRenderings?: Set ) { - super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme"); - this._validateImage = doesImageExist; - this._path = path; - this._isBuiltin = isBuiltin; + super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") + this._validateImage = doesImageExist + this._path = path + this._isBuiltin = isBuiltin if (sharedTagRenderings) { - this._extractImages = new ExtractImages(this._isBuiltin, sharedTagRenderings); + this._extractImages = new ExtractImages(this._isBuiltin, sharedTagRenderings) } } @@ -167,11 +167,11 @@ class ValidateTheme extends DesugaringStep { json: LayoutConfigJson, context: string ): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } { - const errors = []; - const warnings = []; - const information = []; + const errors = [] + const warnings = [] + const information = [] - const theme = new LayoutConfig(json, this._isBuiltin); + const theme = new LayoutConfig(json, this._isBuiltin) { // Legacy format checks @@ -179,31 +179,31 @@ class ValidateTheme extends DesugaringStep { if (json["units"] !== undefined) { errors.push( "The theme " + - json.id + - " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " - ); + json.id + + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " + ) } if (json["roamingRenderings"] !== undefined) { errors.push( "Theme " + - json.id + - " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" - ); + json.id + + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" + ) } } } if (this._isBuiltin && this._extractImages !== undefined) { // Check images: are they local, are the licenses there, is the theme icon square, ... - const images = this._extractImages.convertStrict(json, "validation"); - const remoteImages = images.filter((img) => img.path.indexOf("http") == 0); + const images = this._extractImages.convertStrict(json, "validation") + const remoteImages = images.filter((img) => img.path.indexOf("http") == 0) for (const remoteImage of remoteImages) { errors.push( "Found a remote image: " + - remoteImage + - " in theme " + - json.id + - ", please download it." - ); + remoteImage + + " in theme " + + json.id + + ", please download it." + ) } for (const image of images) { this._validateImage.convertJoin( @@ -212,30 +212,30 @@ class ValidateTheme extends DesugaringStep { errors, warnings, information - ); + ) } } try { if (this._isBuiltin) { if (theme.id !== theme.id.toLowerCase()) { - errors.push("Theme ids should be in lowercase, but it is " + theme.id); + errors.push("Theme ids should be in lowercase, but it is " + theme.id) } const filename = this._path.substring( this._path.lastIndexOf("/") + 1, this._path.length - 5 - ); + ) if (theme.id !== filename) { errors.push( "Theme ids should be the same as the name.json, but we got id: " + - theme.id + - " and filename " + - filename + - " (" + - this._path + - ")" - ); + theme.id + + " and filename " + + filename + + " (" + + this._path + + ")" + ) } this._validateImage.convertJoin( theme.icon, @@ -243,44 +243,44 @@ class ValidateTheme extends DesugaringStep { errors, warnings, information - ); + ) } - const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])); + const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])) if (dups.length > 0) { errors.push( `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` - ); + ) } if (json["mustHaveLanguage"] !== undefined) { const checked = new ValidateLanguageCompleteness( ...json["mustHaveLanguage"] - ).convert(theme, theme.id); + ).convert(theme, theme.id) - errors.push(...checked.errors); + errors.push(...checked.errors) } if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { // The first key in the the title-field must be english, otherwise the title in the loading page will be the different language - const targetLanguage = theme.title.SupportedLanguages()[0]; + const targetLanguage = theme.title.SupportedLanguages()[0] if (targetLanguage !== "en") { warnings.push( `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key` - ); + ) } // Official, public themes must have a full english translation - const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id); - errors.push(...checked.errors); + const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id) + errors.push(...checked.errors) } } catch (e) { - errors.push(e); + errors.push(e) } return { result: json, errors, warnings, - information - }; + information, + } } } @@ -295,7 +295,7 @@ export class ValidateThemeAndLayers extends Fuse { "Validates a theme and the contained layers", new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings), new On("layers", new Each(new ValidateLayer(undefined, isBuiltin, doesImageExist))) - ); + ) } } @@ -305,26 +305,26 @@ class OverrideShadowingCheck extends DesugaringStep { "Checks that an 'overrideAll' does not override a single override", [], "OverrideShadowingCheck" - ); + ) } convert( json: LayoutConfigJson, _: string ): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } { - const overrideAll = json.overrideAll; + const overrideAll = json.overrideAll if (overrideAll === undefined) { - return { result: json }; + return { result: json } } - const errors = []; - const withOverride = json.layers.filter((l) => l["override"] !== undefined); + const errors = [] + const withOverride = json.layers.filter((l) => l["override"] !== undefined) for (const layer of withOverride) { for (const key in overrideAll) { if (key.endsWith("+") || key.startsWith("+")) { // This key will _add_ to the list, not overwrite it - so no warning is needed - continue; + continue } if ( layer["override"][key] !== undefined || @@ -335,19 +335,19 @@ class OverrideShadowingCheck extends DesugaringStep { JSON.stringify(layer["builtin"]) + " has a shadowed property: " + key + - " is overriden by overrideAll of the theme"; - errors.push(w); + " is overriden by overrideAll of the theme" + errors.push(w) } } } - return { result: json, errors }; + return { result: json, errors } } } class MiscThemeChecks extends DesugaringStep { constructor() { - super("Miscelleanous checks on the theme", [], "MiscThemesChecks"); + super("Miscelleanous checks on the theme", [], "MiscThemesChecks") } convert( @@ -359,19 +359,19 @@ class MiscThemeChecks extends DesugaringStep { warnings?: string[] information?: string[] } { - const warnings = []; - const errors = []; + const warnings = [] + const errors = [] if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) { - errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")"); + errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")") } if (json.socialImage === "") { - warnings.push("Social image for theme " + json.id + " is the emtpy string"); + warnings.push("Social image for theme " + json.id + " is the emtpy string") } return { result: json, warnings, - errors - }; + errors, + } } } @@ -381,7 +381,7 @@ export class PrevalidateTheme extends Fuse { "Various consistency checks on the raw JSON", new MiscThemeChecks(), new OverrideShadowingCheck() - ); + ) } } @@ -391,7 +391,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep 0)) { - return { result: json }; + return { result: json } } - const tagRendering = new TagRenderingConfig(json); + const tagRendering = new TagRenderingConfig(json) - const errors = []; + const errors = [] for (let i = 0; i < tagRendering.mappings.length; i++) { - const mapping = tagRendering.mappings[i]; + const mapping = tagRendering.mappings[i] if (!mapping.addExtraTags) { - continue; + continue } - const keysInMapping = new Set(mapping.if.usedKeys()); + const keysInMapping = new Set(mapping.if.usedKeys()) - const keysInAddExtraTags = mapping.addExtraTags.map((t) => t.key); + const keysInAddExtraTags = mapping.addExtraTags.map((t) => t.key) - const duplicateKeys = keysInAddExtraTags.filter((k) => keysInMapping.has(k)); + 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(", ") - ); + 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 { result: json, - errors - }; + errors, + } } } export class DetectShadowedMappings extends DesugaringStep { - private readonly _calculatedTagNames: string[]; + private readonly _calculatedTagNames: string[] constructor(layerConfig?: LayerConfigJson) { - super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings"); - this._calculatedTagNames = DetectShadowedMappings.extractCalculatedTagNames(layerConfig); + super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings") + this._calculatedTagNames = DetectShadowedMappings.extractCalculatedTagNames(layerConfig) } /** @@ -458,11 +458,11 @@ export class DetectShadowedMappings extends DesugaringStep { if (ct.indexOf(":=") >= 0) { - return ct.split(":=")[0]; + return ct.split(":=")[0] } - return ct.split("=")[0]; + return ct.split("=")[0] }) ?? [] - ); + ) } /** @@ -502,40 +502,40 @@ export class DetectShadowedMappings extends DesugaringStep { - const ctx = `${context}.mappings[${i}]`; - const ifTags = TagUtils.Tag(m.if, ctx); - const hideInAnswer = m["hideInAnswer"]; + const ctx = `${context}.mappings[${i}]` + const ifTags = TagUtils.Tag(m.if, ctx) + const hideInAnswer = m["hideInAnswer"] if (hideInAnswer !== undefined && hideInAnswer !== false && hideInAnswer !== true) { - let conditionTags = TagUtils.Tag(hideInAnswer); + let conditionTags = TagUtils.Tag(hideInAnswer) // Merge the condition too! - return new And([conditionTags, ifTags]); + return new And([conditionTags, ifTags]) } - return ifTags; - }); + return ifTags + }) for (let i = 0; i < json.mappings.length; i++) { if (!parsedConditions[i].isUsableAsAnswer()) { // There is no straightforward way to convert this mapping.if into a properties-object, so we simply skip this one // Yes, it might be shadowed, but running this check is to difficult right now - continue; + continue } - const keyValues = parsedConditions[i].asChange(defaultProperties); - const properties = {}; + const keyValues = parsedConditions[i].asChange(defaultProperties) + const properties = {} keyValues.forEach(({ k, v }) => { - properties[k] = v; - }); + properties[k] = v + }) for (let j = 0; j < i; j++) { - const doesMatch = parsedConditions[j].matchesProperties(properties); + const doesMatch = parsedConditions[j].matchesProperties(properties) if ( doesMatch && json.mappings[j]["hideInAnswer"] === true && @@ -543,15 +543,15 @@ export class DetectShadowedMappings extends DesugaringStep= 0; - const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []); - const ctx = `${context}.mappings[${i}]`; + const mapping = json.mappings[i] + const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0 + const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []) + const ctx = `${context}.mappings[${i}]` if (images.length > 0) { if (!ignore) { errors.push( `${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join( ", " )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged` - ); + ) } else { information.push( `${ctx}: Ignored image ${images.join( ", " )} in 'then'-clause of a mapping as this check has been disabled` - ); + ) for (const image of images) { - this._doesImageExist.convertJoin(image, ctx, errors, warnings, information); + this._doesImageExist.convertJoin(image, ctx, errors, warnings, information) } } } else if (ignore) { - warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`); + warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) } } @@ -655,72 +655,86 @@ export class DetectMappingsWithImages extends DesugaringStep> { constructor() { - super("Given a possible set of translations, validates that does have `rel='noopener'` set", [], "ValidatePossibleLinks"); + super( + "Given a possible set of translations, validates that does have `rel='noopener'` set", + [], + "ValidatePossibleLinks" + ) } public isTabnabbingProne(str: string): boolean { - const p = parse_html(str); - const links = Array.from(p.getElementsByTagName("a")); + const p = parse_html(str) + const links = Array.from(p.getElementsByTagName("a")) if (links.length == 0) { - return false; + return false } for (const link of Array.from(links)) { if (link.getAttribute("target") !== "_blank") { - continue; + continue } - const rel = new Set(link.getAttribute("rel")?.split(" ") ?? []); + const rel = new Set(link.getAttribute("rel")?.split(" ") ?? []) if (rel.has("noopener")) { - continue; + continue } - const source = link.getAttribute("href"); + const source = link.getAttribute("href") if (source.startsWith("http")) { // No variable part - we assume the link is safe - continue; + continue } - return true; + return true } - return false; + return false } - convert(json: string | Record, context: string): { - result: string | Record; - errors?: string[]; - warnings?: string[]; + convert( + json: string | Record, + context: string + ): { + result: string | Record + errors?: string[] + warnings?: string[] information?: string[] } { - - const errors = []; + const errors = [] if (typeof json === "string") { if (this.isTabnabbingProne(json)) { - errors.push("At " + context + ": the string " + json + " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"); + errors.push( + "At " + + context + + ": the string " + + json + + " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" + ) } } else { for (const k in json) { if (this.isTabnabbingProne(json[k])) { - errors.push(`At ${context}: the translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`); + errors.push( + `At ${context}: the translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping` + ) } } } return { errors, - result: json - }; + result: json, + } } } class MiscTagRenderingChecks extends DesugaringStep { - private _options: { noQuestionHintCheck: boolean }; + private _options: { noQuestionHintCheck: boolean } constructor(options: { noQuestionHintCheck: boolean }) { - super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks"); - this._options = options; + super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") + this._options = options } convert( @@ -732,26 +746,26 @@ class MiscTagRenderingChecks extends DesugaringStep { warnings?: string[] information?: string[] } { - const warnings = []; - const errors = []; + const warnings = [] + const errors = [] if (json["special"] !== undefined) { errors.push( "At " + - context + - ": detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`" - ); + context + + ': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' + ) } if (json["group"]) { errors.push( "At " + - context + - ": groups are deprecated, use `\"label\": [\"" + - json["group"] + - "\"]` instead" - ); + context + + ': groups are deprecated, use `"label": ["' + + json["group"] + + '"]` instead' + ) } - const freeformType = json["freeform"]?.["type"]; + const freeformType = json["freeform"]?.["type"] if (freeformType) { if (Validators.availableTypes.indexOf(freeformType) < 0) { throw ( @@ -761,14 +775,14 @@ class MiscTagRenderingChecks extends DesugaringStep { freeformType + "; try one of " + Validators.availableTypes.join(", ") - ); + ) } } return { result: json, errors, - warnings - }; + warnings, + } } } @@ -783,16 +797,12 @@ export class ValidateTagRenderings extends Fuse { new DetectShadowedMappings(layerConfig), new DetectConflictingAddExtraTags(), new DetectMappingsWithImages(doesImageExist), - new On("render", - new ValidatePossibleLinks()), - new On("question", - new ValidatePossibleLinks()), - new On("questionHint", - new ValidatePossibleLinks()), - new On("mappings", - new Each(new On("then", new ValidatePossibleLinks()))), + new On("render", new ValidatePossibleLinks()), + new On("question", new ValidatePossibleLinks()), + new On("questionHint", new ValidatePossibleLinks()), + new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), new MiscTagRenderingChecks(options) - ); + ) } } @@ -801,41 +811,41 @@ export class ValidateLayer extends DesugaringStep { * The paths where this layer is originally saved. Triggers some extra checks * @private */ - private readonly _path?: string; - private readonly _isBuiltin: boolean; - private readonly _doesImageExist: DoesImageExist; + private readonly _path?: string + private readonly _isBuiltin: boolean + private readonly _doesImageExist: DoesImageExist constructor(path: string, isBuiltin: boolean, doesImageExist: DoesImageExist) { - super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer"); - this._path = path; - this._isBuiltin = isBuiltin; - this._doesImageExist = doesImageExist; + super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") + this._path = path + this._isBuiltin = isBuiltin + this._doesImageExist = doesImageExist } convert( json: LayerConfigJson, context: string ): { result: LayerConfigJson; errors: string[]; warnings?: string[]; information?: string[] } { - const errors = []; - const warnings = []; - const information = []; - context = "While validating a layer: " + context; + const errors = [] + const warnings = [] + const information = [] + context = "While validating a layer: " + context if (typeof json === "string") { - errors.push(context + ": This layer hasn't been expanded: " + json); + errors.push(context + ": This layer hasn't been expanded: " + json) return { result: null, - errors - }; + errors, + } } if (json.source === "special") { if (!Constants.priviliged_layers.find((x) => x == json.id)) { errors.push( context + - ": layer " + - json.id + - " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" - ); + ": layer " + + json.id + + " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" + ) } } @@ -843,49 +853,49 @@ export class ValidateLayer extends DesugaringStep { if (json.title === undefined && json.source !== "special:library") { errors.push( context + - ": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." - ); + ": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." + ) } if (json.title === null) { information.push( context + - ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." - ); + ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." + ) } } if (json["builtin"] !== undefined) { - errors.push(context + ": This layer hasn't been expanded: " + json); + errors.push(context + ": This layer hasn't been expanded: " + json) return { result: null, - errors - }; + errors, + } } if (json.minzoom > Constants.minZoomLevelToAddNewPoint) { ;(json.presets?.length > 0 ? errors : warnings).push( `At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates` - ); + ) } { // duplicate ids in tagrenderings check const duplicates = Utils.Dedup( Utils.Dupiclates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) - ); + ) if (duplicates.length > 0) { - console.log(json.tagRenderings); + console.log(json.tagRenderings) errors.push( "At " + - context + - ": some tagrenderings have a duplicate id: " + - duplicates.join(", ") - ); + context + + ": some tagrenderings have a duplicate id: " + + duplicates.join(", ") + ) } } if (json.deletion !== undefined && json.deletion instanceof DeleteConfig) { if (json.deletion.softDeletionTags === undefined) { - warnings.push("No soft-deletion tags in deletion block for layer " + json.id); + warnings.push("No soft-deletion tags in deletion block for layer " + json.id) } } @@ -896,9 +906,9 @@ export class ValidateLayer extends DesugaringStep { if (json["overpassTags"] !== undefined) { errors.push( "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)" - ); + 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", @@ -909,88 +919,88 @@ export class ValidateLayer extends DesugaringStep { "width", "color", "colour", - "iconOverlays" - ]; + "iconOverlays", + ] for (const forbiddenKey of forbiddenTopLevel) { if (json[forbiddenKey] !== undefined) errors.push( context + - ": layer " + - json.id + - " still has a forbidden key " + - forbiddenKey - ); + ": layer " + + json.id + + " still has a forbidden key " + + forbiddenKey + ) } if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { errors.push( context + - ": layer " + - json.id + - " contains an old 'hideUnderlayingFeaturesMinPercentage'" - ); + ": layer " + + json.id + + " contains an old 'hideUnderlayingFeaturesMinPercentage'" + ) } if ( json.isShown !== undefined && (json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined) ) { - warnings.push(context + " has a tagRendering as `isShown`"); + warnings.push(context + " has a tagRendering as `isShown`") } } if (this._isBuiltin) { // Check location of layer file - const expected: string = `assets/layers/${json.id}/${json.id}.json`; + const expected: string = `assets/layers/${json.id}/${json.id}.json` if (this._path != undefined && this._path.indexOf(expected) < 0) { errors.push( "Layer is in an incorrect place. The path is " + - this._path + - ", but expected " + - expected - ); + this._path + + ", but expected " + + expected + ) } } if (this._isBuiltin) { // Check for correct IDs if (json.tagRenderings?.some((tr) => tr["id"] === "")) { - const emptyIndexes: number[] = []; + const emptyIndexes: number[] = [] for (let i = 0; i < json.tagRenderings.length; i++) { - const tagRendering = json.tagRenderings[i]; + const tagRendering = json.tagRenderings[i] if (tagRendering["id"] === "") { - emptyIndexes.push(i); + emptyIndexes.push(i) } } errors.push( `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context}.tagRenderings.[${emptyIndexes.join( "," )}])` - ); + ) } const duplicateIds = Utils.Dupiclates( (json.tagRenderings ?? []) ?.map((f) => f["id"]) .filter((id) => id !== "questions") - ); + ) if (duplicateIds.length > 0 && !Utils.runningFromConsole) { errors.push( `Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)` - ); + ) } if (json.description === undefined) { if (typeof json.source === null) { - errors.push(context + ": A priviliged layer must have a description"); + errors.push(context + ": A priviliged layer must have a description") } else { - warnings.push(context + ": A builtin layer should have a description"); + warnings.push(context + ": A builtin layer should have a description") } } } if (json.filter) { - const r = new On("filter", new Each(new ValidateFilter())).convert(json, context); - warnings.push(...(r.warnings ?? [])); - errors.push(...(r.errors ?? [])); - information.push(...(r.information ?? [])); + const r = new On("filter", new Each(new ValidateFilter())).convert(json, context) + warnings.push(...(r.warnings ?? [])) + errors.push(...(r.errors ?? [])) + information.push(...(r.information ?? [])) } if (json.tagRenderings !== undefined) { @@ -998,74 +1008,74 @@ export class ValidateLayer extends DesugaringStep { "tagRenderings", new Each( new ValidateTagRenderings(json, this._doesImageExist, { - noQuestionHintCheck: json["#"]?.indexOf("no-question-hint-check") >= 0 + noQuestionHintCheck: json["#"]?.indexOf("no-question-hint-check") >= 0, }) ) - ).convert(json, context); - warnings.push(...(r.warnings ?? [])); - errors.push(...(r.errors ?? [])); - information.push(...(r.information ?? [])); + ).convert(json, context) + warnings.push(...(r.warnings ?? [])) + errors.push(...(r.errors ?? [])) + information.push(...(r.information ?? [])) } { const hasCondition = json.mapRendering?.filter( (mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined - ); + ) if (hasCondition?.length > 0) { errors.push( "At " + - context + - ":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" + - JSON.stringify(hasCondition, null, " ") - ); + context + + ":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" + + JSON.stringify(hasCondition, null, " ") + ) } } if (json.presets !== undefined) { if (typeof json.source === "string") { - throw "A special layer cannot have presets"; + throw "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"]); + const baseTags = TagUtils.Tag(json.source["osmTags"]) for (let i = 0; i < json.presets.length; i++) { - const preset = json.presets[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 = {}; + ).asChange({ id: "node/-1" }) + const properties = {} for (const tag of tags) { - properties[tag.k] = tag.v; + properties[tag.k] = tag.v } - const doMatch = baseTags.matchesProperties(properties); + const doMatch = baseTags.matchesProperties(properties) if (!doMatch) { errors.push( context + - ".presets[" + - i + - "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + - JSON.stringify(properties) + - "\n The required tags are: " + - baseTags.asHumanString(false, false, {}) - ); + ".presets[" + + i + + "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + + JSON.stringify(properties) + + "\n The required tags are: " + + baseTags.asHumanString(false, false, {}) + ) } } } } catch (e) { - errors.push(e); + errors.push(e) } return { result: json, errors, warnings, - information - }; + information, + } } } export class ValidateFilter extends DesugaringStep { constructor() { - super("Detect common errors in the filters", [], "ValidateFilter"); + super("Detect common errors in the filters", [], "ValidateFilter") } convert( @@ -1079,22 +1089,22 @@ export class ValidateFilter extends DesugaringStep { } { if (typeof filter === "string") { // Calling another filter, we skip - return { result: filter }; + return { result: filter } } - const errors = []; + const errors = [] for (const option of filter.options) { for (let i = 0; i < option.fields?.length ?? 0; i++) { - const field = option.fields[i]; - const type = field.type ?? "string"; + const field = option.fields[i] + const type = field.type ?? "string" if (Validators.availableTypes.find((t) => t === type) === undefined) { const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from( Validators.availableTypes - ).join(",")}`; - errors.push(err); + ).join(",")}` + errors.push(err) } } } - return { result: filter, errors }; + return { result: filter, errors } } } @@ -1107,7 +1117,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ "Tries to detect layers where a shared filter can be used (or where similar filters occur)", [], "DetectDuplicateFilters" - ); + ) } convert( @@ -1119,11 +1129,11 @@ export class DetectDuplicateFilters extends DesugaringStep<{ warnings?: string[] information?: string[] } { - const errors: string[] = []; - const warnings: string[] = []; - const information: string[] = []; + const errors: string[] = [] + const warnings: string[] = [] + const information: string[] = [] - const { layers, themes } = json; + const { layers, themes } = json const perOsmTag = new Map< string, { @@ -1131,24 +1141,24 @@ export class DetectDuplicateFilters extends DesugaringStep<{ layout: LayoutConfigJson | undefined filter: FilterConfigJson }[] - >(); + >() for (const layer of layers) { - this.addLayerFilters(layer, perOsmTag); + this.addLayerFilters(layer, perOsmTag) } for (const theme of themes) { if (theme.id === "personal") { - continue; + continue } for (const layer of theme.layers) { if (typeof layer === "string") { - continue; + continue } if (layer["builtin"] !== undefined) { - continue; + continue } - this.addLayerFilters(layer, perOsmTag, theme); + this.addLayerFilters(layer, perOsmTag, theme) } } @@ -1156,25 +1166,25 @@ export class DetectDuplicateFilters extends DesugaringStep<{ perOsmTag.forEach((value, key) => { if (value.length <= 1) { // Seen this key just once, it is unique - return; + return } - let msg = "Possible duplicate filter: " + key; + let msg = "Possible duplicate filter: " + key for (const { filter, layer, layout } of value) { - let id = ""; + let id = "" if (layout !== undefined) { - id = layout.id + ":"; + id = layout.id + ":" } - msg += `\n - ${id}${layer.id}.${filter.id}`; + msg += `\n - ${id}${layer.id}.${filter.id}` } - warnings.push(msg); - }); + warnings.push(msg) + }) return { result: json, errors, warnings, - information - }; + information, + } } /** @@ -1193,33 +1203,33 @@ export class DetectDuplicateFilters extends DesugaringStep<{ layout?: LayoutConfigJson | undefined ): void { if (layer.filter === undefined || layer.filter === null) { - return; + return } if (layer.filter["sameAs"] !== undefined) { - return; + return } for (const filter of <(string | FilterConfigJson)[]>layer.filter) { if (typeof filter === "string") { - continue; + continue } if (filter["#"]?.indexOf("ignore-possible-duplicate") >= 0) { - continue; + continue } for (const option of filter.options) { if (option.osmTags === undefined) { - continue; + continue } - const key = JSON.stringify(option.osmTags); + const key = JSON.stringify(option.osmTags) if (!perOsmTag.has(key)) { - perOsmTag.set(key, []); + perOsmTag.set(key, []) } perOsmTag.get(key).push({ layer, filter, - layout - }); + layout, + }) } } } diff --git a/src/UI/Base/Table.ts b/src/UI/Base/Table.ts index c400b9129..d6632f30b 100644 --- a/src/UI/Base/Table.ts +++ b/src/UI/Base/Table.ts @@ -29,7 +29,15 @@ export default class Table extends BaseUIElement { const header = Utils.NoNull(headerMarkdownParts).join(" | ") const headerSep = headerMarkdownParts.map((part) => "-".repeat(part.length + 2)).join(" | ") const table = this._contents - .map((row) => row.map((el) => el?.AsMarkdown()?.replaceAll("\\","\\\\")?.replaceAll("|", "\\|") ?? " ").join(" | ")) + .map((row) => + row + .map( + (el) => + el?.AsMarkdown()?.replaceAll("\\", "\\\\")?.replaceAll("|", "\\|") ?? + " " + ) + .join(" | ") + ) .join("\n") return "\n\n" + [header, headerSep, table, ""].join("\n") diff --git a/src/UI/BigComponents/ContactLink.svelte b/src/UI/BigComponents/ContactLink.svelte index 7cebd2c0d..2cd12366b 100644 --- a/src/UI/BigComponents/ContactLink.svelte +++ b/src/UI/BigComponents/ContactLink.svelte @@ -35,7 +35,12 @@ src={`https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/${resource.type}.svg`} />
- + {resource.resolved.name ?? resource.resolved.url} {resource.resolved?.description} diff --git a/src/UI/BigComponents/CopyrightPanel.ts b/src/UI/BigComponents/CopyrightPanel.ts index d066a4d00..f3b6f7634 100644 --- a/src/UI/BigComponents/CopyrightPanel.ts +++ b/src/UI/BigComponents/CopyrightPanel.ts @@ -102,7 +102,11 @@ export default class CopyrightPanel extends Combine { let bgAttr: BaseUIElement | string = undefined if (attrText && attrUrl) { bgAttr = - "" + attrText + "" + "" + + attrText + + "" } else if (attrUrl) { bgAttr = attrUrl } else { diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts index f5ce9772d..5c3f6b5c6 100644 --- a/src/UI/Image/ImageUploadFlow.ts +++ b/src/UI/Image/ImageUploadFlow.ts @@ -72,10 +72,7 @@ export class ImageUploadFlow extends Toggle { labelContent, ]).SetClass("w-full flex justify-center items-center") - const licenseStore = state?.osmConnection?.GetPreference( - "pictures-license", - "CC0" - ) + const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0") const fileSelector = new FileSelectorButton(label, { acceptType: "image/*", diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte index 798bebb09..eeb3c19c7 100644 --- a/src/UI/Popup/LinkableImage.svelte +++ b/src/UI/Popup/LinkableImage.svelte @@ -1,72 +1,71 @@ -
+ +
{#if linkable} {/if} diff --git a/src/UI/Popup/NearbyImages.svelte b/src/UI/Popup/NearbyImages.svelte index 30c886914..c5b14927c 100644 --- a/src/UI/Popup/NearbyImages.svelte +++ b/src/UI/Popup/NearbyImages.svelte @@ -1,40 +1,44 @@ - -
+
-

@@ -43,7 +47,7 @@ let images: Store = imagesProvider.store.map(images => images.slic {#if $images.length === 0} {:else} -
+
{#each $images as image (image.pictureUrl)} diff --git a/src/UI/Popup/NearbyImagesCollapsed.svelte b/src/UI/Popup/NearbyImagesCollapsed.svelte index e1679bff9..0a315dcb3 100644 --- a/src/UI/Popup/NearbyImagesCollapsed.svelte +++ b/src/UI/Popup/NearbyImagesCollapsed.svelte @@ -1,36 +1,48 @@ {#if expanded} - {expanded = false}}/> + { + expanded = false + }} + /> {:else} - + {/if} diff --git a/src/UI/Popup/SendEmail.svelte b/src/UI/Popup/SendEmail.svelte index 32a04f5f1..1af281361 100644 --- a/src/UI/Popup/SendEmail.svelte +++ b/src/UI/Popup/SendEmail.svelte @@ -1,22 +1,18 @@ - - + + + {button_text} diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 0522baf0e..216468f1b 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -1,43 +1,43 @@ {#if config.question !== undefined} diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte index 4ad143006..cd44cf328 100644 --- a/src/UI/StylesheetTestGui.svelte +++ b/src/UI/StylesheetTestGui.svelte @@ -1,7 +1,7 @@
@@ -39,12 +39,8 @@ Main action (disabled) - - + +
- + + {:else if $geopermission === "requested"} + + {:else if $geopermission !== "denied"} + + {/if}
From 90c44cc8bdfb7b2447f1974ed94430e5b2fa3c6f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:09:19 +0200 Subject: [PATCH 074/133] Themes: enable deletion of bike_cafes --- assets/layers/bike_cafe/bike_cafe.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index d90821ded..ea86ad10a 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -363,5 +363,6 @@ "fr": "Un vélo café est un café à destination des cyclistes avec, par exemple, des services tels qu’une pompe, et de nombreuses décorations liées aux vélos, etc.", "cs": "Cyklokavárna je kavárna zaměřená na cyklisty, například se službami, jako je pumpa, se spoustou výzdoby související s jízdními koly, …", "ca": "Un cafè ciclista és un cafè enfocat a ciclistes, per exemple, amb serveis com una manxa, amb molta decoració relacionada amb el ciclisme, …" - } + }, + "deletion": true } From 57e0093e478f541c46145a72dbfb9fc70d467633 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:09:42 +0200 Subject: [PATCH 075/133] Fix: initialize user settings with a strict value to be able to override it --- assets/layers/usersettings/usersettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index 8054c2775..10d21c736 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -24,7 +24,7 @@ "_mastodon_candidate_a=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ", "_mastodon_link=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.getAttribute(\"rel\")?.indexOf('me') >= 0)[0]?.href})(feat) ", "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a", - "__current_background='initial_value'" + "__current_background:='initial_value'" ], "tagRenderings": [ { From 6f5b0622a5c8a319ab5c20d822f6a5b817fc1489 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:11:42 +0200 Subject: [PATCH 076/133] Chore: remove some obsolete console.logs --- .../ImageProviders/ImageUploadManager.ts | 0 src/Logic/ImageProviders/ImageUploader.ts | 0 src/Logic/ImageProviders/ImgurUploader.ts | 43 ------------------- .../{LinkPicture.ts => LinkImageAction.ts} | 0 src/Logic/Osm/ChangesetHandler.ts | 7 +-- src/Logic/State/GeoLocationState.ts | 1 - src/Logic/State/UserSettingsMetaTagging.ts | 2 +- src/Logic/UIEventSource.ts | 2 +- src/UI/Base/FileSelector.svelte | 0 src/UI/Image/UploadImage.svelte | 0 src/UI/Image/UploadingImageCounter.svelte | 31 +++++++++++++ src/UI/Popup/DeleteFlow/DeleteWizard.svelte | 2 - src/UI/Popup/LinkableImage.svelte | 4 +- 13 files changed, 39 insertions(+), 53 deletions(-) create mode 100644 src/Logic/ImageProviders/ImageUploadManager.ts create mode 100644 src/Logic/ImageProviders/ImageUploader.ts delete mode 100644 src/Logic/ImageProviders/ImgurUploader.ts rename src/Logic/Osm/Actions/{LinkPicture.ts => LinkImageAction.ts} (100%) create mode 100644 src/UI/Base/FileSelector.svelte create mode 100644 src/UI/Image/UploadImage.svelte create mode 100644 src/UI/Image/UploadingImageCounter.svelte diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/Logic/ImageProviders/ImageUploader.ts b/src/Logic/ImageProviders/ImageUploader.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/Logic/ImageProviders/ImgurUploader.ts b/src/Logic/ImageProviders/ImgurUploader.ts deleted file mode 100644 index bb4fc6a9f..000000000 --- a/src/Logic/ImageProviders/ImgurUploader.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { UIEventSource } from "../UIEventSource" -import { Imgur } from "./Imgur" - -export default class ImgurUploader { - public readonly queue: UIEventSource = new UIEventSource([]) - public readonly failed: UIEventSource = new UIEventSource([]) - public readonly success: UIEventSource = new UIEventSource([]) - public maxFileSizeInMegabytes = 10 - private readonly _handleSuccessUrl: (string) => Promise - - constructor(handleSuccessUrl: (string) => Promise) { - this._handleSuccessUrl = handleSuccessUrl - } - - public uploadMany(title: string, description: string, files: FileList): void { - for (let i = 0; i < files.length; i++) { - this.queue.data.push(files.item(i).name) - } - this.queue.ping() - - const self = this - this.queue.setData([...self.queue.data]) - Imgur.uploadMultiple( - title, - description, - files, - async function (url) { - console.log("File saved at", url) - self.success.data.push(url) - self.success.ping() - await self._handleSuccessUrl(url) - }, - function () { - console.log("All uploads completed") - }, - - function (failReason) { - console.log("Upload failed due to ", failReason) - self.failed.setData([...self.failed.data, failReason]) - } - ) - } -} diff --git a/src/Logic/Osm/Actions/LinkPicture.ts b/src/Logic/Osm/Actions/LinkImageAction.ts similarity index 100% rename from src/Logic/Osm/Actions/LinkPicture.ts rename to src/Logic/Osm/Actions/LinkImageAction.ts diff --git a/src/Logic/Osm/ChangesetHandler.ts b/src/Logic/Osm/ChangesetHandler.ts index dcdb8a936..4b2a70b32 100644 --- a/src/Logic/Osm/ChangesetHandler.ts +++ b/src/Logic/Osm/ChangesetHandler.ts @@ -5,6 +5,7 @@ import Locale from "../../UI/i18n/Locale" import Constants from "../../Models/Constants" import { Changes } from "./Changes" import { Utils } from "../../Utils" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; export interface ChangesetTag { key: string @@ -13,7 +14,7 @@ export interface ChangesetTag { } export class ChangesetHandler { - private readonly allElements: { addAlias: (id0: String, id1: string) => void } + private readonly allElements: FeaturePropertiesStore private osmConnection: OsmConnection private readonly changes: Changes private readonly _dryRun: Store @@ -29,11 +30,11 @@ export class ChangesetHandler { constructor( dryRun: Store, osmConnection: OsmConnection, - allElements: { addAlias: (id0: string, id1: string) => void } | undefined, + allElements: FeaturePropertiesStore | { addAlias: (id0: string, id1: string) => void } | undefined, changes: Changes ) { this.osmConnection = osmConnection - this.allElements = allElements + this.allElements = allElements this.changes = changes this._dryRun = dryRun this.userDetails = osmConnection.userDetails diff --git a/src/Logic/State/GeoLocationState.ts b/src/Logic/State/GeoLocationState.ts index ff7f3ac44..fe395fde0 100644 --- a/src/Logic/State/GeoLocationState.ts +++ b/src/Logic/State/GeoLocationState.ts @@ -61,7 +61,6 @@ export class GeoLocationState { const self = this; this.permission.addCallbackAndRunD(async (state) => { - console.trace("GEOPERMISSION", state) if (state === "granted") { self._previousLocationGrant.setData("true"); self._grantedThisSession.setData(true); diff --git a/src/Logic/State/UserSettingsMetaTagging.ts b/src/Logic/State/UserSettingsMetaTagging.ts index 74a74dae4..33a5ae85b 100644 --- a/src/Logic/State/UserSettingsMetaTagging.ts +++ b/src/Logic/State/UserSettingsMetaTagging.ts @@ -9,6 +9,6 @@ export class ThemeMetaTagging { Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) - Utils.AddLazyProperty(feat.properties, '__current_background', () => 'initial_value' ) + feat.properties['__current_backgroun'] = 'initial_value' } } \ No newline at end of file diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index b72646539..bb22df11c 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -515,7 +515,7 @@ class MappedStore extends Store { } private unregisterFromUpstream() { - console.log("Unregistering callbacks for", this.tag) + console.debug("Unregistering callbacks for", this.tag) this._callbacksAreRegistered = false this._unregisterFromUpstream() this._unregisterFromExtraStores?.forEach((unr) => unr()) diff --git a/src/UI/Base/FileSelector.svelte b/src/UI/Base/FileSelector.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte new file mode 100644 index 000000000..a3bfa02e5 --- /dev/null +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte index 8eada0a7f..2cbfb0850 100644 --- a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte +++ b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte @@ -38,7 +38,6 @@ const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined let currentState: "start" | "confirm" | "applying" | "deleted" = "start" $: { - console.log("Current state is", currentState, $canBeDeleted, canBeDeletedReason) deleteAbility.CheckDeleteability(true) } @@ -55,7 +54,6 @@ let actionToTake: OsmChangeAction const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {})) const deleteReason = changedProperties[DeleteConfig.deleteReasonKey] - console.log("Deleting! Hard?:", canBeDeleted.data, deleteReason) if (deleteReason) { // This is a proper, hard deletion actionToTake = new DeleteAction( diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte index ef04b9868..2aa280f98 100644 --- a/src/UI/Popup/LinkableImage.svelte +++ b/src/UI/Popup/LinkableImage.svelte @@ -6,7 +6,7 @@ import ToSvelte from "../Base/ToSvelte.svelte" import { AttributedImage } from "../Image/AttributedImage" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" - import LinkPicture from "../../Logic/Osm/Actions/LinkPicture" + import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" import { GeoOperations } from "../../Logic/GeoOperations" @@ -40,7 +40,7 @@ const key = Object.keys(image.osmTags)[0] const url = image.osmTags[key] if (isLinked) { - const action = new LinkPicture(currentTags.id, key, url, currentTags, { + const action = new LinkImageAction(currentTags.id, key, url, currentTags, { theme: state.layout.id, changeType: "link-image", }) From 94ba18785d2383b0ae384f8af2e6f66c7ad89e77 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:13:24 +0200 Subject: [PATCH 077/133] Chore: rework image uploading, should work better now --- langs/en.json | 20 +- public/css/index-tailwind-output.css | 20 -- .../Actors/FeaturePropertiesStore.ts | 4 +- .../ImageProviders/ImageUploadManager.ts | 150 ++++++++++++++ src/Logic/ImageProviders/ImageUploader.ts | 15 ++ src/Logic/ImageProviders/Imgur.ts | 79 ++------ src/Logic/Osm/Actions/LinkImageAction.ts | 40 +++- src/Logic/Osm/Actions/OsmChangeAction.ts | 3 + src/Logic/State/UserRelatedState.ts | 43 ++-- src/Models/ThemeViewState.ts | 6 + src/UI/Base/FileSelector.svelte | 40 ++++ src/UI/Base/Loading.svelte | 11 +- src/UI/Image/ImageUploadFlow.ts | 19 +- src/UI/Image/UploadImage.svelte | 73 +++++++ src/UI/Image/UploadingImageCounter.svelte | 68 +++++-- src/UI/SpecialVisualization.ts | 184 +++++++++--------- src/UI/SpecialVisualizations.ts | 11 +- 17 files changed, 548 insertions(+), 238 deletions(-) diff --git a/langs/en.json b/langs/en.json index f25b61951..42432d12c 100644 --- a/langs/en.json +++ b/langs/en.json @@ -344,8 +344,8 @@ }, "useSearch": "Use the search above to see presets", "useSearchForMore": "Use the search function to search within {total} more values…", - "waitingForGeopermission": "Waiting for your permission to use the geolocation...", - "waitingForLocation": "Searching your current location...", + "waitingForGeopermission": "Waiting for your permission to use the geolocation…", + "waitingForLocation": "Searching your current location…", "weekdays": { "abbreviations": { "friday": "Fri", @@ -416,6 +416,22 @@ "pleaseLogin": "Please log in to add a picture", "respectPrivacy": "Do not photograph people nor license plates. Do not upload Google Maps, Google Streetview or other copyrighted sources.", "toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}", + "upload": { + "failReasons": "You might have lost connection to the internet", + "failReasonsAdvanced": "Alternatively, make sure your browser and extensions do not block third-party API's.", + "multiple": { + "done": "{count} images are successfully uploaded. Thank you!", + "partiallyDone": "{count} images are getting uploaded, {done} images are done…", + "someFailed": "Sorry, we could not upload {count} images", + "uploading": "{count} images are getting uploaded…" + }, + "one": { + "done": "Your image was successfully uploaded. Thank you!", + "failed": "Sorry, we could not upload your image", + "retrying": "Your image is getting uploaded again…", + "uploading": "Your image is getting uploaded…" + } + }, "uploadDone": "Your picture has been added. Thanks for helping out!", "uploadFailed": "Could not upload your picture. Are you connected to the Internet, and allow third party API's? The Brave browser or the uMatrix plugin might block them.", "uploadMultipleDone": "{count} pictures have been added. Thanks for helping out!", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index f38fd858e..da51a2a7f 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -2682,26 +2682,6 @@ a.link-underline { } } -@media (prefers-reduced-motion: reduce) { - @-webkit-keyframes spin { - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - @keyframes spin { - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - .motion-reduce\:animate-spin { - -webkit-animation: spin 1s linear infinite; - animation: spin 1s linear infinite; - } -} - @media (max-width: 480px) { .max-\[480px\]\:w-full { width: 100%; diff --git a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index cd7522a29..c8186a8ba 100644 --- a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -7,7 +7,7 @@ import { OsmTags } from "../../../Models/OsmFeature" */ export default class FeaturePropertiesStore { private readonly _elements = new Map>>() - + public readonly aliases = new Map() constructor(...sources: FeatureSource[]) { for (const source of sources) { this.trackFeatureSource(source) @@ -92,7 +92,6 @@ export default class FeaturePropertiesStore { }) } - // noinspection JSUnusedGlobalSymbols public addAlias(oldId: string, newId: string): void { if (newId === undefined) { // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! @@ -112,6 +111,7 @@ export default class FeaturePropertiesStore { } element.data.id = newId this._elements.set(newId, element) + this.aliases.set(newId, oldId) element.ping() } diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index e69de29bb..9bb2f9535 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -0,0 +1,150 @@ +import { ImageUploader } from "./ImageUploader"; +import LinkImageAction from "../Osm/Actions/LinkImageAction"; +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; +import { OsmId, OsmTags } from "../../Models/OsmFeature"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import { Store, UIEventSource } from "../UIEventSource"; +import { OsmConnection } from "../Osm/OsmConnection"; +import { Changes } from "../Osm/Changes"; +import Translations from "../../UI/i18n/Translations"; + + +/** + * The ImageUploadManager has a + */ +export class ImageUploadManager { + + private readonly _uploader: ImageUploader; + private readonly _featureProperties: FeaturePropertiesStore; + private readonly _layout: LayoutConfig; + + private readonly _uploadStarted: Map> = new Map(); + private readonly _uploadFinished: Map> = new Map(); + private readonly _uploadFailed: Map> = new Map(); + private readonly _uploadRetried: Map> = new Map(); + private readonly _uploadRetriedSuccess: Map> = new Map(); + private readonly _osmConnection: OsmConnection; + private readonly _changes: Changes; + + constructor(layout: LayoutConfig, uploader: ImageUploader, featureProperties: FeaturePropertiesStore, osmConnection: OsmConnection, changes: Changes) { + this._uploader = uploader; + this._featureProperties = featureProperties; + this._layout = layout; + this._osmConnection = osmConnection; + this._changes = changes; + } + + /** + * Gets various counters. + * Note that counters can only increase + * If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased + * @param featureId: the id of the feature you want information for. '*' has a global counter + */ + public getCountsFor(featureId: string | "*"): { + retried: Store; + uploadStarted: Store; + retrySuccess: Store; + failed: Store; + uploadFinished: Store + } { + return { + uploadStarted: this.getCounterFor(this._uploadStarted, featureId), + uploadFinished: this.getCounterFor(this._uploadFinished, featureId), + retried: this.getCounterFor(this._uploadRetried, featureId), + failed: this.getCounterFor(this._uploadFailed, featureId), + retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId) + + }; + } + + /** + * Uploads the given image, applies the correct title and license for the known user + */ + public async uploadImageAndApply(file: File, tags: OsmTags) { + + const sizeInBytes = file.size + const featureId = tags.id + console.log(file.name + " has a size of " + sizeInBytes + " Bytes, attaching to", tags.id) + const self = this + if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { + this.increaseCountFor(this._uploadStarted, featureId) + this.increaseCountFor(this._uploadFailed, featureId) + throw( + Translations.t.image.toBig.Subs({ + actual_size: Math.floor(sizeInBytes / 1000000) + "MB", + max_size: self._uploader.maxFileSizeInMegabytes + "MB", + }).txt + ) + } + + + const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); + const license = licenseStore?.data ?? "CC0"; + + const matchingLayer = this._layout?.getMatchingLayer(tags); + + const title = + matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ?? + tags.name ?? + "https//osm.org/" + tags.id; + const description = [ + "author:" + this._osmConnection.userDetails.data.name, + "license:" + license, + "osmid:" + tags.id + ].join("\n"); + + console.log("Upload done, creating ") + const action = await this.uploadImageWithLicense(featureId, title, description, file); + await this._changes.applyAction(action); + } + + private async uploadImageWithLicense( + featureId: OsmId, + title: string, description: string, blob: File + ): Promise { + this.increaseCountFor(this._uploadStarted, featureId); + const properties = this._featureProperties.getStore(featureId); + let key: string; + let value: string; + try { + ({ key, value } = await this._uploader.uploadImage(title, description, blob)); + } catch (e) { + this.increaseCountFor(this._uploadRetried, featureId); + console.error("Could not upload image, trying again:", e); + try { + + ({ key, value } = await this._uploader.uploadImage(title, description, blob)); + this.increaseCountFor(this._uploadRetriedSuccess, featureId); + } catch (e) { + console.error("Could again not upload image due to", e); + this.increaseCountFor(this._uploadFailed, featureId); + } + + } + console.log("Uploading done, creating action for", featureId) + const action = new LinkImageAction(featureId, key, value, properties, { + theme: this._layout.id, + changeType: "add-image" + }); + this.increaseCountFor(this._uploadFinished, featureId); + return action; + } + + private getCounterFor(collection: Map>, key: string | "*") { + if (this._featureProperties.aliases.has(key)) { + key = this._featureProperties.aliases.get(key); + } + if (!collection.has(key)) { + collection.set(key, new UIEventSource(0)); + } + return collection.get(key); + } + + private increaseCountFor(collection: Map>, key: string | "*") { + const counter = this.getCounterFor(collection, key); + counter.setData(counter.data + 1); + const global = this.getCounterFor(collection, "*"); + global.setData(counter.data + 1); + } + +} diff --git a/src/Logic/ImageProviders/ImageUploader.ts b/src/Logic/ImageProviders/ImageUploader.ts index e69de29bb..3efb8d279 100644 --- a/src/Logic/ImageProviders/ImageUploader.ts +++ b/src/Logic/ImageProviders/ImageUploader.ts @@ -0,0 +1,15 @@ +export interface ImageUploader { + maxFileSizeInMegabytes?: number; + /** + * Uploads the 'blob' as image, with some metadata. + * Returns the URL to be linked + the appropriate key to add this to OSM + * @param title + * @param description + * @param blob + */ + uploadImage( + title: string, + description: string, + blob: File + ): Promise<{ key: string, value: string }>; +} diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index a7a142733..4e4a1c541 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -1,60 +1,30 @@ -import ImageProvider, { ProvidedImage } from "./ImageProvider" -import BaseUIElement from "../../UI/BaseUIElement" -import { Utils } from "../../Utils" -import Constants from "../../Models/Constants" -import { LicenseInfo } from "./LicenseInfo" +import ImageProvider, { ProvidedImage } from "./ImageProvider"; +import BaseUIElement from "../../UI/BaseUIElement"; +import { Utils } from "../../Utils"; +import Constants from "../../Models/Constants"; +import { LicenseInfo } from "./LicenseInfo"; +import { ImageUploader } from "./ImageUploader"; -export class Imgur extends ImageProvider { +export class Imgur extends ImageProvider implements ImageUploader{ public static readonly defaultValuePrefix = ["https://i.imgur.com"] public static readonly singleton = new Imgur() public readonly defaultKeyPrefixes: string[] = ["image"] - + public readonly maxFileSizeInMegabytes = 10 private constructor() { super() } - static uploadMultiple( + /** + * Uploads an image, returns the URL where to find the image + * @param title + * @param description + * @param blob + */ + public async uploadImage( title: string, description: string, - blobs: FileList, - handleSuccessfullUpload: (imageURL: string) => Promise, - allDone: () => void, - onFail: (reason: string) => void, - offset: number = 0 - ) { - if (blobs.length == offset) { - allDone() - return - } - const blob = blobs.item(offset) - const self = this - this.uploadImage( - title, - description, - blob, - async (imageUrl) => { - await handleSuccessfullUpload(imageUrl) - self.uploadMultiple( - title, - description, - blobs, - handleSuccessfullUpload, - allDone, - onFail, - offset + 1 - ) - }, - onFail - ) - } - - static uploadImage( - title: string, - description: string, - blob: File, - handleSuccessfullUpload: (imageURL: string) => Promise, - onFail: (reason: string) => void - ) { + blob: File + ): Promise<{ key: string, value: string }> { const apiUrl = "https://api.imgur.com/3/image" const apiKey = Constants.ImgurApiKey @@ -63,6 +33,7 @@ export class Imgur extends ImageProvider { formData.append("title", title) formData.append("description", description) + const settings: RequestInit = { method: "POST", body: formData, @@ -74,17 +45,9 @@ export class Imgur extends ImageProvider { } // Response contains stringified JSON - // Image URL available at response.data.link - fetch(apiUrl, settings) - .then(async function (response) { - const content = await response.json() - await handleSuccessfullUpload(content.data.link) - }) - .catch((reason) => { - console.log("Uploading to IMGUR failed", reason) - // @ts-ignore - onFail(reason) - }) + const response = await fetch(apiUrl, settings) + const content = await response.json() + return { key: "image", value: content.data.link } } SourceIcon(): BaseUIElement { diff --git a/src/Logic/Osm/Actions/LinkImageAction.ts b/src/Logic/Osm/Actions/LinkImageAction.ts index 014a836a0..1b2b90d19 100644 --- a/src/Logic/Osm/Actions/LinkImageAction.ts +++ b/src/Logic/Osm/Actions/LinkImageAction.ts @@ -1,11 +1,20 @@ -import ChangeTagAction from "./ChangeTagAction" -import { Tag } from "../../Tags/Tag" +import ChangeTagAction from "./ChangeTagAction"; +import { Tag } from "../../Tags/Tag"; +import OsmChangeAction from "./OsmChangeAction"; +import { Changes } from "../Changes"; +import { ChangeDescription } from "./ChangeDescription"; +import { Store } from "../../UIEventSource"; + +export default class LinkImageAction extends OsmChangeAction { + private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; + private readonly _url: string; + private readonly _currentTags: Store>; + private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; -export default class LinkPicture extends ChangeTagAction { /** - * Adds a link to an image + * Adds an image-link to a feature * @param elementId - * @param proposedKey: a key which might be used, typically `image`. If the key is already used with a different URL, `key+":0"` will be used instead (or a higher number if needed) + * @param proposedKey a key which might be used, typically `image`. If the key is already used with a different URL, `key+":0"` will be used instead (or a higher number if needed) * @param url * @param currentTags * @param meta @@ -15,18 +24,31 @@ export default class LinkPicture extends ChangeTagAction { elementId: string, proposedKey: "image" | "mapillary" | "wiki_commons" | string, url: string, - currentTags: Record, + currentTags: Store>, meta: { theme: string changeType: "add-image" | "link-image" } ) { - let key = proposedKey + super(elementId, true) + this._proposedKey = proposedKey; + this._url = url; + this._currentTags = currentTags; + this._meta = meta; + } + + protected CreateChangeDescriptions(): Promise { + let key = this._proposedKey let i = 0 + const currentTags = this._currentTags.data + const url = this._url while (currentTags[key] !== undefined && currentTags[key] !== url) { - key = proposedKey + ":" + i + key = this._proposedKey + ":" + i i++ } - super(elementId, new Tag(key, url), currentTags, meta) + const tagChangeAction = new ChangeTagAction ( this.mainObjectId, new Tag(key, url), currentTags, this._meta) + return tagChangeAction.CreateChangeDescriptions() } + + } diff --git a/src/Logic/Osm/Actions/OsmChangeAction.ts b/src/Logic/Osm/Actions/OsmChangeAction.ts index 4161dc967..2bf31b02c 100644 --- a/src/Logic/Osm/Actions/OsmChangeAction.ts +++ b/src/Logic/Osm/Actions/OsmChangeAction.ts @@ -19,6 +19,9 @@ export default abstract class OsmChangeAction { constructor(mainObjectId: string, trackStatistics: boolean = true) { this.trackStatistics = trackStatistics this.mainObjectId = mainObjectId + if(mainObjectId === undefined || mainObjectId === null){ + throw "OsmObject received '"+mainObjectId+"' as mainObjectId" + } } public async Perform(changes: Changes) { diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index 8c276be2c..867ef45d5 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -1,22 +1,22 @@ -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { OsmConnection } from "../Osm/OsmConnection" -import { MangroveIdentity } from "../Web/MangroveReviews" -import { Store, Stores, UIEventSource } from "../UIEventSource" -import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" -import { FeatureSource } from "../FeatureSource/FeatureSource" -import { Feature } from "geojson" -import { Utils } from "../../Utils" -import translators from "../../assets/translators.json" -import codeContributors from "../../assets/contributors.json" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" -import usersettings from "../../../src/assets/generated/layers/usersettings.json" -import Locale from "../../UI/i18n/Locale" -import LinkToWeblate from "../../UI/Base/LinkToWeblate" -import FeatureSwitchState from "./FeatureSwitchState" -import Constants from "../../Models/Constants" -import { QueryParameters } from "../Web/QueryParameters" -import { ThemeMetaTagging } from "./UserSettingsMetaTagging" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import { OsmConnection } from "../Osm/OsmConnection"; +import { MangroveIdentity } from "../Web/MangroveReviews"; +import { Store, Stores, UIEventSource } from "../UIEventSource"; +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; +import { FeatureSource } from "../FeatureSource/FeatureSource"; +import { Feature } from "geojson"; +import { Utils } from "../../Utils"; +import translators from "../../assets/translators.json"; +import codeContributors from "../../assets/contributors.json"; +import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import usersettings from "../../../src/assets/generated/layers/usersettings.json"; +import Locale from "../../UI/i18n/Locale"; +import LinkToWeblate from "../../UI/Base/LinkToWeblate"; +import FeatureSwitchState from "./FeatureSwitchState"; +import Constants from "../../Models/Constants"; +import { QueryParameters } from "../Web/QueryParameters"; +import { ThemeMetaTagging } from "./UserSettingsMetaTagging"; import { MapProperties } from "../../Models/MapProperties"; /** @@ -43,7 +43,7 @@ export default class UserRelatedState { public readonly homeLocation: FeatureSource public readonly language: UIEventSource public readonly preferredBackgroundLayer: UIEventSource - public readonly preferredBackgroundLayerForTheme: UIEventSource + public readonly imageLicense : UIEventSource /** * The number of seconds that the GPS-locations are stored in memory. * Time in seconds @@ -108,6 +108,9 @@ export default class UserRelatedState { documentation: "The ID of a layer or layer category that MapComplete uses by default" }) + this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { + documentation: "The license under which new images are uploaded" + }) this.installedUserThemes = this.InitInstalledUserThemes() this.homeLocation = this.initHomeLocation() diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index caa3b6357..b6f13012b 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -51,6 +51,8 @@ import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"; import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"; import FilteredLayer from "./FilteredLayer"; import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { Imgur } from "../Logic/ImageProviders/Imgur"; /** * @@ -99,6 +101,8 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly userRelatedState: UserRelatedState; readonly geolocation: GeoLocationHandler; + readonly imageUploadManager: ImageUploadManager + readonly lastClickObject: WritableFeatureSource; readonly overlayLayerStates: ReadonlyMap< string, @@ -168,6 +172,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location); + const self = this; this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id); @@ -323,6 +328,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.perLayerFiltered = this.showNormalDataOn(this.map); this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView; + this.imageUploadManager = new ImageUploadManager(layout, Imgur.singleton, this.featureProperties, this.osmConnection, this.changes) this.initActors(); this.addLastClick(lastClick); diff --git a/src/UI/Base/FileSelector.svelte b/src/UI/Base/FileSelector.svelte index e69de29bb..fa4dc2ad6 100644 --- a/src/UI/Base/FileSelector.svelte +++ b/src/UI/Base/FileSelector.svelte @@ -0,0 +1,40 @@ + + +
+ + { + drawAttention = false; + dispatcher("submit", inputElement.files)}} + + on:dragend={ () => {drawAttention = false}} + on:dragover|preventDefault|stopPropagation={(e) => { + console.log("Dragging over!") + drawAttention = true + e.dataTransfer.drop = "copy" + }} + on:dragstart={ () => {drawAttention = false}} + on:drop|preventDefault|stopPropagation={(e) => { + console.log("Got a 'drop'") + drawAttention = false + dispatcher("submit", e.dataTransfer.files) + }} + type="file" + > +
diff --git a/src/UI/Base/Loading.svelte b/src/UI/Base/Loading.svelte index 097bc4472..ff8a622d7 100644 --- a/src/UI/Base/Loading.svelte +++ b/src/UI/Base/Loading.svelte @@ -1,9 +1,12 @@ - -
+
diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts index 5c3f6b5c6..2a90ab66a 100644 --- a/src/UI/Image/ImageUploadFlow.ts +++ b/src/UI/Image/ImageUploadFlow.ts @@ -15,8 +15,9 @@ import Loading from "../Base/Loading" import { LoginToggle } from "../Popup/LoginButton" import Constants from "../../Models/Constants" import { SpecialVisualizationState } from "../SpecialVisualization" +import exp from "constants"; -export class ImageUploadFlow extends Toggle { +export class ImageUploadFlow extends Combine { private static readonly uploadCountsPerId = new Map>() constructor( @@ -129,7 +130,7 @@ export class ImageUploadFlow extends Toggle { uploader.uploadMany(title, description, filelist) }) - const uploadFlow: BaseUIElement = new Combine([ + super([ new VariableUiElement( uploader.queue .map((q) => q.length) @@ -183,17 +184,9 @@ export class ImageUploadFlow extends Toggle { }) .SetClass("underline"), ]).SetStyle("font-size:small;"), - ]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") + ]) + this.SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") + - super( - new LoginToggle( - /*We can show the actual upload button!*/ - uploadFlow, - /* User not logged in*/ t.pleaseLogin.Clone(), - state - ), - undefined /* Nothing as the user badge is disabled*/, - state?.featureSwitchUserbadge - ) } } diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte index e69de29bb..fa82ee34c 100644 --- a/src/UI/Image/UploadImage.svelte +++ b/src/UI/Image/UploadImage.svelte @@ -0,0 +1,73 @@ + + + + + + +
+ + + handleFiles(e.detail)}> +
+ + {#if image !== undefined} + + {:else} + + {/if} + {#if labelText} + {labelText} + {:else} + + {/if} +
+
+ +
+ +
diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte index a3bfa02e5..0c1b6f777 100644 --- a/src/UI/Image/UploadingImageCounter.svelte +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -1,31 +1,67 @@ +{#if $uploadStarted == 1} + {#if $uploadFinished == 1 } + + {:else if $failed == 1} +
+ + + +
+ {:else if $retried == 1} + + + + {:else } + + + + {/if} +{:else if $uploadStarted > 1} + {#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0} + + {:else if $uploadFinished == 0} + + + + {:else if $uploadFinished > 0} + + + + {/if} + {#if $failed > 0} +
+ {#if failed === 1} + + {:else} + - - - - - + {/if} + + +
+ {/if} +{/if} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 4cb3aeb02..1d3575b42 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -1,113 +1,117 @@ -import { Store, UIEventSource } from "../Logic/UIEventSource" -import BaseUIElement from "./BaseUIElement" -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { Changes } from "../Logic/Osm/Changes" -import { ExportableMap, MapProperties } from "../Models/MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Geometry, Point } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { MangroveIdentity } from "../Logic/Web/MangroveReviews" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { MenuState } from "../Models/MenuState" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" -import { RasterLayerPolygon } from "../Models/RasterLayers" +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import BaseUIElement from "./BaseUIElement"; +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { Changes } from "../Logic/Osm/Changes"; +import { ExportableMap, MapProperties } from "../Models/MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Geometry, Point } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { MenuState } from "../Models/MenuState"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import { RasterLayerPolygon } from "../Models/RasterLayers"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; /** * The state needed to render a special Visualisation. */ export interface SpecialVisualizationState { - readonly guistate: MenuState - readonly layout: LayoutConfig - readonly featureSwitches: FeatureSwitchState + readonly guistate: MenuState; + readonly layout: LayoutConfig; + readonly featureSwitches: FeatureSwitchState; - readonly layerState: LayerState - readonly featureProperties: { getStore(id: string): UIEventSource> } + readonly layerState: LayerState; + readonly featureProperties: { getStore(id: string): UIEventSource> }; - readonly indexedFeatures: IndexedFeatureSource + readonly indexedFeatures: IndexedFeatureSource; - /** - * Some features will create a new element that should be displayed. - * These can be injected by appending them to this featuresource (and pinging it) - */ - readonly newFeatures: WritableFeatureSource + /** + * Some features will create a new element that should be displayed. + * These can be injected by appending them to this featuresource (and pinging it) + */ + readonly newFeatures: WritableFeatureSource; - readonly historicalUserLocations: WritableFeatureSource> + readonly historicalUserLocations: WritableFeatureSource>; - readonly osmConnection: OsmConnection - readonly featureSwitchUserbadge: Store - readonly featureSwitchIsTesting: Store - readonly changes: Changes - readonly osmObjectDownloader: OsmObjectDownloader - /** - * State of the main map - */ - readonly mapProperties: MapProperties & ExportableMap + readonly osmConnection: OsmConnection; + readonly featureSwitchUserbadge: Store; + readonly featureSwitchIsTesting: Store; + readonly changes: Changes; + readonly osmObjectDownloader: OsmObjectDownloader; + /** + * State of the main map + */ + readonly mapProperties: MapProperties & ExportableMap; - readonly selectedElement: UIEventSource - /** - * Works together with 'selectedElement' to indicate what properties should be displayed - */ - readonly selectedLayer: UIEventSource - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> + readonly selectedElement: UIEventSource; + /** + * Works together with 'selectedElement' to indicate what properties should be displayed + */ + readonly selectedLayer: UIEventSource; + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; - /** - * If data is currently being fetched from external sources - */ - readonly dataIsLoading: Store - /** - * Only needed for 'ReplaceGeometryAction' - */ - readonly fullNodeDatabase?: FullNodeDatabaseSource + /** + * If data is currently being fetched from external sources + */ + readonly dataIsLoading: Store; + /** + * Only needed for 'ReplaceGeometryAction' + */ + readonly fullNodeDatabase?: FullNodeDatabaseSource; - readonly perLayer: ReadonlyMap - readonly userRelatedState: { - readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> - readonly mangroveIdentity: MangroveIdentity - readonly showAllQuestionsAtOnce: UIEventSource - readonly preferencesAsTags: Store> - readonly language: UIEventSource - } - readonly lastClickObject: WritableFeatureSource + readonly perLayer: ReadonlyMap; + readonly userRelatedState: { + readonly imageLicense: UIEventSource; + readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> + readonly mangroveIdentity: MangroveIdentity + readonly showAllQuestionsAtOnce: UIEventSource + readonly preferencesAsTags: Store> + readonly language: UIEventSource + }; + readonly lastClickObject: WritableFeatureSource; - readonly availableLayers: Store + readonly availableLayers: Store; + + readonly imageUploadManager: ImageUploadManager; } export interface SpecialVisualization { - readonly funcName: string - readonly docs: string | BaseUIElement - readonly example?: string + readonly funcName: string; + readonly docs: string | BaseUIElement; + readonly example?: string; - /** - * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included - */ - readonly needsNodeDatabase?: boolean - readonly args: { - name: string - defaultValue?: string - doc: string - required?: false | boolean - }[] - readonly getLayerDependencies?: (argument: string[]) => string[] + /** + * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included + */ + readonly needsNodeDatabase?: boolean; + readonly args: { + name: string + defaultValue?: string + doc: string + required?: false | boolean + }[]; + readonly getLayerDependencies?: (argument: string[]) => string[]; - structuredExamples?(): { feature: Feature>; args: string[] }[] + structuredExamples?(): { feature: Feature>; args: string[] }[]; - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement; } export type RenderingSpecification = - | string - | { - func: SpecialVisualization - args: string[] - style: string - } + | string + | { + func: SpecialVisualization + args: string[] + style: string +} diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 0d9e1d66f..24b712da2 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -35,7 +35,6 @@ import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" import { SubtleButton } from "./Base/SubtleButton" import Svg from "../Svg" import NoteCommentElement from "./Popup/NoteCommentElement" -import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" import FileSelectorButton from "./Input/FileSelectorButton" import { LoginToggle } from "./Popup/LoginButton" import Toggle from "./Input/Toggle" @@ -74,6 +73,7 @@ import FediverseValidator from "./InputElement/Validators/FediverseValidator" import SendEmail from "./Popup/SendEmail.svelte" import NearbyImages from "./Popup/NearbyImages.svelte" import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" +import UploadImage from "./Image/UploadImage.svelte"; class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -616,16 +616,19 @@ export default class SpecialVisualizations { { name: "image-key", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", - defaultValue: "image", + required: false }, { name: "label", doc: "The text to show on the button", - defaultValue: "Add image", + required: false }, ], constr: (state, tags, args) => { - return new ImageUploadFlow(tags, state, args[0], args[1]) + return new SvelteUIElement(UploadImage, { + state,tags, labelText: args[1], image: args[0] + }) + // return new ImageUploadFlow(tags, state, args[0], args[1]) }, }, { From 9a5a2e9924f668af8666bc156a4788850dda429a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:55:43 +0200 Subject: [PATCH 078/133] Refactoring: port add-image-to-note to new element as well, remove obsolete classes, fix note creation --- .../ImageProviders/ImageUploadManager.ts | 45 +- src/Logic/Osm/Actions/LinkImageAction.ts | 2 +- src/Logic/Osm/OsmConnection.ts | 1008 +++++++++-------- src/UI/Image/ImageUploadFlow.ts | 192 ---- src/UI/Image/UploadImage.svelte | 6 +- src/UI/Input/FileSelectorButton.ts | 111 -- src/UI/Input/Slider.ts | 62 - src/UI/Popup/CreateNewNote.svelte | 5 + src/UI/SpecialVisualization.ts | 3 +- src/UI/SpecialVisualizations.ts | 184 ++- 10 files changed, 617 insertions(+), 1001 deletions(-) delete mode 100644 src/UI/Image/ImageUploadFlow.ts delete mode 100644 src/UI/Input/FileSelectorButton.ts delete mode 100644 src/UI/Input/Slider.ts diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index 9bb2f9535..85964a507 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -7,6 +7,7 @@ import { Store, UIEventSource } from "../UIEventSource"; import { OsmConnection } from "../Osm/OsmConnection"; import { Changes } from "../Osm/Changes"; import Translations from "../../UI/i18n/Translations"; +import NoteCommentElement from "../../UI/Popup/NoteCommentElement"; /** @@ -58,24 +59,25 @@ export class ImageUploadManager { } /** - * Uploads the given image, applies the correct title and license for the known user + * Uploads the given image, applies the correct title and license for the known user. + * Will then add this image to the OSM-feature or the OSM-note */ - public async uploadImageAndApply(file: File, tags: OsmTags) { + public async uploadImageAndApply(file: File, tagsStore: UIEventSource) : Promise{ - const sizeInBytes = file.size - const featureId = tags.id - console.log(file.name + " has a size of " + sizeInBytes + " Bytes, attaching to", tags.id) - const self = this - if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { - this.increaseCountFor(this._uploadStarted, featureId) - this.increaseCountFor(this._uploadFailed, featureId) - throw( - Translations.t.image.toBig.Subs({ - actual_size: Math.floor(sizeInBytes / 1000000) + "MB", - max_size: self._uploader.maxFileSizeInMegabytes + "MB", - }).txt - ) - } + const sizeInBytes = file.size; + const tags= tagsStore.data + const featureId = tags.id; + const self = this; + if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { + this.increaseCountFor(this._uploadStarted, featureId); + this.increaseCountFor(this._uploadFailed, featureId); + throw ( + Translations.t.image.toBig.Subs({ + actual_size: Math.floor(sizeInBytes / 1000000) + "MB", + max_size: self._uploader.maxFileSizeInMegabytes + "MB" + }).txt + ); + } const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); @@ -93,8 +95,15 @@ export class ImageUploadManager { "osmid:" + tags.id ].join("\n"); - console.log("Upload done, creating ") + console.log("Upload done, creating "); const action = await this.uploadImageWithLicense(featureId, title, description, file); + if(!isNaN(Number( featureId))){ + // THis is a map note + const url = action._url + await this._osmConnection.addCommentToNote(featureId, url) + NoteCommentElement.addCommentTo(url, > tagsStore, {osmConnection: this._osmConnection}) + return + } await this._changes.applyAction(action); } @@ -121,7 +130,7 @@ export class ImageUploadManager { } } - console.log("Uploading done, creating action for", featureId) + console.log("Uploading done, creating action for", featureId); const action = new LinkImageAction(featureId, key, value, properties, { theme: this._layout.id, changeType: "add-image" diff --git a/src/Logic/Osm/Actions/LinkImageAction.ts b/src/Logic/Osm/Actions/LinkImageAction.ts index 1b2b90d19..7d4ec23c8 100644 --- a/src/Logic/Osm/Actions/LinkImageAction.ts +++ b/src/Logic/Osm/Actions/LinkImageAction.ts @@ -7,7 +7,7 @@ import { Store } from "../../UIEventSource"; export default class LinkImageAction extends OsmChangeAction { private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; - private readonly _url: string; + public readonly _url: string; private readonly _currentTags: Store>; private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index e650250a9..07028c35a 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -1,551 +1,553 @@ // @ts-ignore -import { osmAuth } from "osm-auth" -import { Store, Stores, UIEventSource } from "../UIEventSource" -import { OsmPreferences } from "./OsmPreferences" -import { Utils } from "../../Utils" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import * as config from "../../../package.json" -export default class UserDetails { - public loggedIn = false - public name = "Not logged in" - public uid: number - public csCount = 0 - public img?: string - public unreadMessages = 0 - public totalMessages: number = 0 - public home: { lon: number; lat: number } - public backend: string - public account_created: string - public tracesCount: number = 0 - public description: string +import { osmAuth } from "osm-auth"; +import { Store, Stores, UIEventSource } from "../UIEventSource"; +import { OsmPreferences } from "./OsmPreferences"; +import { Utils } from "../../Utils"; +import { LocalStorageSource } from "../Web/LocalStorageSource"; +import * as config from "../../../package.json"; - constructor(backend: string) { - this.backend = backend - } +export default class UserDetails { + public loggedIn = false; + public name = "Not logged in"; + public uid: number; + public csCount = 0; + public img?: string; + public unreadMessages = 0; + public totalMessages: number = 0; + public home: { lon: number; lat: number }; + public backend: string; + public account_created: string; + public tracesCount: number = 0; + public description: string; + + constructor(backend: string) { + this.backend = backend; + } } export interface AuthConfig { - "#"?: string // optional comment - oauth_client_id: string - oauth_secret: string - url: string + "#"?: string; // optional comment + oauth_client_id: string; + oauth_secret: string; + url: string; } export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export class OsmConnection { - public static readonly oauth_configs: Record = - config.config.oauth_credentials - public auth - public userDetails: UIEventSource - public isLoggedIn: Store - public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" - ) - public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" - ) + public static readonly oauth_configs: Record = + config.config.oauth_credentials; + public auth; + public userDetails: UIEventSource; + public isLoggedIn: Store; + public gpxServiceIsOnline: UIEventSource = new UIEventSource( + "unknown" + ); + public apiIsOnline: UIEventSource = new UIEventSource( + "unknown" + ); - public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" - ) - public preferencesHandler: OsmPreferences - public readonly _oauth_config: AuthConfig - private readonly _dryRun: Store - private fakeUser: boolean - private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] - private readonly _iframeMode: Boolean | boolean - private readonly _singlePage: boolean - private isChecking = false + public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( + "not-attempted" + ); + public preferencesHandler: OsmPreferences; + public readonly _oauth_config: AuthConfig; + private readonly _dryRun: Store; + private fakeUser: boolean; + private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; + private readonly _iframeMode: Boolean | boolean; + private readonly _singlePage: boolean; + private isChecking = false; - constructor(options?: { - dryRun?: Store - fakeUser?: false | boolean - oauth_token?: UIEventSource - // Used to keep multiple changesets open and to write to the correct changeset - singlePage?: boolean - osmConfiguration?: "osm" | "osm-test" - attemptLogin?: true | boolean - }) { - options = options ?? {} - this.fakeUser = options.fakeUser ?? false - this._singlePage = options.singlePage ?? true - this._oauth_config = - OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? - OsmConnection.oauth_configs.osm - console.debug("Using backend", this._oauth_config.url) - this._iframeMode = Utils.runningFromConsole ? false : window !== window.top + constructor(options?: { + dryRun?: Store + fakeUser?: false | boolean + oauth_token?: UIEventSource + // Used to keep multiple changesets open and to write to the correct changeset + singlePage?: boolean + osmConfiguration?: "osm" | "osm-test" + attemptLogin?: true | boolean + }) { + options = options ?? {}; + this.fakeUser = options.fakeUser ?? false; + this._singlePage = options.singlePage ?? true; + this._oauth_config = + OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? + OsmConnection.oauth_configs.osm; + console.debug("Using backend", this._oauth_config.url); + this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; - // Check if there are settings available in environment variables, and if so, use those - if ( - import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && - import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined - ) { - console.debug("Using environment variables for oauth config") - this._oauth_config = { - oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, - oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, - url: "https://api.openstreetmap.org", - } - } - - this.userDetails = new UIEventSource( - new UserDetails(this._oauth_config.url), - "userDetails" - ) - if (options.fakeUser) { - const ud = this.userDetails.data - ud.csCount = 5678 - ud.loggedIn = true - ud.unreadMessages = 0 - ud.name = "Fake user" - ud.totalMessages = 42 - } - const self = this - this.UpdateCapabilities() - this.isLoggedIn = this.userDetails.map( - (user) => - user.loggedIn && - (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), - [this.apiIsOnline] - ) - this.isLoggedIn.addCallback((isLoggedIn) => { - if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { - // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do - // This means someone attempted to toggle this; so we attempt to login! - self.AttemptLogin() - } - }) - - this._dryRun = options.dryRun ?? new UIEventSource(false) - - this.updateAuthObject() - - this.preferencesHandler = new OsmPreferences( - this.auth, - this - ) - - if (options.oauth_token?.data !== undefined) { - console.log(options.oauth_token.data) - const self = this - this.auth.bootstrapToken( - options.oauth_token.data, - (x) => { - console.log("Called back: ", x) - self.AttemptLogin() - }, - this.auth - ) - - options.oauth_token.setData(undefined) - } - if (this.auth.authenticated() && options.attemptLogin !== false) { - this.AttemptLogin() // Also updates the user badge - } else { - console.log("Not authenticated") - } + // Check if there are settings available in environment variables, and if so, use those + if ( + import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && + import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined + ) { + console.debug("Using environment variables for oauth config"); + this._oauth_config = { + oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, + oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, + url: "https://api.openstreetmap.org" + }; } - public GetPreference( - key: string, - defaultValue: string = undefined, - options?: { - documentation?: string - prefix?: string - } - ): UIEventSource { - return this.preferencesHandler.GetPreference(key, defaultValue, options) + this.userDetails = new UIEventSource( + new UserDetails(this._oauth_config.url), + "userDetails" + ); + if (options.fakeUser) { + const ud = this.userDetails.data; + ud.csCount = 5678; + ud.loggedIn = true; + ud.unreadMessages = 0; + ud.name = "Fake user"; + ud.totalMessages = 42; } + const self = this; + this.UpdateCapabilities(); + this.isLoggedIn = this.userDetails.map( + (user) => + user.loggedIn && + (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), + [this.apiIsOnline] + ); + this.isLoggedIn.addCallback((isLoggedIn) => { + if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { + // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do + // This means someone attempted to toggle this; so we attempt to login! + self.AttemptLogin(); + } + }); - public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { - return this.preferencesHandler.GetLongPreference(key, prefix) + this._dryRun = options.dryRun ?? new UIEventSource(false); + + this.updateAuthObject(); + + this.preferencesHandler = new OsmPreferences( + this.auth, + this + ); + + if (options.oauth_token?.data !== undefined) { + console.log(options.oauth_token.data); + const self = this; + this.auth.bootstrapToken( + options.oauth_token.data, + (x) => { + console.log("Called back: ", x); + self.AttemptLogin(); + }, + this.auth + ); + + options.oauth_token.setData(undefined); } - - public OnLoggedIn(action: (userDetails: UserDetails) => void) { - this._onLoggedIn.push(action) + if (this.auth.authenticated() && options.attemptLogin !== false) { + this.AttemptLogin(); // Also updates the user badge + } else { + console.log("Not authenticated"); } + } - public LogOut() { - this.auth.logout() - this.userDetails.data.loggedIn = false - this.userDetails.data.csCount = 0 - this.userDetails.data.name = "" - this.userDetails.ping() - console.log("Logged out") - this.loadingStatus.setData("not-attempted") + public GetPreference( + key: string, + defaultValue: string = undefined, + options?: { + documentation?: string + prefix?: string } + ): UIEventSource { + return this.preferencesHandler.GetPreference(key, defaultValue, options); + } - /** - * The backend host, without path or trailing '/' - * - * new OsmConnection().Backend() // => "https://www.openstreetmap.org" - */ - public Backend(): string { - return this._oauth_config.url + public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { + return this.preferencesHandler.GetLongPreference(key, prefix); + } + + public OnLoggedIn(action: (userDetails: UserDetails) => void) { + this._onLoggedIn.push(action); + } + + public LogOut() { + this.auth.logout(); + this.userDetails.data.loggedIn = false; + this.userDetails.data.csCount = 0; + this.userDetails.data.name = ""; + this.userDetails.ping(); + console.log("Logged out"); + this.loadingStatus.setData("not-attempted"); + } + + /** + * The backend host, without path or trailing '/' + * + * new OsmConnection().Backend() // => "https://www.openstreetmap.org" + */ + public Backend(): string { + return this._oauth_config.url; + } + + public AttemptLogin() { + this.UpdateCapabilities(); + this.loadingStatus.setData("loading"); + if (this.fakeUser) { + this.loadingStatus.setData("logged-in"); + console.log("AttemptLogin called, but ignored as fakeUser is set"); + return; } - - public AttemptLogin() { - this.UpdateCapabilities() - this.loadingStatus.setData("loading") - if (this.fakeUser) { - this.loadingStatus.setData("logged-in") - console.log("AttemptLogin called, but ignored as fakeUser is set") - return - } - const self = this - console.log("Trying to log in...") - this.updateAuthObject() - LocalStorageSource.Get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href - ) - this.auth.xhr( - { - method: "GET", - path: "/api/0.6/user/details", - }, - function (err, details) { - if (err != null) { - console.log(err) - self.loadingStatus.setData("error") - if (err.status == 401) { - console.log("Clearing tokens...") - // Not authorized - our token probably got revoked - self.auth.logout() - self.LogOut() - } - return - } - - if (details == null) { - self.loadingStatus.setData("error") - return - } - - self.CheckForMessagesContinuously() - - // details is an XML DOM of user details - let userInfo = details.getElementsByTagName("user")[0] - - let data = self.userDetails.data - data.loggedIn = true - console.log("Login completed, userinfo is ", userInfo) - data.name = userInfo.getAttribute("display_name") - data.account_created = userInfo.getAttribute("account_created") - data.uid = Number(userInfo.getAttribute("id")) - data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 - ) - data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 - ) - - data.img = undefined - const imgEl = userInfo.getElementsByTagName("img") - if (imgEl !== undefined && imgEl[0] !== undefined) { - data.img = imgEl[0].getAttribute("href") - } - - const description = userInfo.getElementsByTagName("description") - if (description !== undefined && description[0] !== undefined) { - data.description = description[0]?.innerHTML - } - const homeEl = userInfo.getElementsByTagName("home") - if (homeEl !== undefined && homeEl[0] !== undefined) { - const lat = parseFloat(homeEl[0].getAttribute("lat")) - const lon = parseFloat(homeEl[0].getAttribute("lon")) - data.home = { lat: lat, lon: lon } - } - - self.loadingStatus.setData("logged-in") - const messages = userInfo - .getElementsByTagName("messages")[0] - .getElementsByTagName("received")[0] - data.unreadMessages = parseInt(messages.getAttribute("unread")) - data.totalMessages = parseInt(messages.getAttribute("count")) - - self.userDetails.ping() - for (const action of self._onLoggedIn) { - action(self.userDetails.data) - } - self._onLoggedIn = [] - } - ) - } - - /** - * Interact with the API. - * - * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' - */ - public async interact( - path: string, - method: "GET" | "POST" | "PUT" | "DELETE", - header?: Record, - content?: string - ): Promise { - return new Promise((ok, error) => { - this.auth.xhr( - { - method, - options: { - header, - }, - content, - path: `/api/0.6/${path}`, - }, - function (err, response) { - if (err !== null) { - error(err) - } else { - ok(response) - } - } - ) - }) - } - - public async post( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "POST", header, content) - } - - public async put( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "PUT", header, content) - } - - public async get(path: string, header?: Record): Promise { - return await this.interact(path, "GET", header) - } - - public closeNote(id: number | string, text?: string): Promise { - let textSuffix = "" - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text) - } - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) - return new Promise((ok) => { - ok() - }) - } - return this.post(`notes/${id}/close${textSuffix}`) - } - - public reopenNote(id: number | string, text?: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) - return new Promise((ok) => { - ok() - }) - } - let textSuffix = "" - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text) - } - return this.post(`notes/${id}/reopen${textSuffix}`) - } - - public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually opening note with text ", text) - return new Promise<{ id: number }>((ok) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ) - }) - } - const content = { lat, lon, text } - const response = await this.post("notes.json", JSON.stringify(content), { - "Content-Type": "application/json", - }) - const parsed = JSON.parse(response) - const id = parsed.properties - console.log("OPENED NOTE", id) - return id - } - - public async uploadGpxTrack( - gpx: string, - options: { - description: string - visibility: "private" | "public" | "trackable" | "identifiable" - filename?: string - /** - * Some words to give some properties; - * - * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. - */ - labels: string[] - } - ): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually uploading GPX ", gpx) - return new Promise<{ id: number }>((ok, error) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ) - }) + const self = this; + console.log("Trying to log in..."); + this.updateAuthObject(); + LocalStorageSource.Get("location_before_login").setData( + Utils.runningFromConsole ? undefined : window.location.href + ); + this.auth.xhr( + { + method: "GET", + path: "/api/0.6/user/details" + }, + function(err, details) { + if (err != null) { + console.log(err); + self.loadingStatus.setData("error"); + if (err.status == 401) { + console.log("Clearing tokens..."); + // Not authorized - our token probably got revoked + self.auth.logout(); + self.LogOut(); + } + return; } - const contents = { - file: gpx, - description: options.description ?? "", - tags: options.labels?.join(",") ?? "", - visibility: options.visibility, + if (details == null) { + self.loadingStatus.setData("error"); + return; } - const extras = { - file: - '; filename="' + - (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - '"\r\nContent-Type: application/gpx+xml', + self.CheckForMessagesContinuously(); + + // details is an XML DOM of user details + let userInfo = details.getElementsByTagName("user")[0]; + + let data = self.userDetails.data; + data.loggedIn = true; + console.log("Login completed, userinfo is ", userInfo); + data.name = userInfo.getAttribute("display_name"); + data.account_created = userInfo.getAttribute("account_created"); + data.uid = Number(userInfo.getAttribute("id")); + data.csCount = Number.parseInt( + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + ); + data.tracesCount = Number.parseInt( + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 + ); + + data.img = undefined; + const imgEl = userInfo.getElementsByTagName("img"); + if (imgEl !== undefined && imgEl[0] !== undefined) { + data.img = imgEl[0].getAttribute("href"); } - const boundary = "987654" - - let body = "" - for (const key in contents) { - body += "--" + boundary + "\r\n" - body += 'Content-Disposition: form-data; name="' + key + '"' - if (extras[key] !== undefined) { - body += extras[key] - } - body += "\r\n\r\n" - body += contents[key] + "\r\n" + const description = userInfo.getElementsByTagName("description"); + if (description !== undefined && description[0] !== undefined) { + data.description = description[0]?.innerHTML; + } + const homeEl = userInfo.getElementsByTagName("home"); + if (homeEl !== undefined && homeEl[0] !== undefined) { + const lat = parseFloat(homeEl[0].getAttribute("lat")); + const lon = parseFloat(homeEl[0].getAttribute("lon")); + data.home = { lat: lat, lon: lon }; } - body += "--" + boundary + "--\r\n" - const response = await this.post("gpx/create", body, { - "Content-Type": "multipart/form-data; boundary=" + boundary, - "Content-Length": body.length, - }) - const parsed = JSON.parse(response) - console.log("Uploaded GPX track", parsed) - return { id: parsed } + self.loadingStatus.setData("logged-in"); + const messages = userInfo + .getElementsByTagName("messages")[0] + .getElementsByTagName("received")[0]; + data.unreadMessages = parseInt(messages.getAttribute("unread")); + data.totalMessages = parseInt(messages.getAttribute("count")); + + self.userDetails.ping(); + for (const action of self._onLoggedIn) { + action(self.userDetails.data); + } + self._onLoggedIn = []; + } + ); + } + + /** + * Interact with the API. + * + * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' + */ + public async interact( + path: string, + method: "GET" | "POST" | "PUT" | "DELETE", + header?: Record, + content?: string + ): Promise { + return new Promise((ok, error) => { + this.auth.xhr( + { + method, + options: { + header + }, + content, + path: `/api/0.6/${path}` + }, + function(err, response) { + if (err !== null) { + error(err); + } else { + ok(response); + } + } + ); + }); + } + + public async post( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "POST", header, content); + } + + public async put( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "PUT", header, content); + } + + public async get(path: string, header?: Record): Promise { + return await this.interact(path, "GET", header); + } + + public closeNote(id: number | string, text?: string): Promise { + let textSuffix = ""; + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text); + } + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text); + return new Promise((ok) => { + ok(); + }); + } + return this.post(`notes/${id}/close${textSuffix}`); + } + + public reopenNote(id: number | string, text?: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text); + return new Promise((ok) => { + ok(); + }); + } + let textSuffix = ""; + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text); + } + return this.post(`notes/${id}/reopen${textSuffix}`); + } + + public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually opening note with text ", text); + return new Promise<{ id: number }>((ok) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ); + }); + } + // 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" + }); + const parsed = JSON.parse(response); + const id = parsed.properties; + console.log("OPENED NOTE", id); + return id; + } + + public async uploadGpxTrack( + gpx: string, + options: { + description: string + visibility: "private" | "public" | "trackable" | "identifiable" + filename?: string + /** + * Some words to give some properties; + * + * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. + */ + labels: string[] + } + ): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually uploading GPX ", gpx); + return new Promise<{ id: number }>((ok, error) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ); + }); } - public addCommentToNote(id: number | string, text: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id) - return new Promise((ok) => { - ok() - }) - } - if ((text ?? "") === "") { - throw "Invalid text!" - } + const contents = { + file: gpx, + description: options.description ?? "", + tags: options.labels?.join(",") ?? "", + visibility: options.visibility + }; - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", + const extras = { + file: + "; filename=\"" + + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + + "\"\r\nContent-Type: application/gpx+xml" + }; - path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, - }, - function (err, _) { - if (err !== null) { - error(err) - } else { - ok() - } - } - ) - }) + const boundary = "987654"; + + let body = ""; + for (const key in contents) { + body += "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + key + "\""; + if (extras[key] !== undefined) { + body += extras[key]; + } + body += "\r\n\r\n"; + body += contents[key] + "\r\n"; + } + body += "--" + boundary + "--\r\n"; + + const response = await this.post("gpx/create", body, { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length + }); + const parsed = JSON.parse(response); + console.log("Uploaded GPX track", parsed); + return { id: parsed }; + } + + public addCommentToNote(id: number | string, text: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id); + return new Promise((ok) => { + ok(); + }); + } + if ((text ?? "") === "") { + throw "Invalid text!"; } - private updateAuthObject() { - let pwaStandAloneMode = false - try { - if (Utils.runningFromConsole) { - pwaStandAloneMode = true - } else if ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: fullscreen)").matches - ) { - pwaStandAloneMode = true - } - } catch (e) { - console.warn( - "Detecting standalone mode failed", - e, - ". Assuming in browser and not worrying furhter" - ) + return new Promise((ok, error) => { + this.auth.xhr( + { + method: "POST", + + path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` + }, + function(err, _) { + if (err !== null) { + error(err); + } else { + ok(); + } } - const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage + ); + }); + } - // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... - // Same for an iframe... + /** + * To be called by land.html + */ + public finishLogin(callback: (previousURL: string) => void) { + this.auth.authenticate(function() { + // Fully authed at this point + console.log("Authentication successful!"); + const previousLocation = LocalStorageSource.Get("location_before_login"); + callback(previousLocation.data); + }); + } - this.auth = new osmAuth({ - client_id: this._oauth_config.oauth_client_id, - url: this._oauth_config.url, - scope: "read_prefs write_prefs write_api write_gpx write_notes", - redirect_uri: Utils.runningFromConsole - ? "https://mapcomplete.org/land.html" - : window.location.protocol + "//" + window.location.host + "/land.html", - singlepage: !standalone, - auto: true, - }) + private updateAuthObject() { + let pwaStandAloneMode = false; + try { + if (Utils.runningFromConsole) { + pwaStandAloneMode = true; + } else if ( + window.matchMedia("(display-mode: standalone)").matches || + window.matchMedia("(display-mode: fullscreen)").matches + ) { + pwaStandAloneMode = true; + } + } catch (e) { + console.warn( + "Detecting standalone mode failed", + e, + ". Assuming in browser and not worrying furhter" + ); } + const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage; - /** - * To be called by land.html - */ - public finishLogin(callback: (previousURL: string) => void) { - this.auth.authenticate(function () { - // Fully authed at this point - console.log("Authentication successful!") - const previousLocation = LocalStorageSource.Get("location_before_login") - callback(previousLocation.data) - }) - } + // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... + // Same for an iframe... - private CheckForMessagesContinuously() { - const self = this - if (this.isChecking) { - return - } - this.isChecking = true - Stores.Chronic(5 * 60 * 1000).addCallback((_) => { - if (self.isLoggedIn.data) { - console.log("Checking for messages") - self.AttemptLogin() - } - }) - } + this.auth = new osmAuth({ + client_id: this._oauth_config.oauth_client_id, + url: this._oauth_config.url, + scope: "read_prefs write_prefs write_api write_gpx write_notes", + redirect_uri: Utils.runningFromConsole + ? "https://mapcomplete.org/land.html" + : window.location.protocol + "//" + window.location.host + "/land.html", + singlepage: !standalone, + auto: true + }); + } - private UpdateCapabilities(): void { - const self = this - this.FetchCapabilities().then(({ api, gpx }) => { - self.apiIsOnline.setData(api) - self.gpxServiceIsOnline.setData(gpx) - }) + private CheckForMessagesContinuously() { + const self = this; + if (this.isChecking) { + return; } + this.isChecking = true; + Stores.Chronic(5 * 60 * 1000).addCallback((_) => { + if (self.isLoggedIn.data) { + console.log("Checking for messages"); + self.AttemptLogin(); + } + }); + } - private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { - if (Utils.runningFromConsole) { - return { api: "online", gpx: "online" } - } - const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") - if (result["content"] === undefined) { - console.log("Something went wrong:", result) - return { api: "unreachable", gpx: "unreachable" } - } - const xmlRaw = result["content"] - const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") - const statusEl = parsed.getElementsByTagName("status")[0] - const api = statusEl.getAttribute("api") - const gpx = statusEl.getAttribute("gpx") - return { api, gpx } + private UpdateCapabilities(): void { + const self = this; + this.FetchCapabilities().then(({ api, gpx }) => { + self.apiIsOnline.setData(api); + self.gpxServiceIsOnline.setData(gpx); + }); + } + + private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { + if (Utils.runningFromConsole) { + return { api: "online", gpx: "online" }; } + const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities"); + if (result["content"] === undefined) { + console.log("Something went wrong:", result); + return { api: "unreachable", gpx: "unreachable" }; + } + const xmlRaw = result["content"]; + const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml"); + const statusEl = parsed.getElementsByTagName("status")[0]; + const api = statusEl.getAttribute("api"); + const gpx = statusEl.getAttribute("gpx"); + return { api, gpx }; + } } diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts deleted file mode 100644 index 2a90ab66a..000000000 --- a/src/UI/Image/ImageUploadFlow.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Translations from "../i18n/Translations" -import Svg from "../../Svg" -import { Tag } from "../../Logic/Tags/Tag" -import BaseUIElement from "../BaseUIElement" -import Toggle from "../Input/Toggle" -import FileSelectorButton from "../Input/FileSelectorButton" -import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader" -import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { FixedUiElement } from "../Base/FixedUiElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import Loading from "../Base/Loading" -import { LoginToggle } from "../Popup/LoginButton" -import Constants from "../../Models/Constants" -import { SpecialVisualizationState } from "../SpecialVisualization" -import exp from "constants"; - -export class ImageUploadFlow extends Combine { - private static readonly uploadCountsPerId = new Map>() - - constructor( - tagsSource: Store, - state: SpecialVisualizationState, - imagePrefix: string = "image", - text: string = undefined - ) { - const perId = ImageUploadFlow.uploadCountsPerId - const id = tagsSource.data.id - if (!perId.has(id)) { - perId.set(id, new UIEventSource(0)) - } - const uploadedCount = perId.get(id) - const uploader = new ImgurUploader(async (url) => { - // A file was uploaded - we add it to the tags of the object - - const tags = tagsSource.data - let key = imagePrefix - if (tags[imagePrefix] !== undefined) { - let freeIndex = 0 - while (tags[imagePrefix + ":" + freeIndex] !== undefined) { - freeIndex++ - } - key = imagePrefix + ":" + freeIndex - } - - await state.changes.applyAction( - new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, { - changeType: "add-image", - theme: state.layout.id, - }) - ) - console.log("Adding image:" + key, url) - uploadedCount.data++ - uploadedCount.ping() - }) - - const t = Translations.t.image - - let labelContent: BaseUIElement - if (text === undefined) { - labelContent = Translations.t.image.addPicture - .Clone() - .SetClass("block align-middle mt-1 ml-3 text-4xl ") - } else { - labelContent = new FixedUiElement(text).SetClass( - "block align-middle mt-1 ml-3 text-2xl " - ) - } - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - labelContent, - ]).SetClass("w-full flex justify-center items-center") - - const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0") - - const fileSelector = new FileSelectorButton(label, { - acceptType: "image/*", - allowMultiple: true, - labelClasses: "rounded-full border-2 border-black font-bold", - }) - /* fileSelector.SetClass( - "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" - ) - .SetStyle(" border-color: var(--foreground-color);")*/ - fileSelector.GetValue().addCallback((filelist) => { - if (filelist === undefined || filelist.length === 0) { - return - } - - for (var i = 0; i < filelist.length; i++) { - const sizeInBytes = filelist[i].size - console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes") - if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) { - alert( - Translations.t.image.toBig.Subs({ - actual_size: Math.floor(sizeInBytes / 1000000) + "MB", - max_size: uploader.maxFileSizeInMegabytes + "MB", - }).txt - ) - return - } - } - - const license = licenseStore?.data ?? "CC0" - - const tags = tagsSource.data - - const layout = state?.layout - let matchingLayer: LayerConfig = undefined - for (const layer of layout?.layers ?? []) { - if (layer.source.osmTags.matchesProperties(tags)) { - matchingLayer = layer - break - } - } - - const title = - matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement() - ?.textContent ?? - tags.name ?? - "https//osm.org/" + tags.id - const description = [ - "author:" + state.osmConnection.userDetails.data.name, - "license:" + license, - "osmid:" + tags.id, - ].join("\n") - - uploader.uploadMany(title, description, filelist) - }) - - super([ - new VariableUiElement( - uploader.queue - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return new Loading(t.uploadingPicture).SetClass("alert") - } else { - return new Loading( - t.uploadingMultiple.Subs({ count: "" + l }) - ).SetClass("alert") - } - }) - ), - new VariableUiElement( - uploader.failed - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - console.log(l) - return t.uploadFailed.SetClass("block alert") - }) - ), - new VariableUiElement( - uploadedCount.map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return t.uploadDone.Clone().SetClass("thanks block") - } - return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block") - }) - ), - - fileSelector, - new Combine([ - Translations.t.image.respectPrivacy, - new VariableUiElement( - licenseStore.map((license) => - Translations.t.image.currentLicense.Subs({ license }) - ) - ) - .onClick(() => { - console.log("Opening the license settings... ") - state.guistate.openUsersettings("picture-license") - }) - .SetClass("underline"), - ]).SetStyle("font-size:small;"), - ]) - this.SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") - - - } -} diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte index fa82ee34c..23408e778 100644 --- a/src/UI/Image/UploadImage.svelte +++ b/src/UI/Image/UploadImage.svelte @@ -16,6 +16,10 @@ import Svg from "../../Svg"; export let state: SpecialVisualizationState; export let tags: Store; +/** + * Image to show in the button + * NOT the image to upload! + */ export let image: string = undefined; if (image === "") { image = undefined; @@ -30,7 +34,7 @@ function handleFiles(files: FileList) { const file = files.item(i); console.log("Got file", file.name) try { - state.imageUploadManager.uploadImageAndApply(file, tags.data); + state.imageUploadManager.uploadImageAndApply(file, tags); } catch (e) { alert(e); } diff --git a/src/UI/Input/FileSelectorButton.ts b/src/UI/Input/FileSelectorButton.ts deleted file mode 100644 index c3f56d297..000000000 --- a/src/UI/Input/FileSelectorButton.ts +++ /dev/null @@ -1,111 +0,0 @@ -import BaseUIElement from "../BaseUIElement" -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class FileSelectorButton extends InputElement { - private static _nextid = 0 - private readonly _value = new UIEventSource(undefined) - private readonly _label: BaseUIElement - private readonly _acceptType: string - private readonly allowMultiple: boolean - private readonly _labelClasses: string - - constructor( - label: BaseUIElement, - options?: { - acceptType: "image/*" | string - allowMultiple: true | boolean - labelClasses?: string - } - ) { - super() - this._label = label - this._acceptType = options?.acceptType ?? "image/*" - this._labelClasses = options?.labelClasses ?? "" - this.SetClass("block cursor-pointer") - label.SetClass("cursor-pointer") - this.allowMultiple = options?.allowMultiple ?? true - } - - GetValue(): UIEventSource { - return this._value - } - - IsValid(t: FileList): boolean { - return true - } - - protected InnerConstructElement(): HTMLElement { - const self = this - const el = document.createElement("form") - const label = document.createElement("label") - label.appendChild(this._label.ConstructElement()) - label.classList.add(...this._labelClasses.split(" ").filter((t) => t !== "")) - el.appendChild(label) - - const actualInputElement = document.createElement("input") - actualInputElement.style.cssText = "display:none" - actualInputElement.type = "file" - actualInputElement.accept = this._acceptType - actualInputElement.name = "picField" - actualInputElement.multiple = this.allowMultiple - actualInputElement.id = "fileselector" + FileSelectorButton._nextid - FileSelectorButton._nextid++ - - label.htmlFor = actualInputElement.id - - actualInputElement.onchange = () => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - } - - el.addEventListener("submit", (e) => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - actualInputElement.classList.remove("glowing-shadow") - - e.preventDefault() - }) - - el.appendChild(actualInputElement) - - function setDrawAttention(isOn: boolean) { - if (isOn) { - label.classList.add("glowing-shadow") - } else { - label.classList.remove("glowing-shadow") - } - } - - el.addEventListener("dragover", (event) => { - event.stopPropagation() - event.preventDefault() - setDrawAttention(true) - // Style the drag-and-drop as a "copy file" operation. - event.dataTransfer.dropEffect = "copy" - }) - - window.document.addEventListener("dragenter", () => { - setDrawAttention(true) - }) - - window.document.addEventListener("dragend", () => { - setDrawAttention(false) - }) - - el.addEventListener("drop", (event) => { - event.stopPropagation() - event.preventDefault() - label.classList.remove("glowing-shadow") - const fileList = event.dataTransfer.files - this._value.setData(fileList) - }) - - return el - } -} diff --git a/src/UI/Input/Slider.ts b/src/UI/Input/Slider.ts deleted file mode 100644 index 9fce626a7..000000000 --- a/src/UI/Input/Slider.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class Slider extends InputElement { - private readonly _value: UIEventSource - private readonly min: number - private readonly max: number - private readonly step: number - private readonly vertical: boolean - - /** - * Constructs a slider input element for natural numbers - * @param min: the minimum value that is allowed, inclusive - * @param max: the max value that is allowed, inclusive - * @param options: value: injectable value; step: the step size of the slider - */ - constructor( - min: number, - max: number, - options?: { - value?: UIEventSource - step?: 1 | number - vertical?: false | boolean - } - ) { - super() - this.max = max - this.min = min - this._value = options?.value ?? new UIEventSource(min) - this.step = options?.step ?? 1 - this.vertical = options?.vertical ?? false - } - - GetValue(): UIEventSource { - return this._value - } - - protected InnerConstructElement(): HTMLElement { - const el = document.createElement("input") - el.type = "range" - el.min = "" + this.min - el.max = "" + this.max - el.step = "" + this.step - const valuestore = this._value - el.oninput = () => { - valuestore.setData(Number(el.value)) - } - if (this.vertical) { - el.classList.add("vertical") - el.setAttribute("orient", "vertical") // firefox only workaround... - } - valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data)) - return el - } - - IsValid(t: number): boolean { - return Math.round(t) == t && t >= this.min && t <= this.max - } -} diff --git a/src/UI/Popup/CreateNewNote.svelte b/src/UI/Popup/CreateNewNote.svelte index 4bdb92b23..10eda741a 100644 --- a/src/UI/Popup/CreateNewNote.svelte +++ b/src/UI/Popup/CreateNewNote.svelte @@ -62,8 +62,13 @@ state.newFeatures.features.data.push(feature) state.newFeatures.features.ping() state.selectedElement?.setData(feature) + if(state.featureProperties.trackFeature){ + state.featureProperties.trackFeature(feature) + } comment.setData("") created = true + state.selectedElement.setData(feature) + state.selectedLayer.setData(state.layerState.filteredLayers.get("note")) } diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 1d3575b42..a4e00100b 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -16,6 +16,7 @@ import { MenuState } from "../Models/MenuState"; import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; import { RasterLayerPolygon } from "../Models/RasterLayers"; import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { OsmTags } from "../Models/OsmFeature"; /** * The state needed to render a special Visualisation. @@ -26,7 +27,7 @@ export interface SpecialVisualizationState { readonly featureSwitches: FeatureSwitchState; readonly layerState: LayerState; - readonly featureProperties: { getStore(id: string): UIEventSource> }; + readonly featureProperties: { getStore(id: string): UIEventSource>, trackFeature?(feature: { properties: OsmTags }) }; readonly indexedFeatures: IndexedFeatureSource; diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 24b712da2..23d1b57c8 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,78 +1,70 @@ -import Combine from "./Base/Combine" -import { FixedUiElement } from "./Base/FixedUiElement" -import BaseUIElement from "./BaseUIElement" -import Title from "./Base/Title" -import Table from "./Base/Table" -import { - RenderingSpecification, - SpecialVisualization, - SpecialVisualizationState, -} from "./SpecialVisualization" -import { HistogramViz } from "./Popup/HistogramViz" -import { MinimapViz } from "./Popup/MinimapViz" -import { ShareLinkViz } from "./Popup/ShareLinkViz" -import { UploadToOsmViz } from "./Popup/UploadToOsmViz" -import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" -import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" -import TagApplyButton from "./Popup/TagApplyButton" -import { CloseNoteButton } from "./Popup/CloseNoteButton" -import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" -import AllTagsPanel from "./Popup/AllTagsPanel.svelte" -import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" -import { ImageCarousel } from "./Image/ImageCarousel" -import { ImageUploadFlow } from "./Image/ImageUploadFlow" -import { VariableUiElement } from "./Base/VariableUIElement" -import { Utils } from "../Utils" -import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" -import { Translation } from "./i18n/Translation" -import Translations from "./i18n/Translations" -import ReviewForm from "./Reviews/ReviewForm" -import ReviewElement from "./Reviews/ReviewElement" -import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" -import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" -import { SubtleButton } from "./Base/SubtleButton" -import Svg from "../Svg" -import NoteCommentElement from "./Popup/NoteCommentElement" -import FileSelectorButton from "./Input/FileSelectorButton" -import { LoginToggle } from "./Popup/LoginButton" -import Toggle from "./Input/Toggle" -import { SubstitutedTranslation } from "./SubstitutedTranslation" -import List from "./Base/List" -import StatisticsPanel from "./BigComponents/StatisticsPanel" -import AutoApplyButton from "./Popup/AutoApplyButton" -import { LanguageElement } from "./Popup/LanguageElement" -import FeatureReviews from "../Logic/Web/MangroveReviews" -import Maproulette from "../Logic/Maproulette" -import SvelteUIElement from "./Base/SvelteUIElement" -import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" -import QuestionViz from "./Popup/QuestionViz" -import { Feature, Point } from "geojson" -import { GeoOperations } from "../Logic/GeoOperations" -import CreateNewNote from "./Popup/CreateNewNote.svelte" -import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" -import UserProfile from "./BigComponents/UserProfile.svelte" -import LanguagePicker from "./LanguagePicker" -import Link from "./Base/Link" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import { OsmTags, WayId } from "../Models/OsmFeature" -import MoveWizard from "./Popup/MoveWizard" -import SplitRoadWizard from "./Popup/SplitRoadWizard" -import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" -import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" -import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" -import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" -import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" -import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" -import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" -import { OpenJosm } from "./BigComponents/OpenJosm" -import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" -import FediverseValidator from "./InputElement/Validators/FediverseValidator" -import SendEmail from "./Popup/SendEmail.svelte" -import NearbyImages from "./Popup/NearbyImages.svelte" -import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" +import Combine from "./Base/Combine"; +import { FixedUiElement } from "./Base/FixedUiElement"; +import BaseUIElement from "./BaseUIElement"; +import Title from "./Base/Title"; +import Table from "./Base/Table"; +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; +import { HistogramViz } from "./Popup/HistogramViz"; +import { MinimapViz } from "./Popup/MinimapViz"; +import { ShareLinkViz } from "./Popup/ShareLinkViz"; +import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; +import { MultiApplyViz } from "./Popup/MultiApplyViz"; +import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; +import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; +import TagApplyButton from "./Popup/TagApplyButton"; +import { CloseNoteButton } from "./Popup/CloseNoteButton"; +import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; +import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; +import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; +import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; +import { ImageCarousel } from "./Image/ImageCarousel"; +import { VariableUiElement } from "./Base/VariableUIElement"; +import { Utils } from "../Utils"; +import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; +import { Translation } from "./i18n/Translation"; +import Translations from "./i18n/Translations"; +import ReviewForm from "./Reviews/ReviewForm"; +import ReviewElement from "./Reviews/ReviewElement"; +import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; +import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; +import { SubtleButton } from "./Base/SubtleButton"; +import Svg from "../Svg"; +import NoteCommentElement from "./Popup/NoteCommentElement"; +import { SubstitutedTranslation } from "./SubstitutedTranslation"; +import List from "./Base/List"; +import StatisticsPanel from "./BigComponents/StatisticsPanel"; +import AutoApplyButton from "./Popup/AutoApplyButton"; +import { LanguageElement } from "./Popup/LanguageElement"; +import FeatureReviews from "../Logic/Web/MangroveReviews"; +import Maproulette from "../Logic/Maproulette"; +import SvelteUIElement from "./Base/SvelteUIElement"; +import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; +import QuestionViz from "./Popup/QuestionViz"; +import { Feature, Point } from "geojson"; +import { GeoOperations } from "../Logic/GeoOperations"; +import CreateNewNote from "./Popup/CreateNewNote.svelte"; +import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; +import UserProfile from "./BigComponents/UserProfile.svelte"; +import LanguagePicker from "./LanguagePicker"; +import Link from "./Base/Link"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; +import { OsmTags, WayId } from "../Models/OsmFeature"; +import MoveWizard from "./Popup/MoveWizard"; +import SplitRoadWizard from "./Popup/SplitRoadWizard"; +import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; +import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; +import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; +import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; +import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; +import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; +import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; +import { OpenJosm } from "./BigComponents/OpenJosm"; +import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; +import FediverseValidator from "./InputElement/Validators/FediverseValidator"; +import SendEmail from "./Popup/SendEmail.svelte"; +import NearbyImages from "./Popup/NearbyImages.svelte"; +import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; import UploadImage from "./Image/UploadImage.svelte"; class NearbyImageVis implements SpecialVisualization { @@ -272,6 +264,7 @@ export default class SpecialVisualizations { SpecialVisualizations.specialVisualizations .map((sp) => sp.funcName + "()") .join(", ") + } } @@ -628,7 +621,6 @@ export default class SpecialVisualizations { return new SvelteUIElement(UploadImage, { state,tags, labelText: args[1], image: args[0] }) - // return new ImageUploadFlow(tags, state, args[0], args[1]) }, }, { @@ -867,43 +859,11 @@ export default class SpecialVisualizations { }, ], constr: (state, tags, args) => { - const isUploading = new UIEventSource(false) - const t = Translations.t.notes const id = tags.data[args[0] ?? "id"] - - const uploader = new ImgurUploader(async (url) => { - isUploading.setData(false) - await state.osmConnection.addCommentToNote(id, url) - NoteCommentElement.addCommentTo(url, tags, state) - }) - - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - Translations.t.image.addPicture, - ]).SetClass( - "p-2 border-4 border-black rounded-full font-bold h-full align-center w-full flex justify-center" - ) - - const fileSelector = new FileSelectorButton(label) - fileSelector.GetValue().addCallback((filelist) => { - isUploading.setData(true) - uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist) - }) - const ti = Translations.t.image - const uploadPanel = new Combine([ - fileSelector, - ti.respectPrivacy.SetClass("text-sm"), - ]).SetClass("flex flex-col") - return new LoginToggle( - new Toggle( - Translations.t.image.uploadingPicture.SetClass("alert"), - uploadPanel, - isUploading - ), - t.loginToAddPicture, - state - ) - }, + tags = state.featureProperties.getStore(id) + console.log("Id is", id) + return new SvelteUIElement(UploadImage, {state, tags}) + } }, { funcName: "title", From 9d149cae308a95ed5b78b684b35c6dd0c3da58d6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 02:56:02 +0200 Subject: [PATCH 079/133] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3f0aecdb..cbc562381 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.33.3", + "version": "0.33.4", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From fe3ccd1074431f83c57fa092787b17bb0d47d62b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 03:13:17 +0200 Subject: [PATCH 080/133] CI: add extra config file for easier per-deployment veriations, add hetzner deploy script --- config.json | 3 +++ package.json | 3 ++- scripts/hetzner/config.json | 4 ++++ scripts/hetzner/deployHetzner.sh | 20 ++++++++++++++++++++ src/Models/Constants.ts | 22 +++++++++++----------- 5 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 config.json create mode 100644 scripts/hetzner/config.json create mode 100755 scripts/hetzner/deployHetzner.sh diff --git a/config.json b/config.json new file mode 100644 index 000000000..16f8d4541 --- /dev/null +++ b/config.json @@ -0,0 +1,3 @@ +{ + "#": "Settings in this file override the `config`-section of `package.json`" +} diff --git a/package.json b/package.json index cbc562381..66bf5bccb 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "main": "index.ts", "type": "module", "config": { - "#": "Various endpoints that are instance-specific", + "#": "Various endpoints that are instance-specific. This is the default configuration, which is re-exported in 'Constants.ts'.", + "#": "Use MAPCOMPLETE_CONFIGURATION to use an additional configuration, e.g. `MAPCOMPLETE_CONFIGURATION=config_hetzner`", "#oauth_credentials:comment": [ "`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.", "Are you deploying your own instance? Register your application too.", diff --git a/scripts/hetzner/config.json b/scripts/hetzner/config.json new file mode 100644 index 000000000..c3ad60fdd --- /dev/null +++ b/scripts/hetzner/config.json @@ -0,0 +1,4 @@ +{ + "#":"Some configuration tweaks specifically for hetzner", + "country_coder_host": "https://countrycoder.mapcomplete.org/" +} diff --git a/scripts/hetzner/deployHetzner.sh b/scripts/hetzner/deployHetzner.sh new file mode 100755 index 000000000..70aec756f --- /dev/null +++ b/scripts/hetzner/deployHetzner.sh @@ -0,0 +1,20 @@ +#! /bin/bash +### To be run from the root of the repository + +# Some pointers to get started: +# apt install npm +# apt install unzip +# npm i -g csp-logger + +# wget https://github.com/pietervdvn/latlon2country/raw/main/tiles.zip +# unzip tiles.zip + +MAPCOMPLETE_CONFIGURATION="config_hetzner" +cp config.json config.json.bu && +cp ./scripts/hetzner/config.json . && +npm run prepare-deploy && +mv config.json.bu config.json && +zip dist.zip -r dist/* && +scp -r dist.zip hetzner:/root/ && +scp ./scripts/hetzner/config/* hetzner:/root/ +ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ mv dist public && caddy stop && caddy start" diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 3edeebc87..75ea6f73f 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -1,14 +1,11 @@ -import * as meta from "../../package.json" +import * as packagefile from "../../package.json" +import * as extraconfig from "../../config.json" import { Utils } from "../Utils" export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number] export default class Constants { - public static vNumber = meta.version - - public static ImgurApiKey = meta.config.api_keys.imgur - public static readonly mapillary_client_token_v4 = meta.config.api_keys.mapillary_v4 - + public static vNumber = packagefile.version /** * API key for Maproulette * @@ -17,9 +14,6 @@ export default class Constants { * Using an empty string however does work for most actions, but will attribute all actions to the Superuser. */ public static readonly MaprouletteApiKey = "" - - public static defaultOverpassUrls = meta.config.default_overpass_urls - public static readonly added_by_default = [ "selected_element", "gps_location", @@ -47,7 +41,6 @@ export default class Constants { ...Constants.added_by_default, ...Constants.no_include, ] as const - // The user journey states thresholds when a new feature gets unlocked public static userJourney = { moreScreenUnlock: 1, @@ -104,7 +97,14 @@ export default class Constants { * In seconds */ static zoomToLocationTimeout = 15 - static countryCoderEndpoint: string = meta.config.country_coder_host + private static readonly config = (() => { + const defaultConfig = packagefile.config + return { ...defaultConfig, ...extraconfig } + })() + public static ImgurApiKey = Constants.config.api_keys.imgur + public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 + public static defaultOverpassUrls = Constants.config.default_overpass_urls + static countryCoderEndpoint: string = Constants.config.country_coder_host /** * These are the values that are allowed to use as 'backdrop' icon for a map pin From 1c0c44f2f8446f088918b0b6cc8e282d95d47a37 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 03:14:29 +0200 Subject: [PATCH 081/133] Deploy: add hetzner config files --- scripts/hetzner/config/Caddyfile | 21 +++++++++++++++++++ scripts/hetzner/config/csp-logger-config.json | 7 +++++++ 2 files changed, 28 insertions(+) create mode 100644 scripts/hetzner/config/Caddyfile create mode 100644 scripts/hetzner/config/csp-logger-config.json diff --git a/scripts/hetzner/config/Caddyfile b/scripts/hetzner/config/Caddyfile new file mode 100644 index 000000000..a417808d2 --- /dev/null +++ b/scripts/hetzner/config/Caddyfile @@ -0,0 +1,21 @@ +hosted.mapcomplete.org { + root * public/ + file_server + header { + +Permissions-Policy "interest-cohort=()" + +Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}` + +Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://gc.zgo.at ; img-src * ; report-uri https://report.mapcomplete.org/csp ; report-to csp-endpoint ;" + } +} + +countrycoder.mapcomplete.org { + root * tiles/ + file_server +} + + +report.mapcomplete.org { + reverse_proxy { + to http://127.0.0.1:2600 + } +} diff --git a/scripts/hetzner/config/csp-logger-config.json b/scripts/hetzner/config/csp-logger-config.json new file mode 100644 index 000000000..0c2bfd7a2 --- /dev/null +++ b/scripts/hetzner/config/csp-logger-config.json @@ -0,0 +1,7 @@ +{ + "store": "console", + "allowedOrigin": null, + "port": 2600, + "domainWhitelist": ["localhost:10179", "localhost:2600","hosted.mapcomplete.org", "dev.mapcomplete.org", "mapcomplete.org","*"], + "sourceBlacklist": ["chrome-extension://gighmmpiobklfepjocnamgkkbiglidom"] +} From b20e887f9a4ad026dc9a1b61dd7e9e1a2df9b9e0 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Mon, 25 Sep 2023 07:53:46 +0200 Subject: [PATCH 082/133] Merge branch 'develop' into RobinLinde-patch-1 --- assets/layers/atm/atm.json | 2 +- assets/layers/bike_cafe/bike_cafe.json | 3 +- .../charging_station/charging_station.json | 2 +- assets/layers/elevator/elevator.json | 12 +- .../layers/elongated_coin/elongated_coin.json | 2 +- assets/layers/food/food.json | 31 +- assets/layers/map/map.json | 24 +- assets/layers/note/note.json | 21 +- .../observation_tower/observation_tower.json | 26 +- assets/layers/parking/parking.json | 18 +- .../layers/parking_spaces/parking_spaces.json | 33 +- assets/layers/postoffices/postoffices.json | 15 +- .../rainbow_crossings/rainbow_crossings.json | 2 +- assets/layers/shower/shower.json | 2 +- assets/layers/usersettings/usersettings.json | 69 +- assets/layers/vending_machine/condom.svg | 9 + .../layers/vending_machine/condom.svg.license | 2 + .../layers/vending_machine/license_info.json | 10 + .../vending_machine/vending_machine.json | 46 +- assets/themes/atm/atm.json | 2 +- assets/themes/blind_osm/blind_osm.json | 2 +- assets/themes/campersite/campersite.json | 2 +- .../charging_stations/charging_stations.json | 1 - assets/themes/climbing/climbing.json | 2 +- .../themes/cycle_highways/cycle_highways.json | 2 +- assets/themes/cycle_infra/cycle_infra.json | 1 - assets/themes/cyclofix/cyclofix.json | 1 - .../themes/drinking_water/drinking_water.json | 1 - assets/themes/education/education.json | 1 - .../themes/elongated_coin/elongated_coin.json | 2 +- assets/themes/etymology/etymology.json | 2 +- assets/themes/fritures/fritures.json | 2 +- assets/themes/ghostbikes/ghostbikes.json | 2 +- assets/themes/grb/grb.json | 2 +- assets/themes/healthcare/healthcare.json | 1 - assets/themes/indoors/indoors.json | 1 - .../mapcomplete-changes.json | 2 +- assets/themes/maps/maps.json | 2 +- assets/themes/onwheels/onwheels.json | 3 +- .../openwindpowermap/openwindpowermap.json | 1 - .../osm_community_index.json | 1 - assets/themes/pets/pets.json | 9 +- assets/themes/playgrounds/playgrounds.json | 2 +- assets/themes/postboxes/postboxes.json | 1 - .../rainbow_crossings/rainbow_crossings.json | 1 - assets/themes/speelplekken/speelplekken.json | 2 +- assets/themes/stations/stations.json | 4 +- assets/themes/surveillance/surveillance.json | 4 +- .../toerisme_vlaanderen.json | 2 +- .../vending_machine/vending_machine.json | 4 +- .../walls_and_buildings.json | 1 - assets/themes/width/width.json | 2 +- langs/ca.json | 3 + langs/de.json | 5 + langs/en.json | 18 + langs/fr.json | 3 +- langs/layers/ca.json | 161 ++- langs/layers/da.json | 7 - langs/layers/de.json | 24 +- langs/layers/en.json | 55 +- langs/layers/es.json | 20 +- langs/layers/fr.json | 12 +- langs/layers/nl.json | 14 +- langs/layers/pl.json | 5 +- langs/themes/de.json | 2 +- package.json | 2 +- public/css/index-tailwind-output.css | 28 +- scripts/lint.ts | 8 +- .../Actors/PreferredRasterLayerSelector.ts | 67 ++ .../Actors/FeaturePropertiesStore.ts | 4 +- .../ImageProviders/ImageUploadManager.ts | 159 +++ src/Logic/ImageProviders/ImageUploader.ts | 15 + src/Logic/ImageProviders/Imgur.ts | 79 +- src/Logic/ImageProviders/ImgurUploader.ts | 43 - src/Logic/Osm/Actions/LinkImageAction.ts | 54 + src/Logic/Osm/Actions/LinkPicture.ts | 32 - src/Logic/Osm/Actions/OsmChangeAction.ts | 3 + src/Logic/Osm/ChangesetHandler.ts | 7 +- src/Logic/Osm/OsmConnection.ts | 1008 +++++++++-------- src/Logic/State/FeatureSwitchState.ts | 2 +- src/Logic/State/GeoLocationState.ts | 122 +- src/Logic/State/UserRelatedState.ts | 87 +- src/Logic/State/UserSettingsMetaTagging.ts | 1 + src/Logic/Tags/TagUtils.ts | 7 +- src/Logic/UIEventSource.ts | 20 +- src/Models/RasterLayers.ts | 193 ++-- .../Conversion/LegacyJsonConvert.ts | 7 +- .../ThemeConfig/Conversion/Validation.ts | 83 +- src/Models/ThemeConfig/LayerConfig.ts | 2 +- src/Models/ThemeViewState.ts | 510 ++++----- src/UI/Base/FileSelector.svelte | 40 + src/UI/Base/Loading.svelte | 11 +- .../BigComponents/BackgroundSwitcher.svelte | 2 +- src/UI/BigComponents/ThemeIntroPanel.svelte | 72 +- src/UI/DownloadFlow/DownloadPdf.svelte | 2 +- src/UI/Image/ImageUploadFlow.ts | 199 ---- src/UI/Image/UploadImage.svelte | 77 ++ src/UI/Image/UploadingImageCounter.svelte | 67 ++ src/UI/Input/FileSelectorButton.ts | 111 -- src/UI/Input/Slider.ts | 62 - src/UI/Map/MapLibreAdaptor.ts | 16 +- src/UI/Map/MaplibreMap.svelte | 2 +- src/UI/Popup/CreateNewNote.svelte | 5 + src/UI/Popup/DeleteFlow/DeleteWizard.svelte | 2 - src/UI/Popup/LinkableImage.svelte | 4 +- .../TagRendering/TagRenderingQuestion.svelte | 1 + src/UI/SpecialVisualization.ts | 185 +-- src/UI/SpecialVisualizations.ts | 193 ++-- src/UI/ThemeViewGUI.svelte | 2 +- src/Utils.ts | 2 +- src/Utils/pngMapCreator.ts | 2 +- 111 files changed, 2398 insertions(+), 1928 deletions(-) create mode 100644 assets/layers/vending_machine/condom.svg create mode 100644 assets/layers/vending_machine/condom.svg.license create mode 100644 src/Logic/Actors/PreferredRasterLayerSelector.ts create mode 100644 src/Logic/ImageProviders/ImageUploadManager.ts create mode 100644 src/Logic/ImageProviders/ImageUploader.ts delete mode 100644 src/Logic/ImageProviders/ImgurUploader.ts create mode 100644 src/Logic/Osm/Actions/LinkImageAction.ts delete mode 100644 src/Logic/Osm/Actions/LinkPicture.ts create mode 100644 src/UI/Base/FileSelector.svelte delete mode 100644 src/UI/Image/ImageUploadFlow.ts create mode 100644 src/UI/Image/UploadImage.svelte create mode 100644 src/UI/Image/UploadingImageCounter.svelte delete mode 100644 src/UI/Input/FileSelectorButton.ts delete mode 100644 src/UI/Input/Slider.ts diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 04e74522d..c7724e9fc 100644 --- a/assets/layers/atm/atm.json +++ b/assets/layers/atm/atm.json @@ -504,4 +504,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index d90821ded..ea86ad10a 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -363,5 +363,6 @@ "fr": "Un vélo café est un café à destination des cyclistes avec, par exemple, des services tels qu’une pompe, et de nombreuses décorations liées aux vélos, etc.", "cs": "Cyklokavárna je kavárna zaměřená na cyklisty, například se službami, jako je pumpa, se spoustou výzdoby související s jízdními koly, …", "ca": "Un cafè ciclista és un cafè enfocat a ciclistes, per exemple, amb serveis com una manxa, amb molta decoració relacionada amb el ciclisme, …" - } + }, + "deletion": true } diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 174b24c58..62361dea1 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -5294,4 +5294,4 @@ }, "neededChangesets": 10 } -} \ No newline at end of file +} diff --git a/assets/layers/elevator/elevator.json b/assets/layers/elevator/elevator.json index ac773ac13..4a00a45ce 100644 --- a/assets/layers/elevator/elevator.json +++ b/assets/layers/elevator/elevator.json @@ -213,22 +213,26 @@ { "id": "speech_output_available", "question": { - "en": "Has this elevator speech output?" + "en": "Has this elevator speech output?", + "de": "Verfügt der Aufzug über eine Sprachausgabe?" }, "questionHint": { - "en": "E.g. it announces the current floor" + "en": "E.g. it announces the current floor", + "de": "Z.B. werden Stockwerke angesagt" }, "mappings": [ { "if": "speech_output=yes", "then": { - "en": "This elevator has speech output" + "en": "This elevator has speech output", + "de": "Der Aufzug verfügt über eine Sprachausgabe" } }, { "if": "speech_output=no", "then": { - "en": "This elevator does not have speech output" + "en": "This elevator does not have speech output", + "de": "Der Aufzug verfügt über keine Sprachausgabe" } } ] diff --git a/assets/layers/elongated_coin/elongated_coin.json b/assets/layers/elongated_coin/elongated_coin.json index ac5d68e54..872bb92a3 100644 --- a/assets/layers/elongated_coin/elongated_coin.json +++ b/assets/layers/elongated_coin/elongated_coin.json @@ -386,4 +386,4 @@ "accepts_debit_cards", "accepts_credit_cards" ] -} \ No newline at end of file +} diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 64b55a87d..5d2c36717 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -1012,13 +1012,7 @@ "options": [ { "question": { - "en": "Has a vegetarian menu", - "de": "Vegetarische Gerichte im Angebot", - "es": "Tiene menú vegetariano", - "fr": "A un menu végétarien", - "nl": "Heeft een vegetarisch menu", - "pl": "Ma menu wegetariańskie", - "ca": "Té menú vegetarià" + "en": "Restaurants and fast food businesses" } }, { @@ -1048,13 +1042,8 @@ "options": [ { "question": { - "en": "Has a vegan menu", - "nl": "Heeft een veganistisch menu", - "de": "Vegane Gerichte im Angebot", - "es": "Tiene menú vegano", - "fr": "A un menu végétalien", - "pl": "Ma menu wegańskie", - "ca": "Té menú vegà" + "en": "Has a vegetarian menu", + "nl": "Heeft een vegetarisch menu" }, "osmTags": { "or": [ @@ -1072,13 +1061,13 @@ "options": [ { "question": { - "en": "Has a halal menu", - "nl": "Heeft een halal menu", - "de": "Halal Gerichte im Angebot", - "es": "Tiene menú halah", - "fr": "A un menu halal", - "da": "Har en halalmenu", - "ca": "Té menú halal" + "en": "Has a vegan menu", + "nl": "Heeft een veganistisch menu", + "de": "Vegane Gerichte im Angebot", + "es": "Tiene menú vegano", + "fr": "A un menu végétalien", + "pl": "Ma menu wegańskie", + "ca": "Té menú vegà" }, "osmTags": { "or": [ diff --git a/assets/layers/map/map.json b/assets/layers/map/map.json index 6bdc9dd29..41177739e 100644 --- a/assets/layers/map/map.json +++ b/assets/layers/map/map.json @@ -59,7 +59,8 @@ "if": "map_type=topo", "then": { "en": "Topographical map

The map contains contour lines.

", - "de": "Topographische Katte

Die Karte enthält Höhenlinien.

" + "de": "Topographische Katte

Die Karte enthält Höhenlinien.

", + "ca": "Mapa topogràfic

El mapa conté línies de contorn.

" } }, { @@ -75,7 +76,8 @@ "then": { "en": "This is a schematic map.

A sketched map with only important ways and POIs. The angles, distances etc. are merely illustrative, not accurate.

", "de": "Dies ist eine schematische Karte.

Eine skizzierte Karte mit nur wichtigen Wegen und POIs. Die Winkel, Entfernungen usw. sind lediglich illustrativ, nicht genau.

", - "pl": "To jest mapa schematyczna.

Mapa-szkic z tylko ważnymi drogami i POI. Kąty, odległości itp. są tylko ilustratywne, niedokładne.

" + "pl": "To jest mapa schematyczna.

Mapa-szkic z tylko ważnymi drogami i POI. Kąty, odległości itp. są tylko ilustratywne, niedokładne.

", + "ca": "Això és un mapa esquemàtic.

Un mapa esbossat amb només camins importants i PDI. Els angles, els trajectes etc. són merament il·lustratius, no acurat.

" } }, { @@ -92,7 +94,8 @@ "question": { "en": "What is the size of the shown area on the map?", "de": "Was wird von der Fläche abgedeckt?", - "pl": "Jaki jest rozmiar obszaru pokazanego na tej mapie?" + "pl": "Jaki jest rozmiar obszaru pokazanego na tej mapie?", + "ca": "Quina és la mida de l'àrea mostrada en el mapa?" }, "mappings": [ { @@ -100,14 +103,16 @@ "then": { "en": "A map of the rooms within a building", "de": "Eine Karte der Räume innerhalb eines Gebäudes", - "pl": "Plan pomieszczeń w budynku" + "pl": "Plan pomieszczeń w budynku", + "ca": "Un mapa de les habitacions dins d'un edifici" } }, { "if": "map_size=site", "then": { "en": "A map of special site, like of a historical castle, a park, a campus, a forest, ....", - "de": "Örtlichkeit (z.B. Burg)" + "de": "Örtlichkeit (z.B. Burg)", + "ca": "Un mapa d'un lloc especial, com un castell històric, un parc, un campus, un bosc, …" } }, { @@ -115,7 +120,8 @@ "then": { "en": "A map showing the village or town", "de": "Eine Karte, die das Dorf oder die Stadt anzeigt", - "pl": "Mapa pokazująca wieś lub niewielkie miasto" + "pl": "Mapa pokazująca wieś lub niewielkie miasto", + "ca": "Un mapa que mostra el poble o la ciutat" } }, { @@ -123,7 +129,8 @@ "then": { "en": " A map of a city", "de": "Stadt", - "pl": " Mapa miasta" + "pl": " Mapa miasta", + "ca": " Un mapa d'una ciutat" } }, { @@ -131,7 +138,8 @@ "then": { "en": "The map of an entire region, showing multiple cities and villages", "de": "Region", - "pl": "Mapa całego regionu, pokazująca wiele miast i wsi" + "pl": "Mapa całego regionu, pokazująca wiele miast i wsi", + "ca": "El mapa d'una regió sencera, mostrant múltiples ciutats i pobles" } } ] diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index 8509ed685..eb71c8c5e 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -4,7 +4,8 @@ "en": "OpenStreetMap notes", "nl": "OpenStreetMap Notes", "de": "OpenStreetMap-Hinweise", - "es": "Notas de OpenStreetMap" + "es": "Notas de OpenStreetMap", + "ca": "Notes d'OpenStreetMap" }, "description": "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)", "source": { @@ -33,7 +34,8 @@ "nl": "Gesloten Note", "de": "Geschlossene Notiz", "es": "Nota cerrada", - "pl": "Zamknięta notatka" + "pl": "Zamknięta notatka", + "ca": "Nota tancada" } } ] @@ -72,7 +74,8 @@ "en": "

Nearby images

The pictures below are nearby geotagged images and might be helpful to handle this note.", "de": "

Bilder aus der Nähe

Die folgenden Bilder sind mit Geotags versehene Bilder aus der Nähe und könnten für die Bearbeitung dieser Notiz hilfreich sein.", "es": "

Imágenes cercanas

Las imágenes de debajo son imágenes geoetiquetadas cercanas y pueden ser útiles para encargarse de esta nota.", - "nl": "

Afbeeldingen in de buurt

Onderstaande afbeeldingen zijn afbeeldingen met geo-referentie en die in de buurt genomen zijn. Mogelijks zijn ze nuttig om deze kaartnota af te handelen." + "nl": "

Afbeeldingen in de buurt

Onderstaande afbeeldingen zijn afbeeldingen met geo-referentie en die in de buurt genomen zijn. Mogelijks zijn ze nuttig om deze kaartnota af te handelen.", + "ca": "

Imatges properes

Les imatges de sota són imatges geoetiquetades properes i poden ser útils per a encarregar-se d'aquesta nota." }, "special": { "type": "nearby_images", @@ -86,7 +89,8 @@ "en": "Report {_first_user} for spam or inappropriate messages", "nl": "{_first_user} melden voor spam of ongepaste opmerkingen", "de": "", - "es": "Reportar {_first_user}" + "es": "Reportar {_first_user}", + "ca": "Reporta {_first_user} per spam o missatges inapropiats" }, "condition": "_opened_by_anonymous_user=false" }, @@ -96,7 +100,8 @@ "en": "Report this note as spam or inappropriate", "nl": "Deze note melden als spam of ongepast", "de": "Notiz als Spam oder unangemessen melden", - "es": "Reporta esta nota como spam o inapropiada" + "es": "Reporta esta nota como spam o inapropiada", + "ca": "Reporta aquesta nota com spam o inapropiada" } } ], @@ -330,7 +335,8 @@ "en": "Hide import notes", "nl": "Verberg import Notes", "de": "Importnotizen ausblenden", - "es": "Ocultar las nostras de importación" + "es": "Ocultar las notas de importación", + "ca": "Oculta les notes d'importació" } }, { @@ -339,7 +345,8 @@ "en": "Show only import Notes", "nl": "Toon enkel import Notes", "de": "Nur Importnotizen anzeigen", - "es": "Solo mostrar las notas de importación" + "es": "Solo mostrar las notas de importación", + "ca": "Mostrar només les notes d'importació" } } ] diff --git a/assets/layers/observation_tower/observation_tower.json b/assets/layers/observation_tower/observation_tower.json index d5446b2d0..bb8c0c59e 100644 --- a/assets/layers/observation_tower/observation_tower.json +++ b/assets/layers/observation_tower/observation_tower.json @@ -40,7 +40,8 @@ "nl": "Torens om van het uitzicht te genieten", "de": "Türme zur Aussicht auf die umgebende Landschaft", "es": "Torres con vista panorámica", - "pl": "Wieże z panoramicznym widokiem" + "pl": "Wieże z panoramicznym widokiem", + "ca": "Torres amb vista panoràmica" }, "tagRenderings": [ "images", @@ -93,7 +94,8 @@ "nl": "Deze toren is {height} hoog", "de": "Dieser Turm ist {height} hoch", "es": "Esta torre mide {height}", - "pl": "Ta wieża ma wysokość {height}" + "pl": "Ta wieża ma wysokość {height}", + "ca": "Aquesta torre fa {height}" }, "freeform": { "key": "height", @@ -141,14 +143,16 @@ "nl": "Hoeveel moet men betalen om deze toren te bezoeken?", "de": "Was kostet der Zugang zu diesem Turm?", "es": "¿Cuánto hay que pagar para entrar en esta torre?", - "pl": "Ile kosztuje wstęp na tę wieżę?" + "pl": "Ile kosztuje wstęp na tę wieżę?", + "ca": "Quant hi ha que pagar per entrar a aquesta torre?" }, "render": { "en": "Visiting this tower costs {charge}", "nl": "Deze toren bezoeken kost {charge}", "de": "Der Besuch des Turms kostet {charge}", "es": "Visitar esta torre cuesta {charge}", - "pl": "Wizyta na tej wieży kosztuje {charge}" + "pl": "Wizyta na tej wieży kosztuje {charge}", + "ca": "Visitar aquesta torre costa {charge}" }, "freeform": { "key": "charge", @@ -228,8 +232,9 @@ "en": "Does this tower have an elevator?", "nl": "Heeft deze toren een lift?", "de": "Hat dieser Turm einen Aufzug?", - "es": "¿Tiene ascensor esta torre?", - "pl": "Czy ta wieża ma windę?" + "es": "¿Esta torre tiene ascensor?", + "pl": "Czy ta wieża ma windę?", + "ca": "Aquesta torre té ascensor?" }, "mappings": [ { @@ -239,7 +244,8 @@ "nl": "Deze toren heeft een lift die bezoekers naar de top van de toren brengt", "de": "Dieser Turm verfügt über einen Aufzug, der die Besucher nach oben bringt", "es": "Esta torre tiene un ascensor que lleva a los visitantes a la cima", - "pl": "Ta wieża ma windę, która zabiera zwiedzających na górę" + "pl": "Ta wieża ma windę, która zabiera zwiedzających na górę", + "ca": "Aquesta torre té un ascensor que porta els visitants al cim" } }, { @@ -249,7 +255,8 @@ "nl": "Deze toren heeft geen lift", "de": "Dieser Turm hat keinen Aufzug", "es": "Esta torre no tiene ascensor", - "pl": "Ta wieża nie ma windy" + "pl": "Ta wieża nie ma windy", + "ca": "Aquesta torre no té ascensor" } } ], @@ -274,7 +281,8 @@ "en": "Maintained by {operator}", "de": "Betrieben von {operator}", "es": "Mantenida por {operator}", - "pl": "Obsługiwana przez {operator}" + "pl": "Obsługiwana przez {operator}", + "ca": "Mantés per {operator}" }, "freeform": { "key": "operator" diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index c61d92edb..a37c76317 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -21,7 +21,8 @@ "de": "Parkplatz", "es": "aparcamiento de coches", "fr": "Lieu de stationnement", - "pl": "Parking samochodowy" + "pl": "Parking samochodowy", + "ca": "Aparcament de cotxes" } }, "description": { @@ -55,7 +56,8 @@ "en": "This is a parking bay next to a street", "nl": "Dit is een parkeerplek langs een weg", "de": "Dies ist eine Parkbucht neben einer Straße", - "fr": "C'est un lieu de stationnement à côté d'une route" + "fr": "C'est un lieu de stationnement à côté d'une route", + "ca": "Es tracta d'un aparcament al costat d'un carrer" } }, { @@ -76,7 +78,8 @@ "nl": "Dit is een bovengrondse parkeergarage met meerdere verdiepingen", "de": "Dies ist ein mehrstöckiges oberirdisches Parkhaus", "fr": "C'est un parking à plusieurs étages", - "pl": "To jest wielopiętrowy parking" + "pl": "To jest wielopiętrowy parking", + "ca": "Es tracta d'un garatge de diverses plantes" } }, { @@ -95,7 +98,8 @@ "nl": "Dit is een strook voor parkeren op de weg", "de": "Dies ist eine Fahrspur zum Parken auf der Straße", "fr": "C'est une voie de stationnement sur la route", - "pl": "To jest pas do parkowania na jezdni" + "pl": "To jest pas do parkowania na jezdni", + "ca": "Aquest és un carril per aparcar al carrer" } }, { @@ -122,7 +126,8 @@ "en": "This is a parking on a layby", "nl": "Dit is een parkeerplek op een layby", "de": "Hier gibt es Parkmöglichkeiten auf einem kleinen Rastplatz", - "fr": "C'est un parking sur une aire de stationnement" + "fr": "C'est un parking sur une aire de stationnement", + "ca": "Aquest és un aparcament en una zona de descans" } }, { @@ -178,7 +183,8 @@ "nl": "Er zijn geen parkeerplaatsen voor gehandicapten", "de": "Es gibt keine barrierefreien Stellplätze", "fr": "Il n'y a pas de places de stationnement pour personnes à mobilité réduite", - "pl": "Nie ma tutaj żadnych miejsc parkingowych dla niepełnosprawnych" + "pl": "Nie ma tutaj żadnych miejsc parkingowych dla niepełnosprawnych", + "ca": "No hi ha places d'aparcament per a minusvàlids" }, "hideInAnswer": true }, diff --git a/assets/layers/parking_spaces/parking_spaces.json b/assets/layers/parking_spaces/parking_spaces.json index 4126a614c..69d9ae667 100644 --- a/assets/layers/parking_spaces/parking_spaces.json +++ b/assets/layers/parking_spaces/parking_spaces.json @@ -4,13 +4,15 @@ "en": "Parking Spaces", "de": "Stellplätze", "nl": "Parkeerplekken", - "pl": "Miejsca parkingowe" + "pl": "Miejsca parkingowe", + "ca": "Places d'aparcament" }, "description": { "en": "Layer showing individual parking spaces.", "de": "Ebene mit den einzelnen PKW Stellplätzen.", "nl": "Laag met individuele parkeerplekken.", - "pl": "Warstwa pokazująca pojedyncze miejsca parkingowe." + "pl": "Warstwa pokazująca pojedyncze miejsca parkingowe.", + "ca": "Capa que mostra aparcaments de cotxes individuals." }, "minzoom": 19, "source": { @@ -43,7 +45,8 @@ "en": "This is a normal parking space.", "de": "Dies ist ein normaler Stellplatz.", "nl": "Dit is een normale parkeerplek.", - "pl": "To jest zwykłe miejsce parkingowe." + "pl": "To jest zwykłe miejsce parkingowe.", + "ca": "Aquesta és una plaça d'aparcament normal." } }, { @@ -52,7 +55,8 @@ "en": "This is a disabled parking space.", "de": "Dies ist ein Behindertenstellplatz.", "nl": "Dit is een gehandicaptenparkeerplaats.", - "pl": "To jest miejsce parkingowe dla niepełnosprawnych." + "pl": "To jest miejsce parkingowe dla niepełnosprawnych.", + "ca": "Aquesta és una plaça d'aparcament per a minusvàlids." } }, { @@ -61,7 +65,8 @@ "en": "This is a private parking space.", "de": "Dies ist ein privater Stellplatz.", "nl": "Dit is een privéparkeerplek.", - "pl": "To jest prywatne miejsce parkingowe." + "pl": "To jest prywatne miejsce parkingowe.", + "ca": "Es tracta d'una plaça d'aparcament privada." } }, { @@ -70,7 +75,8 @@ "en": "This is parking space reserved for charging vehicles.", "de": "Dies ist ein Stellplatz, der für das Laden von Fahrzeugen reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor het opladen van voertuigen.", - "pl": "To miejsce parkingowe jest zarezerwowane dla ładowania pojazdów." + "pl": "To miejsce parkingowe jest zarezerwowane dla ładowania pojazdów.", + "ca": "Es tracta d'una plaça d'aparcament reservada per a la recàrrega de vehicles." } }, { @@ -79,7 +85,8 @@ "en": "This is parking space reserved for deliveries.", "de": "Dies ist ein Stellplatz, der für Lieferfahrzeuge reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor leveringen.", - "pl": "To miejsce parkingowe jest przeznaczone dla dostaw." + "pl": "To miejsce parkingowe jest przeznaczone dla dostaw.", + "ca": "Es tracta d'una plaça d'aparcament reservada per a repartidors." } }, { @@ -104,7 +111,8 @@ "en": "This is parking space reserved for buses.", "de": "Dies ist ein Stellplatz, der für Busse reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor bussen.", - "pl": "To miejsce parkingowe jest przeznaczone dla busów." + "pl": "To miejsce parkingowe jest przeznaczone dla busów.", + "ca": "Es tracta d'una plaça d'aparcament reservada per a autobusos." } }, { @@ -113,7 +121,8 @@ "en": "This is parking space reserved for motorcycles.", "de": "Dies ist ein Stellplatz, der für Motorräder reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor motoren.", - "pl": "To miejsce parkingowe jest przeznaczone dla motocykli." + "pl": "To miejsce parkingowe jest przeznaczone dla motocykli.", + "ca": "Es tracta d'una plaça d'aparcament reservada per a motos." } }, { @@ -122,7 +131,8 @@ "en": "This is a parking space reserved for parents with children.", "de": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor ouders met kinderen.", - "pl": "To miejsce jest przeznaczone dla rodziców z dziećmi." + "pl": "To miejsce jest przeznaczone dla rodziców z dziećmi.", + "ca": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." } }, { @@ -131,7 +141,8 @@ "en": "This is a parking space reserved for staff.", "de": "Dies ist ein Stellplatz, der für das Personal reserviert ist.", "nl": "Deze parkeerplek is gereserveerd voor personeel.", - "pl": "To jest miejsce parkingowe przeznaczone dla pracowników." + "pl": "To jest miejsce parkingowe przeznaczone dla pracowników.", + "ca": "Es tracta d'una plaça d'aparcament reservada al personal." } }, { diff --git a/assets/layers/postoffices/postoffices.json b/assets/layers/postoffices/postoffices.json index d61face7a..d463f9b4d 100644 --- a/assets/layers/postoffices/postoffices.json +++ b/assets/layers/postoffices/postoffices.json @@ -59,7 +59,8 @@ }, "then": { "en": "Post partner at {name}", - "de": "Postfiliale im {name}" + "de": "Postfiliale im {name}", + "ca": "Col·laborador postal a {name}" } } ] @@ -415,7 +416,8 @@ "question": { "en": "Does this post office have an ATM?", "nl": "Heeft dit postkantoor een bankautomaat?", - "de": "Verfügt die Postfiliale über einen Geldautomat?" + "de": "Verfügt die Postfiliale über einen Geldautomat?", + "ca": "Aquesta oficina postal té un caixer automàtic?" }, "mappings": [ { @@ -423,7 +425,8 @@ "then": { "en": "This post office has an ATM", "nl": "Dit postkantoor heeft een bankautomaat", - "de": "Die Postfiliale verfügt über einen Geldautomat" + "de": "Die Postfiliale verfügt über einen Geldautomat", + "ca": "Aquesta oficina postal té un caixer automàtic" } }, { @@ -431,7 +434,8 @@ "then": { "en": "This post office does not have an ATM", "nl": "Dit postkantoor heeft geen bankautomaaat", - "de": "Die Postfiliale verfügt nicht über einen Geldautomat" + "de": "Die Postfiliale verfügt nicht über einen Geldautomat", + "ca": "Aquesta oficina postal no té un caixer automàtic" } }, { @@ -439,7 +443,8 @@ "then": { "en": "This post office does have an ATM, but it is mapped as a different icon", "nl": "Dit postkantoor heeft een bankautomaat, maar deze staat apart op de kaart aangeduid", - "de": "Die Postfiliale verfügt über einen Geldautomat, der aber bereits separat kartiert ist" + "de": "Die Postfiliale verfügt über einen Geldautomat, der aber bereits separat kartiert ist", + "ca": "Aquesta oficina postal té un caixer automàtic, però està mapejat com a un element diferent" } } ] diff --git a/assets/layers/rainbow_crossings/rainbow_crossings.json b/assets/layers/rainbow_crossings/rainbow_crossings.json index 4eb94edce..a24c729d7 100644 --- a/assets/layers/rainbow_crossings/rainbow_crossings.json +++ b/assets/layers/rainbow_crossings/rainbow_crossings.json @@ -134,4 +134,4 @@ "lineCap": "square" } ] -} \ No newline at end of file +} diff --git a/assets/layers/shower/shower.json b/assets/layers/shower/shower.json index cc1761692..5dc7a8aed 100644 --- a/assets/layers/shower/shower.json +++ b/assets/layers/shower/shower.json @@ -244,4 +244,4 @@ "fr": "Une couche affichant les douches (publiques)", "ca": "Una capa que mostra dutxes (públiques)" } -} \ No newline at end of file +} diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index 13d583563..10d21c736 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -23,7 +23,8 @@ "_d=feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? ''", "_mastodon_candidate_a=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ", "_mastodon_link=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.getAttribute(\"rel\")?.indexOf('me') >= 0)[0]?.href})(feat) ", - "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a" + "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a", + "__current_background:='initial_value'" ], "tagRenderings": [ { @@ -103,6 +104,72 @@ "*": "{logout()}" } }, + { + "id": "background-layer-readonly", + "condition": { + "and": [ + "_theme:backgroundLayer~*", + "mapcomplete-preferred-background-layer~*", + "_theme:backgroundLayer!:={mapcomplete-preferred-background-layer}" + ] + }, + "render": { + "en": "This thematic map has a predefined background layer set. Your default theme setting does not apply" + } + }, + { + "id": "background-layer", + "question": { + "en": "What background layer should be shown by default?" + }, + "condition": "_theme:backgroundLayer=", + "mappings": [ + { + "if": "mapcomplete-preferred-background-layer=", + "then": { + "en": "Use the default background layer" + } + }, + { + "if": "mapcomplete-preferred-background-layer=osm", + "then": { + "en": "Use OpenStreetMap-carto as default layer" + } + }, + { + "if": "mapcomplete-preferred-background-layer=photo", + "then": { + "en": "Use aerial imagery as default background" + } + }, + { + "if": "mapcomplete-preferred-background-layer=map", + "then": { + "en": "Use a non-openstreetmap based map as default background" + } + }, + { + "if": "mapcomplete-preferred-background-layer:={__current_background}", + "then": { + "en": "Use the current background layer ({__current_background}) as default background" + }, + "hideInAnswer": { + "or": [ + "__current_background=", + "__current_background=osm", + "__current_background=initial_value" + ] + } + }, + { + "if": "mapcomplete-preferred-background-layer~*", + "then": { + "en": "Use background layer {mapcomplete-preferred-background-layer} as default background" + }, + "hideInAnswer": true + } + ] + }, { "id": "picture-license", "description": "This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants", diff --git a/assets/layers/vending_machine/condom.svg b/assets/layers/vending_machine/condom.svg new file mode 100644 index 000000000..3d2c88a75 --- /dev/null +++ b/assets/layers/vending_machine/condom.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/assets/layers/vending_machine/condom.svg.license b/assets/layers/vending_machine/condom.svg.license new file mode 100644 index 000000000..7ac182ac7 --- /dev/null +++ b/assets/layers/vending_machine/condom.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Jesus Jezzini De Anda +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/vending_machine/license_info.json b/assets/layers/vending_machine/license_info.json index c1b450200..f910585c2 100644 --- a/assets/layers/vending_machine/license_info.json +++ b/assets/layers/vending_machine/license_info.json @@ -1,4 +1,14 @@ [ + { + "path": "condom.svg", + "license": "CC0-1.0", + "authors": [ + " \tJesus Jezzini De Anda" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Condom_-_The_Noun_Project.svg" + ] + }, { "path": "cow.svg", "license": "CC-BY-4.0", diff --git a/assets/layers/vending_machine/vending_machine.json b/assets/layers/vending_machine/vending_machine.json index bbfcf09d5..deb56d624 100644 --- a/assets/layers/vending_machine/vending_machine.json +++ b/assets/layers/vending_machine/vending_machine.json @@ -130,7 +130,8 @@ "de": "Kondome werden verkauft", "fr": "Vent des préservatifs", "ca": "Es venen preservatius" - } + }, + "icon": "./assets/layers/vending_machine/condom.svg" }, { "if": "vending=coffee", @@ -287,6 +288,14 @@ "ca": "Es venen bitllets de transport públic" }, "icon": "./assets/themes/stations/public_transport_tickets.svg" + }, + { + "if": "vending=meat", + "then": { + "en": "Meat products are being sold", + "nl": "Vleesproducten worden verkocht" + }, + "icon": "./assets/layers/id_presets/temaki-meat.svg" } ], "multiAnswer": true @@ -365,7 +374,18 @@ } ] }, - "level" + "level", + { + "builtin": "phone", + "override": { + "question": { + "en": "What is the phone number of the operator of this vending machine?" + }, + "questionHint": { + "en": "This is the number you can call in case of problems with the vending machine" + } + } + } ], "calculatedTags": [ "_vending_count=feat.properties.vending.split(';').length" @@ -448,6 +468,10 @@ { "if": "vending=flowers", "then": "circle:white;./assets/layers/id_presets/maki-florist.svg" + }, + { + "if": "vending=condoms", + "then": "circle:white;./assets/layers/vending_machine/condom.svg" } ] }, @@ -797,6 +821,24 @@ "ca": "Venda de flors" }, "osmTags": "vending~i~.*flowers.*" + }, + { + "osmTags": "vending~i~.*parking_tickets.*", + "question": { + "en": "Sale of parking tickets" + } + }, + { + "osmTags": "vending=elongated_coin", + "question": { + "en": "Sale of pressed pennies" + } + }, + { + "osmTags": "vending~i~.*public_transport_tickets.*", + "question": { + "en": "Sale of public transport tickets" + } } ] } diff --git a/assets/themes/atm/atm.json b/assets/themes/atm/atm.json index 0034cceb2..6d9e827ec 100644 --- a/assets/themes/atm/atm.json +++ b/assets/themes/atm/atm.json @@ -172,4 +172,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/blind_osm/blind_osm.json b/assets/themes/blind_osm/blind_osm.json index 9095f965c..9532bd0cf 100644 --- a/assets/themes/blind_osm/blind_osm.json +++ b/assets/themes/blind_osm/blind_osm.json @@ -31,7 +31,7 @@ "startLat": 52.99238, "startLon": 6.570614, "startZoom": 20, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "builtin": "cycleways_and_roads", diff --git a/assets/themes/campersite/campersite.json b/assets/themes/campersite/campersite.json index 43ed75b0f..def4b0124 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -1607,4 +1607,4 @@ ] }, "credits": "joost schouppe" -} +} \ No newline at end of file diff --git a/assets/themes/charging_stations/charging_stations.json b/assets/themes/charging_stations/charging_stations.json index 8697b901a..91137ad15 100644 --- a/assets/themes/charging_stations/charging_stations.json +++ b/assets/themes/charging_stations/charging_stations.json @@ -57,7 +57,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "defaultBackgroundId": "CartoDB.Voyager", "layers": [ "charging_station" ] diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index d89247da8..e139cc7e9 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -465,4 +465,4 @@ "toilet" ], "credits": "Christian Neumann " -} +} \ No newline at end of file diff --git a/assets/themes/cycle_highways/cycle_highways.json b/assets/themes/cycle_highways/cycle_highways.json index 024b347ba..98df8a06c 100644 --- a/assets/themes/cycle_highways/cycle_highways.json +++ b/assets/themes/cycle_highways/cycle_highways.json @@ -265,6 +265,6 @@ ] } ], - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "credits": "L'imaginaire" } \ No newline at end of file diff --git a/assets/themes/cycle_infra/cycle_infra.json b/assets/themes/cycle_infra/cycle_infra.json index 02297eb73..66d5839a9 100644 --- a/assets/themes/cycle_infra/cycle_infra.json +++ b/assets/themes/cycle_infra/cycle_infra.json @@ -45,7 +45,6 @@ "cs": "Mapa, kde můžete prohlížet a upravovat věci související s cyklistickou infrastrukturou. Vytvořeno během #osoc21." }, "hideFromOverview": false, - "defaultBackgroundId": "CartoDB.Voyager", "icon": "./assets/themes/cycle_infra/cycle-infra.svg", "startLat": 51, "startLon": 3.75, diff --git a/assets/themes/cyclofix/cyclofix.json b/assets/themes/cyclofix/cyclofix.json index 80d11b7e3..a99d009c7 100644 --- a/assets/themes/cyclofix/cyclofix.json +++ b/assets/themes/cyclofix/cyclofix.json @@ -36,7 +36,6 @@ "credits": "Originally created during Open Summer of Code by Pieter Fiers, Thibault Declercq, Pierre Barban, Joost Schouppe and Pieter Vander Vennet", "icon": "./assets/themes/cyclofix/logo.svg", "startLat": 0, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 0, "startZoom": 1, "widenFactor": 2, diff --git a/assets/themes/drinking_water/drinking_water.json b/assets/themes/drinking_water/drinking_water.json index 95432c6d6..eb6cd55de 100644 --- a/assets/themes/drinking_water/drinking_water.json +++ b/assets/themes/drinking_water/drinking_water.json @@ -37,7 +37,6 @@ }, "icon": "./assets/themes/drinking_water/logo.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/education/education.json b/assets/themes/education/education.json index a5860ec19..fc8aa5260 100644 --- a/assets/themes/education/education.json +++ b/assets/themes/education/education.json @@ -24,7 +24,6 @@ "eu": "Hezkuntza", "pl": "Edukacja" }, - "defaultBackgroundId": "CartoDB.Voyager", "startLat": 0, "startLon": 0, "startZoom": 0, diff --git a/assets/themes/elongated_coin/elongated_coin.json b/assets/themes/elongated_coin/elongated_coin.json index 6cb1ca911..4ac8f93a1 100644 --- a/assets/themes/elongated_coin/elongated_coin.json +++ b/assets/themes/elongated_coin/elongated_coin.json @@ -19,4 +19,4 @@ "startLat": 53.0565, "startLon": 8.7492, "startZoom": 11 -} +} \ No newline at end of file diff --git a/assets/themes/etymology/etymology.json b/assets/themes/etymology/etymology.json index 074b624fe..e901d7153 100644 --- a/assets/themes/etymology/etymology.json +++ b/assets/themes/etymology/etymology.json @@ -288,4 +288,4 @@ } ], "hideFromOverview": false -} +} \ No newline at end of file diff --git a/assets/themes/fritures/fritures.json b/assets/themes/fritures/fritures.json index 18804d332..64cf09969 100644 --- a/assets/themes/fritures/fritures.json +++ b/assets/themes/fritures/fritures.json @@ -69,4 +69,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/ghostbikes/ghostbikes.json b/assets/themes/ghostbikes/ghostbikes.json index 3d80bbc44..74f25481a 100644 --- a/assets/themes/ghostbikes/ghostbikes.json +++ b/assets/themes/ghostbikes/ghostbikes.json @@ -44,7 +44,7 @@ "layers": [ "ghost_bike" ], - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "clustering": { "maxZoom": 0 } diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 1a35d878d..b3931dcb1 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -773,4 +773,4 @@ "overpassMaxZoom": 15, "osmApiTileSize": 17, "credits": "Pieter Vander Vennet" -} +} \ No newline at end of file diff --git a/assets/themes/healthcare/healthcare.json b/assets/themes/healthcare/healthcare.json index 70052f91e..488a88fe3 100644 --- a/assets/themes/healthcare/healthcare.json +++ b/assets/themes/healthcare/healthcare.json @@ -27,7 +27,6 @@ }, "icon": "./assets/layers/doctors/doctors.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/indoors/indoors.json b/assets/themes/indoors/indoors.json index bef9d9bbe..60ae89052 100644 --- a/assets/themes/indoors/indoors.json +++ b/assets/themes/indoors/indoors.json @@ -27,7 +27,6 @@ }, "icon": "./assets/layers/entrance/entrance.svg", "startLat": 51.17181, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.144383, "startZoom": 14, "widenFactor": 2, diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 2c38dae55..62d38abba 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -131,7 +131,7 @@ "render": { "en": "Change made with {host}", "ca": "Canviat fet amb {host}", - "de": "Änderung über {host}", + "de": "Geändert über {host}", "fr": "Modification faite avec {host}", "nl": "Wijziging gemaakt met {host}" }, diff --git a/assets/themes/maps/maps.json b/assets/themes/maps/maps.json index c484a92c1..4d88672cd 100644 --- a/assets/themes/maps/maps.json +++ b/assets/themes/maps/maps.json @@ -47,7 +47,7 @@ "startLon": 0, "startZoom": 1, "widenFactor": 5, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ "map" ] diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json index 716632054..38544e0b7 100644 --- a/assets/themes/onwheels/onwheels.json +++ b/assets/themes/onwheels/onwheels.json @@ -24,7 +24,6 @@ }, "icon": "./assets/themes/onwheels/crest.svg", "startLat": 50.86622, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.350103, "startZoom": 17, "widenFactor": 2, @@ -525,4 +524,4 @@ ] }, "enableDownload": true -} +} \ No newline at end of file diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 2b21d36c9..a453698d5 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -41,6 +41,5 @@ "layers": [ "windturbine" ], - "defaultBackgroundId": "CartoDB.Voyager", "credits": "Seppe Santens" } \ No newline at end of file diff --git a/assets/themes/osm_community_index/osm_community_index.json b/assets/themes/osm_community_index/osm_community_index.json index 229f9aca4..16aa4e481 100644 --- a/assets/themes/osm_community_index/osm_community_index.json +++ b/assets/themes/osm_community_index/osm_community_index.json @@ -29,7 +29,6 @@ }, "icon": "./assets/themes/osm_community_index/osm.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "clustering": false, diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json index e63b216c6..650095516 100644 --- a/assets/themes/pets/pets.json +++ b/assets/themes/pets/pets.json @@ -144,14 +144,7 @@ "width": 5 } ], - "presets": [ - { - "tags": [ - "shop=yes", - "dog=yes" - ] - } - ], + "=presets": [], "source": { "=osmTags": { "and": [ diff --git a/assets/themes/playgrounds/playgrounds.json b/assets/themes/playgrounds/playgrounds.json index 50820e091..50fd8786d 100644 --- a/assets/themes/playgrounds/playgrounds.json +++ b/assets/themes/playgrounds/playgrounds.json @@ -71,4 +71,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/postboxes/postboxes.json b/assets/themes/postboxes/postboxes.json index 172977b86..de8889598 100644 --- a/assets/themes/postboxes/postboxes.json +++ b/assets/themes/postboxes/postboxes.json @@ -46,7 +46,6 @@ "startLon": 9.9937, "startZoom": 13, "widenFactor": 1.5, - "defaultBackgroundId": "CartoDB.Voyager", "clustering": { "maxZoom": 14, "minNeededElements": 100 diff --git a/assets/themes/rainbow_crossings/rainbow_crossings.json b/assets/themes/rainbow_crossings/rainbow_crossings.json index 689c29df9..043c87e28 100644 --- a/assets/themes/rainbow_crossings/rainbow_crossings.json +++ b/assets/themes/rainbow_crossings/rainbow_crossings.json @@ -24,7 +24,6 @@ }, "icon": "./assets/themes/rainbow_crossings/logo.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index be57a32c6..3b382ed8b 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -27,7 +27,7 @@ "startZoom": 12, "widenFactor": 1.2, "socialImage": "./assets/themes/speelplekken/social_image.jpg", - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "id": "shadow", diff --git a/assets/themes/stations/stations.json b/assets/themes/stations/stations.json index f24f1464a..8b242d1af 100644 --- a/assets/themes/stations/stations.json +++ b/assets/themes/stations/stations.json @@ -27,7 +27,7 @@ "startLon": 0, "startZoom": 0, "hideFromOverview": true, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "builtin": "indoors", @@ -412,4 +412,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/themes/surveillance/surveillance.json b/assets/themes/surveillance/surveillance.json index 54d568bf5..4e616eb45 100644 --- a/assets/themes/surveillance/surveillance.json +++ b/assets/themes/surveillance/surveillance.json @@ -54,8 +54,8 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "defaultBackgroundId": "osm", + "defaultBackgroundId": "maptiler.carto", "layers": [ "surveillance_camera" ] -} \ No newline at end of file +} diff --git a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json index 86621c491..6436b4362 100644 --- a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json +++ b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json @@ -239,4 +239,4 @@ "hideFromOverview": true, "enableMoreQuests": false, "enableShareScreen": false -} +} \ No newline at end of file diff --git a/assets/themes/vending_machine/vending_machine.json b/assets/themes/vending_machine/vending_machine.json index d34a960bf..5257a2840 100644 --- a/assets/themes/vending_machine/vending_machine.json +++ b/assets/themes/vending_machine/vending_machine.json @@ -29,6 +29,7 @@ "sameAs": "vending_machine" }, "minzoom": 18, + "=presets": [], "source": { "osmTags": { "and": [ @@ -36,8 +37,7 @@ "vending!~(parking_tickets|elongated_coin|public_transport_tickets)" ] } - }, - "=presets": [] + } } }, { diff --git a/assets/themes/walls_and_buildings/walls_and_buildings.json b/assets/themes/walls_and_buildings/walls_and_buildings.json index 9175d774a..1c221af37 100644 --- a/assets/themes/walls_and_buildings/walls_and_buildings.json +++ b/assets/themes/walls_and_buildings/walls_and_buildings.json @@ -26,7 +26,6 @@ }, "icon": "./assets/layers/walls_and_buildings/walls_and_buildings.png", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/width/width.json b/assets/themes/width/width.json index 6f16d225e..6d0bfb114 100644 --- a/assets/themes/width/width.json +++ b/assets/themes/width/width.json @@ -271,4 +271,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/langs/ca.json b/langs/ca.json index 772d01878..21c6d7381 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -405,6 +405,9 @@ "doDelete": "Esborrar imatge", "dontDelete": "Cancel·lar", "isDeleted": "Esborrada", + "nearby": { + "seeNearby": "Explora i vincula imatges properes" + }, "pleaseLogin": "Entrar per pujar una foto", "respectPrivacy": "Respecta la privacitat. No fotografiïs gent o matrícules. No facis servir imatges de Google Maps, Google Streetview o altres fonts amb copyright.", "toBig": "La teva imatge és massa gran ara que medeix {actual_size}. Usa imatges de com a molt {max_size}", diff --git a/langs/de.json b/langs/de.json index f4b2280f8..671f97a02 100644 --- a/langs/de.json +++ b/langs/de.json @@ -405,6 +405,11 @@ "doDelete": "Bild entfernen", "dontDelete": "Abbrechen", "isDeleted": "Gelöscht", + "nearby": { + "link": "Dieses Bild zeigt das Objekt", + "seeNearby": "Bilder in der Nähe durchsuchen und verlinken", + "title": "Straßenbilder in der Nähe" + }, "pleaseLogin": "Bitte anmelden, um ein Bild hinzuzufügen", "respectPrivacy": "Bitte respektieren Sie die Privatsphäre. Fotografieren Sie weder Personen noch Nummernschilder. Benutzen Sie keine urheberrechtlich geschützten Quellen wie z.B. Google Maps oder Google Streetview.", "toBig": "Ihr Bild ist mit {actual_size} zu groß. Die maximale Bildgröße ist {max_size}", diff --git a/langs/en.json b/langs/en.json index 7d75d0a7b..42432d12c 100644 --- a/langs/en.json +++ b/langs/en.json @@ -344,6 +344,8 @@ }, "useSearch": "Use the search above to see presets", "useSearchForMore": "Use the search function to search within {total} more values…", + "waitingForGeopermission": "Waiting for your permission to use the geolocation…", + "waitingForLocation": "Searching your current location…", "weekdays": { "abbreviations": { "friday": "Fri", @@ -414,6 +416,22 @@ "pleaseLogin": "Please log in to add a picture", "respectPrivacy": "Do not photograph people nor license plates. Do not upload Google Maps, Google Streetview or other copyrighted sources.", "toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}", + "upload": { + "failReasons": "You might have lost connection to the internet", + "failReasonsAdvanced": "Alternatively, make sure your browser and extensions do not block third-party API's.", + "multiple": { + "done": "{count} images are successfully uploaded. Thank you!", + "partiallyDone": "{count} images are getting uploaded, {done} images are done…", + "someFailed": "Sorry, we could not upload {count} images", + "uploading": "{count} images are getting uploaded…" + }, + "one": { + "done": "Your image was successfully uploaded. Thank you!", + "failed": "Sorry, we could not upload your image", + "retrying": "Your image is getting uploaded again…", + "uploading": "Your image is getting uploaded…" + } + }, "uploadDone": "Your picture has been added. Thanks for helping out!", "uploadFailed": "Could not upload your picture. Are you connected to the Internet, and allow third party API's? The Brave browser or the uMatrix plugin might block them.", "uploadMultipleDone": "{count} pictures have been added. Thanks for helping out!", diff --git a/langs/fr.json b/langs/fr.json index 19c56011b..7d9422991 100644 --- a/langs/fr.json +++ b/langs/fr.json @@ -279,7 +279,8 @@ "public": { "name": "Publique" } - } + }, + "title": "Envoyer votre trace sur OpenStreetMap.org" }, "weekdays": { "abbreviations": { diff --git a/langs/layers/ca.json b/langs/layers/ca.json index dee218cc2..7664e722d 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -3940,9 +3940,6 @@ }, "2": { "options": { - "0": { - "question": "Té menú vegetarià" - }, "1": { "question": "Només negocis de menjar ràpid" }, @@ -3951,17 +3948,10 @@ } } }, - "3": { - "options": { - "0": { - "question": "Té menú vegà" - } - } - }, "4": { "options": { "0": { - "question": "Té menú halal" + "question": "Té menú vegà" } } }, @@ -4795,6 +4785,36 @@ } }, "question": "En quines dades es basa aquest mapa?" + }, + "map_size": { + "mappings": { + "0": { + "then": "Un mapa de les habitacions dins d'un edifici" + }, + "1": { + "then": "Un mapa d'un lloc especial, com un castell històric, un parc, un campus, un bosc, …" + }, + "2": { + "then": "Un mapa que mostra el poble o la ciutat" + }, + "3": { + "then": " Un mapa d'una ciutat" + }, + "4": { + "then": "El mapa d'una regió sencera, mostrant múltiples ciutats i pobles" + } + }, + "question": "Quina és la mida de l'àrea mostrada en el mapa?" + }, + "map_type": { + "mappings": { + "0": { + "then": "Mapa topogràfic

El mapa conté línies de contorn.

" + }, + "2": { + "then": "Això és un mapa esquemàtic.

Un mapa esbossat amb només camins importants i PDI. Els angles, els trajectes etc. són merament il·lustratius, no acurat.

" + } + } } }, "title": { @@ -5061,18 +5081,56 @@ } }, "note": { + "filter": { + "10": { + "options": { + "1": { + "question": "Oculta les notes d'importació" + }, + "2": { + "question": "Mostrar només les notes d'importació" + } + } + } + }, + "name": "Notes d'OpenStreetMap", + "tagRenderings": { + "nearby-images": { + "render": { + "before": "

Imatges properes

Les imatges de sota són imatges geoetiquetades properes i poden ser útils per a encarregar-se d'aquesta nota." + } + }, + "report-contributor": { + "render": "Reporta {_first_user} per spam o missatges inapropiats" + }, + "report-note": { + "render": "Reporta aquesta nota com spam o inapropiada" + } + }, "title": { + "mappings": { + "0": { + "then": "Nota tancada" + } + }, "render": "Nota" } }, "observation_tower": { + "description": "Torres amb vista panoràmica", "name": "Torres d'observació", "tagRenderings": { + "Fee": { + "question": "Quant hi ha que pagar per entrar a aquesta torre?", + "render": "Visitar aquesta torre costa {charge}" + }, "Height": { - "question": "Quina és l'alçada d'aquesta torre?" + "question": "Quina és l'alçada d'aquesta torre?", + "render": "Aquesta torre fa {height}" }, "Operator": { - "question": "Qui manté aquesta torre?" + "question": "Qui manté aquesta torre?", + "render": "Mantés per {operator}" }, "access": { "mappings": { @@ -5085,6 +5143,17 @@ }, "question": "Es pot visitar aquesta torre?" }, + "elevator": { + "mappings": { + "0": { + "then": "Aquesta torre té un ascensor que porta els visitants al cim" + }, + "1": { + "then": "Aquesta torre no té ascensor" + } + }, + "question": "Aquesta torre té ascensor?" + }, "name": { "mappings": { "0": { @@ -5272,6 +5341,9 @@ "0": { "then": "Hi ha places d'aparcament per a gent amb mobilitat reduïda, però no es sap quantes" }, + "1": { + "then": "No hi ha places d'aparcament per a minusvàlids" + }, "2": { "then": "No hi ha places d'aparcament per a persones amb mobilitat reduïda" } @@ -5284,15 +5356,32 @@ "0": { "then": "Aquest és un aparcament en superfície" }, + "1": { + "then": "Es tracta d'un aparcament al costat d'un carrer" + }, "2": { "then": "Aquest és un aparcament subterrani" + }, + "3": { + "then": "Es tracta d'un garatge de diverses plantes" + }, + "5": { + "then": "Aquest és un carril per aparcar al carrer" + }, + "8": { + "then": "Aquest és un aparcament en una zona de descans" } }, "question": "Quin tipus d'aparcament és aquest?" } + }, + "title": { + "render": "Aparcament de cotxes" } }, "parking_spaces": { + "description": "Capa que mostra aparcaments de cotxes individuals.", + "name": "Places d'aparcament", "tagRenderings": { "capacity": { "mappings": { @@ -5306,6 +5395,33 @@ "mappings": { "0": { "then": "És un lloc normal d'aparcament." + }, + "1": { + "then": "Aquesta és una plaça d'aparcament normal." + }, + "2": { + "then": "Aquesta és una plaça d'aparcament per a minusvàlids." + }, + "3": { + "then": "Es tracta d'una plaça d'aparcament privada." + }, + "4": { + "then": "Es tracta d'una plaça d'aparcament reservada per a la recàrrega de vehicles." + }, + "5": { + "then": "Es tracta d'una plaça d'aparcament reservada per a repartidors." + }, + "8": { + "then": "Es tracta d'una plaça d'aparcament reservada per a autobusos." + }, + "9": { + "then": "Es tracta d'una plaça d'aparcament reservada per a motos." + }, + "10": { + "then": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." + }, + "11": { + "then": "Es tracta d'una plaça d'aparcament reservada al personal." } } } @@ -5512,6 +5628,20 @@ } }, "tagRenderings": { + "has_atm": { + "mappings": { + "0": { + "then": "Aquesta oficina postal té un caixer automàtic" + }, + "1": { + "then": "Aquesta oficina postal no té un caixer automàtic" + }, + "2": { + "then": "Aquesta oficina postal té un caixer automàtic, però està mapejat com a un element diferent" + } + }, + "question": "Aquesta oficina postal té un caixer automàtic?" + }, "letter-from": { "mappings": { "0": { @@ -5606,6 +5736,11 @@ } }, "title": { + "mappings": { + "1": { + "then": "Col·laborador postal a {name}" + } + }, "render": "Oficina de correus" } }, diff --git a/langs/layers/da.json b/langs/layers/da.json index c292c0ae0..b47321dca 100644 --- a/langs/layers/da.json +++ b/langs/layers/da.json @@ -2063,13 +2063,6 @@ }, "food": { "filter": { - "4": { - "options": { - "0": { - "question": "Har en halalmenu" - } - } - }, "5": { "options": { "0": { diff --git a/langs/layers/de.json b/langs/layers/de.json index 2990d5b59..87b25f976 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -4202,6 +4202,18 @@ } } }, + "speech_output_available": { + "mappings": { + "0": { + "then": "Der Aufzug verfügt über eine Sprachausgabe" + }, + "1": { + "then": "Der Aufzug verfügt über keine Sprachausgabe" + } + }, + "question": "Verfügt der Aufzug über eine Sprachausgabe?", + "questionHint": "Z.B. werden Stockwerke angesagt" + }, "tactile_writing_language": { "render": { "special": { @@ -4839,9 +4851,6 @@ }, "2": { "options": { - "0": { - "question": "Vegetarische Gerichte im Angebot" - }, "1": { "question": "Nur Fastfood-Geschäfte" }, @@ -4850,17 +4859,10 @@ } } }, - "3": { - "options": { - "0": { - "question": "Vegane Gerichte im Angebot" - } - } - }, "4": { "options": { "0": { - "question": "Halal Gerichte im Angebot" + "question": "Vegane Gerichte im Angebot" } } }, diff --git a/langs/layers/en.json b/langs/layers/en.json index cecb797c1..d76fa43e5 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -4892,7 +4892,7 @@ "2": { "options": { "0": { - "question": "Has a vegetarian menu" + "question": "Restaurants and fast food businesses" }, "1": { "question": "Only fastfood businesses" @@ -4905,14 +4905,14 @@ "3": { "options": { "0": { - "question": "Has a vegan menu" + "question": "Has a vegetarian menu" } } }, "4": { "options": { "0": { - "question": "Has a halal menu" + "question": "Has a vegan menu" } } }, @@ -8872,7 +8872,7 @@ "has_alpr": { "mappings": { "0": { - "then": "This is a normal camera" + "then": "This is a camera without number plate recognition." }, "1": { "then": "This is an ALPR (Automatic License Plate Reader)" @@ -9652,6 +9652,32 @@ }, "question": "Should questions for unknown data fields appear one-by-one or together?" }, + "background-layer": { + "mappings": { + "0": { + "then": "Use the default background layer" + }, + "1": { + "then": "Use OpenStreetMap-carto as default layer" + }, + "2": { + "then": "Use aerial imagery as default background" + }, + "3": { + "then": "Use a non-openstreetmap based map as default background" + }, + "4": { + "then": "Use the current background layer ({__current_background}) as default background" + }, + "5": { + "then": "Use background layer {mapcomplete-preferred-background-layer} as default background" + } + }, + "question": "What background layer should be shown by default?" + }, + "background-layer-readonly": { + "render": "This thematic map has a predefined background layer set. Your default theme setting does not apply" + }, "contributor-thanks": { "mappings": { "0": { @@ -9870,8 +9896,20 @@ "15": { "question": "Sale of potatoes" }, + "16": { + "question": "Sale of meat" + }, "17": { "question": "Sale of flowers" + }, + "18": { + "question": "Sale of parking tickets" + }, + "19": { + "question": "Sale of pressed pennies" + }, + "20": { + "question": "Sale of public transport tickets" } } } @@ -9904,6 +9942,12 @@ "question": "Who operates this vending machine?", "render": "This vending machine is operated by {operator}" }, + "phone": { + "override": { + "question": "What is the phone number of the operator of this vending machine?", + "questionHint": "This is the number you can call in case of problems with the vending machine" + } + }, "vending": { "mappings": { "0": { @@ -9965,6 +10009,9 @@ }, "19": { "then": "Public transport tickets are sold" + }, + "20": { + "then": "Meat products are being sold" } }, "question": "What does this vending machine sell?", diff --git a/langs/layers/es.json b/langs/layers/es.json index b05d4a0e5..96bab0796 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -2679,24 +2679,10 @@ }, "description": "Una capa mostrando restaurantes y locales de comida rápida (con un renderizado especial para friterías)", "filter": { - "2": { - "options": { - "0": { - "question": "Tiene menú vegetariano" - } - } - }, - "3": { - "options": { - "0": { - "question": "Tiene menú vegano" - } - } - }, "4": { "options": { "0": { - "question": "Tiene menú halah" + "question": "Tiene menú vegano" } } }, @@ -3215,7 +3201,7 @@ "question": "Todas las notas" }, "1": { - "question": "Ocultar las nostras de importación" + "question": "Ocultar las notas de importación" }, "2": { "question": "Solo mostrar las notas de importación" @@ -3282,7 +3268,7 @@ "then": "Esta torre no tiene ascensor" } }, - "question": "¿Tiene ascensor esta torre?" + "question": "¿Esta torre tiene ascensor?" }, "name": { "mappings": { diff --git a/langs/layers/fr.json b/langs/layers/fr.json index e58849563..48944c237 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -3409,9 +3409,6 @@ }, "2": { "options": { - "0": { - "question": "A un menu végétarien" - }, "1": { "question": "Seulement les fastfood" }, @@ -3420,17 +3417,10 @@ } } }, - "3": { - "options": { - "0": { - "question": "A un menu végétalien" - } - } - }, "4": { "options": { "0": { - "question": "A un menu halal" + "question": "A un menu végétalien" } } }, diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 2089e0b5e..817c577ec 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -4526,24 +4526,17 @@ } } }, - "2": { + "3": { "options": { "0": { "question": "Heeft een vegetarisch menu" } } }, - "3": { - "options": { - "0": { - "question": "Heeft een veganistisch menu" - } - } - }, "4": { "options": { "0": { - "question": "Heeft een halal menu" + "question": "Heeft een veganistisch menu" } } }, @@ -9153,6 +9146,9 @@ }, "19": { "then": "Openbaar vervoerkaartjes worden verkocht" + }, + "20": { + "then": "Vleesproducten worden verkocht" } }, "question": "Wat verkoopt deze verkoopautomaat?", diff --git a/langs/layers/pl.json b/langs/layers/pl.json index 3624477a4..606dc16b0 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -1473,9 +1473,6 @@ }, "2": { "options": { - "0": { - "question": "Ma menu wegetariańskie" - }, "1": { "question": "Tylko fast-foody" }, @@ -1484,7 +1481,7 @@ } } }, - "3": { + "4": { "options": { "0": { "question": "Ma menu wegańskie" diff --git a/langs/themes/de.json b/langs/themes/de.json index 8a31bbcd5..d528c5bb6 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -925,7 +925,7 @@ }, "host": { "question": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?", - "render": "Änderung über {host}" + "render": "Geändert über {host}" }, "locale": { "question": "In welcher Benutzersprache wurde diese Änderung vorgenommen?", diff --git a/package.json b/package.json index f3f0aecdb..cbc562381 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.33.3", + "version": "0.33.4", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index b6011afb6..da51a2a7f 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -858,6 +858,10 @@ video { margin-right: 3rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mr-2 { margin-right: 0.5rem; } @@ -886,10 +890,6 @@ video { margin-right: 0.25rem; } -.mb-4 { - margin-bottom: 1rem; -} - .ml-1 { margin-left: 0.25rem; } @@ -2662,6 +2662,26 @@ a.link-underline { opacity: 1; } +@media (prefers-reduced-motion: no-preference) { + @-webkit-keyframes spin { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + @keyframes spin { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + .motion-safe\:animate-spin { + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; + } +} + @media (max-width: 480px) { .max-\[480px\]\:w-full { width: 100%; diff --git a/scripts/lint.ts b/scripts/lint.ts index d3c8d0042..a144083df 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -3,10 +3,10 @@ import { writeFileSync } from "fs" import { FixLegacyTheme, UpdateLegacyLayer, -} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert" -import Translations from "../UI/i18n/Translations" -import { Translation } from "../UI/i18n/Translation" -import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" +} from "../src/Models/ThemeConfig/Conversion/LegacyJsonConvert" +import Translations from "../src/UI/i18n/Translations" +import { Translation } from "../src/UI/i18n/Translation" +import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" /* * This script reads all theme and layer files and reformats them inplace diff --git a/src/Logic/Actors/PreferredRasterLayerSelector.ts b/src/Logic/Actors/PreferredRasterLayerSelector.ts new file mode 100644 index 000000000..7ed20aa95 --- /dev/null +++ b/src/Logic/Actors/PreferredRasterLayerSelector.ts @@ -0,0 +1,67 @@ +import { Store, UIEventSource } from "../UIEventSource"; +import { RasterLayerPolygon } from "../../Models/RasterLayers"; + +/** + * Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value. + * + * It the requested layer is not available, a layer of the same type will be selected. + */ +export class PreferredRasterLayerSelector { + private readonly _rasterLayerSetting: UIEventSource; + private readonly _availableLayers: Store; + private readonly _preferredBackgroundLayer: UIEventSource; + private readonly _queryParameter: UIEventSource; + + constructor(rasterLayerSetting: UIEventSource, availableLayers: Store, queryParameter: UIEventSource, preferredBackgroundLayer: UIEventSource) { + this._rasterLayerSetting = rasterLayerSetting; + this._availableLayers = availableLayers; + this._queryParameter = queryParameter; + this._preferredBackgroundLayer = preferredBackgroundLayer; + const self = this; + + this._rasterLayerSetting.addCallbackD(layer => { + if (layer.properties.id !== this._queryParameter.data) { + this._queryParameter.setData(undefined); + return true; + } + }); + + + this._queryParameter.addCallbackAndRunD(_ => { + const isApplied = self.updateLayer(); + if (!isApplied) { + // A different layer was set as background + // We remove this queryParameter instead + self._queryParameter.setData(undefined); + return true; // Unregister + } + }); + + this._preferredBackgroundLayer.addCallbackD(_ => self.updateLayer()); + + this._availableLayers.addCallbackD(_ => self.updateLayer()); + + } + + /** + * Returns 'true' if the target layer is set or is the current layer + * @private + */ + private updateLayer() { + + // What is the ID of the layer we have to (try to) load? + const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data; + const available = this._availableLayers.data; + const isCategory = targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map" + const foundLayer = isCategory ? available.find(l => l.properties.category === targetLayerId) : available.find(l => l.properties.id === targetLayerId); + if (foundLayer) { + this._rasterLayerSetting.setData(foundLayer); + return true; + } + + // The current layer is not in view + + + } + +} diff --git a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index cd7522a29..c8186a8ba 100644 --- a/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/src/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -7,7 +7,7 @@ import { OsmTags } from "../../../Models/OsmFeature" */ export default class FeaturePropertiesStore { private readonly _elements = new Map>>() - + public readonly aliases = new Map() constructor(...sources: FeatureSource[]) { for (const source of sources) { this.trackFeatureSource(source) @@ -92,7 +92,6 @@ export default class FeaturePropertiesStore { }) } - // noinspection JSUnusedGlobalSymbols public addAlias(oldId: string, newId: string): void { if (newId === undefined) { // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! @@ -112,6 +111,7 @@ export default class FeaturePropertiesStore { } element.data.id = newId this._elements.set(newId, element) + this.aliases.set(newId, oldId) element.ping() } diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts new file mode 100644 index 000000000..85964a507 --- /dev/null +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -0,0 +1,159 @@ +import { ImageUploader } from "./ImageUploader"; +import LinkImageAction from "../Osm/Actions/LinkImageAction"; +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; +import { OsmId, OsmTags } from "../../Models/OsmFeature"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import { Store, UIEventSource } from "../UIEventSource"; +import { OsmConnection } from "../Osm/OsmConnection"; +import { Changes } from "../Osm/Changes"; +import Translations from "../../UI/i18n/Translations"; +import NoteCommentElement from "../../UI/Popup/NoteCommentElement"; + + +/** + * The ImageUploadManager has a + */ +export class ImageUploadManager { + + private readonly _uploader: ImageUploader; + private readonly _featureProperties: FeaturePropertiesStore; + private readonly _layout: LayoutConfig; + + private readonly _uploadStarted: Map> = new Map(); + private readonly _uploadFinished: Map> = new Map(); + private readonly _uploadFailed: Map> = new Map(); + private readonly _uploadRetried: Map> = new Map(); + private readonly _uploadRetriedSuccess: Map> = new Map(); + private readonly _osmConnection: OsmConnection; + private readonly _changes: Changes; + + constructor(layout: LayoutConfig, uploader: ImageUploader, featureProperties: FeaturePropertiesStore, osmConnection: OsmConnection, changes: Changes) { + this._uploader = uploader; + this._featureProperties = featureProperties; + this._layout = layout; + this._osmConnection = osmConnection; + this._changes = changes; + } + + /** + * Gets various counters. + * Note that counters can only increase + * If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased + * @param featureId: the id of the feature you want information for. '*' has a global counter + */ + public getCountsFor(featureId: string | "*"): { + retried: Store; + uploadStarted: Store; + retrySuccess: Store; + failed: Store; + uploadFinished: Store + } { + return { + uploadStarted: this.getCounterFor(this._uploadStarted, featureId), + uploadFinished: this.getCounterFor(this._uploadFinished, featureId), + retried: this.getCounterFor(this._uploadRetried, featureId), + failed: this.getCounterFor(this._uploadFailed, featureId), + retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId) + + }; + } + + /** + * Uploads the given image, applies the correct title and license for the known user. + * Will then add this image to the OSM-feature or the OSM-note + */ + public async uploadImageAndApply(file: File, tagsStore: UIEventSource) : Promise{ + + const sizeInBytes = file.size; + const tags= tagsStore.data + const featureId = tags.id; + const self = this; + if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) { + this.increaseCountFor(this._uploadStarted, featureId); + this.increaseCountFor(this._uploadFailed, featureId); + throw ( + Translations.t.image.toBig.Subs({ + actual_size: Math.floor(sizeInBytes / 1000000) + "MB", + max_size: self._uploader.maxFileSizeInMegabytes + "MB" + }).txt + ); + } + + + const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); + const license = licenseStore?.data ?? "CC0"; + + const matchingLayer = this._layout?.getMatchingLayer(tags); + + const title = + matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ?? + tags.name ?? + "https//osm.org/" + tags.id; + const description = [ + "author:" + this._osmConnection.userDetails.data.name, + "license:" + license, + "osmid:" + tags.id + ].join("\n"); + + console.log("Upload done, creating "); + const action = await this.uploadImageWithLicense(featureId, title, description, file); + if(!isNaN(Number( featureId))){ + // THis is a map note + const url = action._url + await this._osmConnection.addCommentToNote(featureId, url) + NoteCommentElement.addCommentTo(url, > tagsStore, {osmConnection: this._osmConnection}) + return + } + await this._changes.applyAction(action); + } + + private async uploadImageWithLicense( + featureId: OsmId, + title: string, description: string, blob: File + ): Promise { + this.increaseCountFor(this._uploadStarted, featureId); + const properties = this._featureProperties.getStore(featureId); + let key: string; + let value: string; + try { + ({ key, value } = await this._uploader.uploadImage(title, description, blob)); + } catch (e) { + this.increaseCountFor(this._uploadRetried, featureId); + console.error("Could not upload image, trying again:", e); + try { + + ({ key, value } = await this._uploader.uploadImage(title, description, blob)); + this.increaseCountFor(this._uploadRetriedSuccess, featureId); + } catch (e) { + console.error("Could again not upload image due to", e); + this.increaseCountFor(this._uploadFailed, featureId); + } + + } + console.log("Uploading done, creating action for", featureId); + const action = new LinkImageAction(featureId, key, value, properties, { + theme: this._layout.id, + changeType: "add-image" + }); + this.increaseCountFor(this._uploadFinished, featureId); + return action; + } + + private getCounterFor(collection: Map>, key: string | "*") { + if (this._featureProperties.aliases.has(key)) { + key = this._featureProperties.aliases.get(key); + } + if (!collection.has(key)) { + collection.set(key, new UIEventSource(0)); + } + return collection.get(key); + } + + private increaseCountFor(collection: Map>, key: string | "*") { + const counter = this.getCounterFor(collection, key); + counter.setData(counter.data + 1); + const global = this.getCounterFor(collection, "*"); + global.setData(counter.data + 1); + } + +} diff --git a/src/Logic/ImageProviders/ImageUploader.ts b/src/Logic/ImageProviders/ImageUploader.ts new file mode 100644 index 000000000..3efb8d279 --- /dev/null +++ b/src/Logic/ImageProviders/ImageUploader.ts @@ -0,0 +1,15 @@ +export interface ImageUploader { + maxFileSizeInMegabytes?: number; + /** + * Uploads the 'blob' as image, with some metadata. + * Returns the URL to be linked + the appropriate key to add this to OSM + * @param title + * @param description + * @param blob + */ + uploadImage( + title: string, + description: string, + blob: File + ): Promise<{ key: string, value: string }>; +} diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index a7a142733..4e4a1c541 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -1,60 +1,30 @@ -import ImageProvider, { ProvidedImage } from "./ImageProvider" -import BaseUIElement from "../../UI/BaseUIElement" -import { Utils } from "../../Utils" -import Constants from "../../Models/Constants" -import { LicenseInfo } from "./LicenseInfo" +import ImageProvider, { ProvidedImage } from "./ImageProvider"; +import BaseUIElement from "../../UI/BaseUIElement"; +import { Utils } from "../../Utils"; +import Constants from "../../Models/Constants"; +import { LicenseInfo } from "./LicenseInfo"; +import { ImageUploader } from "./ImageUploader"; -export class Imgur extends ImageProvider { +export class Imgur extends ImageProvider implements ImageUploader{ public static readonly defaultValuePrefix = ["https://i.imgur.com"] public static readonly singleton = new Imgur() public readonly defaultKeyPrefixes: string[] = ["image"] - + public readonly maxFileSizeInMegabytes = 10 private constructor() { super() } - static uploadMultiple( + /** + * Uploads an image, returns the URL where to find the image + * @param title + * @param description + * @param blob + */ + public async uploadImage( title: string, description: string, - blobs: FileList, - handleSuccessfullUpload: (imageURL: string) => Promise, - allDone: () => void, - onFail: (reason: string) => void, - offset: number = 0 - ) { - if (blobs.length == offset) { - allDone() - return - } - const blob = blobs.item(offset) - const self = this - this.uploadImage( - title, - description, - blob, - async (imageUrl) => { - await handleSuccessfullUpload(imageUrl) - self.uploadMultiple( - title, - description, - blobs, - handleSuccessfullUpload, - allDone, - onFail, - offset + 1 - ) - }, - onFail - ) - } - - static uploadImage( - title: string, - description: string, - blob: File, - handleSuccessfullUpload: (imageURL: string) => Promise, - onFail: (reason: string) => void - ) { + blob: File + ): Promise<{ key: string, value: string }> { const apiUrl = "https://api.imgur.com/3/image" const apiKey = Constants.ImgurApiKey @@ -63,6 +33,7 @@ export class Imgur extends ImageProvider { formData.append("title", title) formData.append("description", description) + const settings: RequestInit = { method: "POST", body: formData, @@ -74,17 +45,9 @@ export class Imgur extends ImageProvider { } // Response contains stringified JSON - // Image URL available at response.data.link - fetch(apiUrl, settings) - .then(async function (response) { - const content = await response.json() - await handleSuccessfullUpload(content.data.link) - }) - .catch((reason) => { - console.log("Uploading to IMGUR failed", reason) - // @ts-ignore - onFail(reason) - }) + const response = await fetch(apiUrl, settings) + const content = await response.json() + return { key: "image", value: content.data.link } } SourceIcon(): BaseUIElement { diff --git a/src/Logic/ImageProviders/ImgurUploader.ts b/src/Logic/ImageProviders/ImgurUploader.ts deleted file mode 100644 index bb4fc6a9f..000000000 --- a/src/Logic/ImageProviders/ImgurUploader.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { UIEventSource } from "../UIEventSource" -import { Imgur } from "./Imgur" - -export default class ImgurUploader { - public readonly queue: UIEventSource = new UIEventSource([]) - public readonly failed: UIEventSource = new UIEventSource([]) - public readonly success: UIEventSource = new UIEventSource([]) - public maxFileSizeInMegabytes = 10 - private readonly _handleSuccessUrl: (string) => Promise - - constructor(handleSuccessUrl: (string) => Promise) { - this._handleSuccessUrl = handleSuccessUrl - } - - public uploadMany(title: string, description: string, files: FileList): void { - for (let i = 0; i < files.length; i++) { - this.queue.data.push(files.item(i).name) - } - this.queue.ping() - - const self = this - this.queue.setData([...self.queue.data]) - Imgur.uploadMultiple( - title, - description, - files, - async function (url) { - console.log("File saved at", url) - self.success.data.push(url) - self.success.ping() - await self._handleSuccessUrl(url) - }, - function () { - console.log("All uploads completed") - }, - - function (failReason) { - console.log("Upload failed due to ", failReason) - self.failed.setData([...self.failed.data, failReason]) - } - ) - } -} diff --git a/src/Logic/Osm/Actions/LinkImageAction.ts b/src/Logic/Osm/Actions/LinkImageAction.ts new file mode 100644 index 000000000..7d4ec23c8 --- /dev/null +++ b/src/Logic/Osm/Actions/LinkImageAction.ts @@ -0,0 +1,54 @@ +import ChangeTagAction from "./ChangeTagAction"; +import { Tag } from "../../Tags/Tag"; +import OsmChangeAction from "./OsmChangeAction"; +import { Changes } from "../Changes"; +import { ChangeDescription } from "./ChangeDescription"; +import { Store } from "../../UIEventSource"; + +export default class LinkImageAction extends OsmChangeAction { + private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; + public readonly _url: string; + private readonly _currentTags: Store>; + private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; + + /** + * Adds an image-link to a feature + * @param elementId + * @param proposedKey a key which might be used, typically `image`. If the key is already used with a different URL, `key+":0"` will be used instead (or a higher number if needed) + * @param url + * @param currentTags + * @param meta + * + */ + constructor( + elementId: string, + proposedKey: "image" | "mapillary" | "wiki_commons" | string, + url: string, + currentTags: Store>, + meta: { + theme: string + changeType: "add-image" | "link-image" + } + ) { + super(elementId, true) + this._proposedKey = proposedKey; + this._url = url; + this._currentTags = currentTags; + this._meta = meta; + } + + protected CreateChangeDescriptions(): Promise { + let key = this._proposedKey + let i = 0 + const currentTags = this._currentTags.data + const url = this._url + while (currentTags[key] !== undefined && currentTags[key] !== url) { + key = this._proposedKey + ":" + i + i++ + } + const tagChangeAction = new ChangeTagAction ( this.mainObjectId, new Tag(key, url), currentTags, this._meta) + return tagChangeAction.CreateChangeDescriptions() + } + + +} diff --git a/src/Logic/Osm/Actions/LinkPicture.ts b/src/Logic/Osm/Actions/LinkPicture.ts deleted file mode 100644 index 014a836a0..000000000 --- a/src/Logic/Osm/Actions/LinkPicture.ts +++ /dev/null @@ -1,32 +0,0 @@ -import ChangeTagAction from "./ChangeTagAction" -import { Tag } from "../../Tags/Tag" - -export default class LinkPicture extends ChangeTagAction { - /** - * Adds a link to an image - * @param elementId - * @param proposedKey: a key which might be used, typically `image`. If the key is already used with a different URL, `key+":0"` will be used instead (or a higher number if needed) - * @param url - * @param currentTags - * @param meta - * - */ - constructor( - elementId: string, - proposedKey: "image" | "mapillary" | "wiki_commons" | string, - url: string, - currentTags: Record, - meta: { - theme: string - changeType: "add-image" | "link-image" - } - ) { - let key = proposedKey - let i = 0 - while (currentTags[key] !== undefined && currentTags[key] !== url) { - key = proposedKey + ":" + i - i++ - } - super(elementId, new Tag(key, url), currentTags, meta) - } -} diff --git a/src/Logic/Osm/Actions/OsmChangeAction.ts b/src/Logic/Osm/Actions/OsmChangeAction.ts index 4161dc967..2bf31b02c 100644 --- a/src/Logic/Osm/Actions/OsmChangeAction.ts +++ b/src/Logic/Osm/Actions/OsmChangeAction.ts @@ -19,6 +19,9 @@ export default abstract class OsmChangeAction { constructor(mainObjectId: string, trackStatistics: boolean = true) { this.trackStatistics = trackStatistics this.mainObjectId = mainObjectId + if(mainObjectId === undefined || mainObjectId === null){ + throw "OsmObject received '"+mainObjectId+"' as mainObjectId" + } } public async Perform(changes: Changes) { diff --git a/src/Logic/Osm/ChangesetHandler.ts b/src/Logic/Osm/ChangesetHandler.ts index dcdb8a936..4b2a70b32 100644 --- a/src/Logic/Osm/ChangesetHandler.ts +++ b/src/Logic/Osm/ChangesetHandler.ts @@ -5,6 +5,7 @@ import Locale from "../../UI/i18n/Locale" import Constants from "../../Models/Constants" import { Changes } from "./Changes" import { Utils } from "../../Utils" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; export interface ChangesetTag { key: string @@ -13,7 +14,7 @@ export interface ChangesetTag { } export class ChangesetHandler { - private readonly allElements: { addAlias: (id0: String, id1: string) => void } + private readonly allElements: FeaturePropertiesStore private osmConnection: OsmConnection private readonly changes: Changes private readonly _dryRun: Store @@ -29,11 +30,11 @@ export class ChangesetHandler { constructor( dryRun: Store, osmConnection: OsmConnection, - allElements: { addAlias: (id0: string, id1: string) => void } | undefined, + allElements: FeaturePropertiesStore | { addAlias: (id0: string, id1: string) => void } | undefined, changes: Changes ) { this.osmConnection = osmConnection - this.allElements = allElements + this.allElements = allElements this.changes = changes this._dryRun = dryRun this.userDetails = osmConnection.userDetails diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index e650250a9..07028c35a 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -1,551 +1,553 @@ // @ts-ignore -import { osmAuth } from "osm-auth" -import { Store, Stores, UIEventSource } from "../UIEventSource" -import { OsmPreferences } from "./OsmPreferences" -import { Utils } from "../../Utils" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import * as config from "../../../package.json" -export default class UserDetails { - public loggedIn = false - public name = "Not logged in" - public uid: number - public csCount = 0 - public img?: string - public unreadMessages = 0 - public totalMessages: number = 0 - public home: { lon: number; lat: number } - public backend: string - public account_created: string - public tracesCount: number = 0 - public description: string +import { osmAuth } from "osm-auth"; +import { Store, Stores, UIEventSource } from "../UIEventSource"; +import { OsmPreferences } from "./OsmPreferences"; +import { Utils } from "../../Utils"; +import { LocalStorageSource } from "../Web/LocalStorageSource"; +import * as config from "../../../package.json"; - constructor(backend: string) { - this.backend = backend - } +export default class UserDetails { + public loggedIn = false; + public name = "Not logged in"; + public uid: number; + public csCount = 0; + public img?: string; + public unreadMessages = 0; + public totalMessages: number = 0; + public home: { lon: number; lat: number }; + public backend: string; + public account_created: string; + public tracesCount: number = 0; + public description: string; + + constructor(backend: string) { + this.backend = backend; + } } export interface AuthConfig { - "#"?: string // optional comment - oauth_client_id: string - oauth_secret: string - url: string + "#"?: string; // optional comment + oauth_client_id: string; + oauth_secret: string; + url: string; } export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export class OsmConnection { - public static readonly oauth_configs: Record = - config.config.oauth_credentials - public auth - public userDetails: UIEventSource - public isLoggedIn: Store - public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" - ) - public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" - ) + public static readonly oauth_configs: Record = + config.config.oauth_credentials; + public auth; + public userDetails: UIEventSource; + public isLoggedIn: Store; + public gpxServiceIsOnline: UIEventSource = new UIEventSource( + "unknown" + ); + public apiIsOnline: UIEventSource = new UIEventSource( + "unknown" + ); - public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" - ) - public preferencesHandler: OsmPreferences - public readonly _oauth_config: AuthConfig - private readonly _dryRun: Store - private fakeUser: boolean - private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] - private readonly _iframeMode: Boolean | boolean - private readonly _singlePage: boolean - private isChecking = false + public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( + "not-attempted" + ); + public preferencesHandler: OsmPreferences; + public readonly _oauth_config: AuthConfig; + private readonly _dryRun: Store; + private fakeUser: boolean; + private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; + private readonly _iframeMode: Boolean | boolean; + private readonly _singlePage: boolean; + private isChecking = false; - constructor(options?: { - dryRun?: Store - fakeUser?: false | boolean - oauth_token?: UIEventSource - // Used to keep multiple changesets open and to write to the correct changeset - singlePage?: boolean - osmConfiguration?: "osm" | "osm-test" - attemptLogin?: true | boolean - }) { - options = options ?? {} - this.fakeUser = options.fakeUser ?? false - this._singlePage = options.singlePage ?? true - this._oauth_config = - OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? - OsmConnection.oauth_configs.osm - console.debug("Using backend", this._oauth_config.url) - this._iframeMode = Utils.runningFromConsole ? false : window !== window.top + constructor(options?: { + dryRun?: Store + fakeUser?: false | boolean + oauth_token?: UIEventSource + // Used to keep multiple changesets open and to write to the correct changeset + singlePage?: boolean + osmConfiguration?: "osm" | "osm-test" + attemptLogin?: true | boolean + }) { + options = options ?? {}; + this.fakeUser = options.fakeUser ?? false; + this._singlePage = options.singlePage ?? true; + this._oauth_config = + OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? + OsmConnection.oauth_configs.osm; + console.debug("Using backend", this._oauth_config.url); + this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; - // Check if there are settings available in environment variables, and if so, use those - if ( - import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && - import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined - ) { - console.debug("Using environment variables for oauth config") - this._oauth_config = { - oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, - oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, - url: "https://api.openstreetmap.org", - } - } - - this.userDetails = new UIEventSource( - new UserDetails(this._oauth_config.url), - "userDetails" - ) - if (options.fakeUser) { - const ud = this.userDetails.data - ud.csCount = 5678 - ud.loggedIn = true - ud.unreadMessages = 0 - ud.name = "Fake user" - ud.totalMessages = 42 - } - const self = this - this.UpdateCapabilities() - this.isLoggedIn = this.userDetails.map( - (user) => - user.loggedIn && - (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), - [this.apiIsOnline] - ) - this.isLoggedIn.addCallback((isLoggedIn) => { - if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { - // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do - // This means someone attempted to toggle this; so we attempt to login! - self.AttemptLogin() - } - }) - - this._dryRun = options.dryRun ?? new UIEventSource(false) - - this.updateAuthObject() - - this.preferencesHandler = new OsmPreferences( - this.auth, - this - ) - - if (options.oauth_token?.data !== undefined) { - console.log(options.oauth_token.data) - const self = this - this.auth.bootstrapToken( - options.oauth_token.data, - (x) => { - console.log("Called back: ", x) - self.AttemptLogin() - }, - this.auth - ) - - options.oauth_token.setData(undefined) - } - if (this.auth.authenticated() && options.attemptLogin !== false) { - this.AttemptLogin() // Also updates the user badge - } else { - console.log("Not authenticated") - } + // Check if there are settings available in environment variables, and if so, use those + if ( + import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && + import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined + ) { + console.debug("Using environment variables for oauth config"); + this._oauth_config = { + oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, + oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, + url: "https://api.openstreetmap.org" + }; } - public GetPreference( - key: string, - defaultValue: string = undefined, - options?: { - documentation?: string - prefix?: string - } - ): UIEventSource { - return this.preferencesHandler.GetPreference(key, defaultValue, options) + this.userDetails = new UIEventSource( + new UserDetails(this._oauth_config.url), + "userDetails" + ); + if (options.fakeUser) { + const ud = this.userDetails.data; + ud.csCount = 5678; + ud.loggedIn = true; + ud.unreadMessages = 0; + ud.name = "Fake user"; + ud.totalMessages = 42; } + const self = this; + this.UpdateCapabilities(); + this.isLoggedIn = this.userDetails.map( + (user) => + user.loggedIn && + (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), + [this.apiIsOnline] + ); + this.isLoggedIn.addCallback((isLoggedIn) => { + if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { + // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do + // This means someone attempted to toggle this; so we attempt to login! + self.AttemptLogin(); + } + }); - public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { - return this.preferencesHandler.GetLongPreference(key, prefix) + this._dryRun = options.dryRun ?? new UIEventSource(false); + + this.updateAuthObject(); + + this.preferencesHandler = new OsmPreferences( + this.auth, + this + ); + + if (options.oauth_token?.data !== undefined) { + console.log(options.oauth_token.data); + const self = this; + this.auth.bootstrapToken( + options.oauth_token.data, + (x) => { + console.log("Called back: ", x); + self.AttemptLogin(); + }, + this.auth + ); + + options.oauth_token.setData(undefined); } - - public OnLoggedIn(action: (userDetails: UserDetails) => void) { - this._onLoggedIn.push(action) + if (this.auth.authenticated() && options.attemptLogin !== false) { + this.AttemptLogin(); // Also updates the user badge + } else { + console.log("Not authenticated"); } + } - public LogOut() { - this.auth.logout() - this.userDetails.data.loggedIn = false - this.userDetails.data.csCount = 0 - this.userDetails.data.name = "" - this.userDetails.ping() - console.log("Logged out") - this.loadingStatus.setData("not-attempted") + public GetPreference( + key: string, + defaultValue: string = undefined, + options?: { + documentation?: string + prefix?: string } + ): UIEventSource { + return this.preferencesHandler.GetPreference(key, defaultValue, options); + } - /** - * The backend host, without path or trailing '/' - * - * new OsmConnection().Backend() // => "https://www.openstreetmap.org" - */ - public Backend(): string { - return this._oauth_config.url + public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { + return this.preferencesHandler.GetLongPreference(key, prefix); + } + + public OnLoggedIn(action: (userDetails: UserDetails) => void) { + this._onLoggedIn.push(action); + } + + public LogOut() { + this.auth.logout(); + this.userDetails.data.loggedIn = false; + this.userDetails.data.csCount = 0; + this.userDetails.data.name = ""; + this.userDetails.ping(); + console.log("Logged out"); + this.loadingStatus.setData("not-attempted"); + } + + /** + * The backend host, without path or trailing '/' + * + * new OsmConnection().Backend() // => "https://www.openstreetmap.org" + */ + public Backend(): string { + return this._oauth_config.url; + } + + public AttemptLogin() { + this.UpdateCapabilities(); + this.loadingStatus.setData("loading"); + if (this.fakeUser) { + this.loadingStatus.setData("logged-in"); + console.log("AttemptLogin called, but ignored as fakeUser is set"); + return; } - - public AttemptLogin() { - this.UpdateCapabilities() - this.loadingStatus.setData("loading") - if (this.fakeUser) { - this.loadingStatus.setData("logged-in") - console.log("AttemptLogin called, but ignored as fakeUser is set") - return - } - const self = this - console.log("Trying to log in...") - this.updateAuthObject() - LocalStorageSource.Get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href - ) - this.auth.xhr( - { - method: "GET", - path: "/api/0.6/user/details", - }, - function (err, details) { - if (err != null) { - console.log(err) - self.loadingStatus.setData("error") - if (err.status == 401) { - console.log("Clearing tokens...") - // Not authorized - our token probably got revoked - self.auth.logout() - self.LogOut() - } - return - } - - if (details == null) { - self.loadingStatus.setData("error") - return - } - - self.CheckForMessagesContinuously() - - // details is an XML DOM of user details - let userInfo = details.getElementsByTagName("user")[0] - - let data = self.userDetails.data - data.loggedIn = true - console.log("Login completed, userinfo is ", userInfo) - data.name = userInfo.getAttribute("display_name") - data.account_created = userInfo.getAttribute("account_created") - data.uid = Number(userInfo.getAttribute("id")) - data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 - ) - data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 - ) - - data.img = undefined - const imgEl = userInfo.getElementsByTagName("img") - if (imgEl !== undefined && imgEl[0] !== undefined) { - data.img = imgEl[0].getAttribute("href") - } - - const description = userInfo.getElementsByTagName("description") - if (description !== undefined && description[0] !== undefined) { - data.description = description[0]?.innerHTML - } - const homeEl = userInfo.getElementsByTagName("home") - if (homeEl !== undefined && homeEl[0] !== undefined) { - const lat = parseFloat(homeEl[0].getAttribute("lat")) - const lon = parseFloat(homeEl[0].getAttribute("lon")) - data.home = { lat: lat, lon: lon } - } - - self.loadingStatus.setData("logged-in") - const messages = userInfo - .getElementsByTagName("messages")[0] - .getElementsByTagName("received")[0] - data.unreadMessages = parseInt(messages.getAttribute("unread")) - data.totalMessages = parseInt(messages.getAttribute("count")) - - self.userDetails.ping() - for (const action of self._onLoggedIn) { - action(self.userDetails.data) - } - self._onLoggedIn = [] - } - ) - } - - /** - * Interact with the API. - * - * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' - */ - public async interact( - path: string, - method: "GET" | "POST" | "PUT" | "DELETE", - header?: Record, - content?: string - ): Promise { - return new Promise((ok, error) => { - this.auth.xhr( - { - method, - options: { - header, - }, - content, - path: `/api/0.6/${path}`, - }, - function (err, response) { - if (err !== null) { - error(err) - } else { - ok(response) - } - } - ) - }) - } - - public async post( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "POST", header, content) - } - - public async put( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "PUT", header, content) - } - - public async get(path: string, header?: Record): Promise { - return await this.interact(path, "GET", header) - } - - public closeNote(id: number | string, text?: string): Promise { - let textSuffix = "" - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text) - } - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) - return new Promise((ok) => { - ok() - }) - } - return this.post(`notes/${id}/close${textSuffix}`) - } - - public reopenNote(id: number | string, text?: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) - return new Promise((ok) => { - ok() - }) - } - let textSuffix = "" - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text) - } - return this.post(`notes/${id}/reopen${textSuffix}`) - } - - public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually opening note with text ", text) - return new Promise<{ id: number }>((ok) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ) - }) - } - const content = { lat, lon, text } - const response = await this.post("notes.json", JSON.stringify(content), { - "Content-Type": "application/json", - }) - const parsed = JSON.parse(response) - const id = parsed.properties - console.log("OPENED NOTE", id) - return id - } - - public async uploadGpxTrack( - gpx: string, - options: { - description: string - visibility: "private" | "public" | "trackable" | "identifiable" - filename?: string - /** - * Some words to give some properties; - * - * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. - */ - labels: string[] - } - ): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually uploading GPX ", gpx) - return new Promise<{ id: number }>((ok, error) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ) - }) + const self = this; + console.log("Trying to log in..."); + this.updateAuthObject(); + LocalStorageSource.Get("location_before_login").setData( + Utils.runningFromConsole ? undefined : window.location.href + ); + this.auth.xhr( + { + method: "GET", + path: "/api/0.6/user/details" + }, + function(err, details) { + if (err != null) { + console.log(err); + self.loadingStatus.setData("error"); + if (err.status == 401) { + console.log("Clearing tokens..."); + // Not authorized - our token probably got revoked + self.auth.logout(); + self.LogOut(); + } + return; } - const contents = { - file: gpx, - description: options.description ?? "", - tags: options.labels?.join(",") ?? "", - visibility: options.visibility, + if (details == null) { + self.loadingStatus.setData("error"); + return; } - const extras = { - file: - '; filename="' + - (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - '"\r\nContent-Type: application/gpx+xml', + self.CheckForMessagesContinuously(); + + // details is an XML DOM of user details + let userInfo = details.getElementsByTagName("user")[0]; + + let data = self.userDetails.data; + data.loggedIn = true; + console.log("Login completed, userinfo is ", userInfo); + data.name = userInfo.getAttribute("display_name"); + data.account_created = userInfo.getAttribute("account_created"); + data.uid = Number(userInfo.getAttribute("id")); + data.csCount = Number.parseInt( + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + ); + data.tracesCount = Number.parseInt( + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 + ); + + data.img = undefined; + const imgEl = userInfo.getElementsByTagName("img"); + if (imgEl !== undefined && imgEl[0] !== undefined) { + data.img = imgEl[0].getAttribute("href"); } - const boundary = "987654" - - let body = "" - for (const key in contents) { - body += "--" + boundary + "\r\n" - body += 'Content-Disposition: form-data; name="' + key + '"' - if (extras[key] !== undefined) { - body += extras[key] - } - body += "\r\n\r\n" - body += contents[key] + "\r\n" + const description = userInfo.getElementsByTagName("description"); + if (description !== undefined && description[0] !== undefined) { + data.description = description[0]?.innerHTML; + } + const homeEl = userInfo.getElementsByTagName("home"); + if (homeEl !== undefined && homeEl[0] !== undefined) { + const lat = parseFloat(homeEl[0].getAttribute("lat")); + const lon = parseFloat(homeEl[0].getAttribute("lon")); + data.home = { lat: lat, lon: lon }; } - body += "--" + boundary + "--\r\n" - const response = await this.post("gpx/create", body, { - "Content-Type": "multipart/form-data; boundary=" + boundary, - "Content-Length": body.length, - }) - const parsed = JSON.parse(response) - console.log("Uploaded GPX track", parsed) - return { id: parsed } + self.loadingStatus.setData("logged-in"); + const messages = userInfo + .getElementsByTagName("messages")[0] + .getElementsByTagName("received")[0]; + data.unreadMessages = parseInt(messages.getAttribute("unread")); + data.totalMessages = parseInt(messages.getAttribute("count")); + + self.userDetails.ping(); + for (const action of self._onLoggedIn) { + action(self.userDetails.data); + } + self._onLoggedIn = []; + } + ); + } + + /** + * Interact with the API. + * + * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' + */ + public async interact( + path: string, + method: "GET" | "POST" | "PUT" | "DELETE", + header?: Record, + content?: string + ): Promise { + return new Promise((ok, error) => { + this.auth.xhr( + { + method, + options: { + header + }, + content, + path: `/api/0.6/${path}` + }, + function(err, response) { + if (err !== null) { + error(err); + } else { + ok(response); + } + } + ); + }); + } + + public async post( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "POST", header, content); + } + + public async put( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "PUT", header, content); + } + + public async get(path: string, header?: Record): Promise { + return await this.interact(path, "GET", header); + } + + public closeNote(id: number | string, text?: string): Promise { + let textSuffix = ""; + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text); + } + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text); + return new Promise((ok) => { + ok(); + }); + } + return this.post(`notes/${id}/close${textSuffix}`); + } + + public reopenNote(id: number | string, text?: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text); + return new Promise((ok) => { + ok(); + }); + } + let textSuffix = ""; + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text); + } + return this.post(`notes/${id}/reopen${textSuffix}`); + } + + public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually opening note with text ", text); + return new Promise<{ id: number }>((ok) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ); + }); + } + // 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" + }); + const parsed = JSON.parse(response); + const id = parsed.properties; + console.log("OPENED NOTE", id); + return id; + } + + public async uploadGpxTrack( + gpx: string, + options: { + description: string + visibility: "private" | "public" | "trackable" | "identifiable" + filename?: string + /** + * Some words to give some properties; + * + * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. + */ + labels: string[] + } + ): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually uploading GPX ", gpx); + return new Promise<{ id: number }>((ok, error) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ); + }); } - public addCommentToNote(id: number | string, text: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id) - return new Promise((ok) => { - ok() - }) - } - if ((text ?? "") === "") { - throw "Invalid text!" - } + const contents = { + file: gpx, + description: options.description ?? "", + tags: options.labels?.join(",") ?? "", + visibility: options.visibility + }; - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", + const extras = { + file: + "; filename=\"" + + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + + "\"\r\nContent-Type: application/gpx+xml" + }; - path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, - }, - function (err, _) { - if (err !== null) { - error(err) - } else { - ok() - } - } - ) - }) + const boundary = "987654"; + + let body = ""; + for (const key in contents) { + body += "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + key + "\""; + if (extras[key] !== undefined) { + body += extras[key]; + } + body += "\r\n\r\n"; + body += contents[key] + "\r\n"; + } + body += "--" + boundary + "--\r\n"; + + const response = await this.post("gpx/create", body, { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length + }); + const parsed = JSON.parse(response); + console.log("Uploaded GPX track", parsed); + return { id: parsed }; + } + + public addCommentToNote(id: number | string, text: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id); + return new Promise((ok) => { + ok(); + }); + } + if ((text ?? "") === "") { + throw "Invalid text!"; } - private updateAuthObject() { - let pwaStandAloneMode = false - try { - if (Utils.runningFromConsole) { - pwaStandAloneMode = true - } else if ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: fullscreen)").matches - ) { - pwaStandAloneMode = true - } - } catch (e) { - console.warn( - "Detecting standalone mode failed", - e, - ". Assuming in browser and not worrying furhter" - ) + return new Promise((ok, error) => { + this.auth.xhr( + { + method: "POST", + + path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` + }, + function(err, _) { + if (err !== null) { + error(err); + } else { + ok(); + } } - const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage + ); + }); + } - // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... - // Same for an iframe... + /** + * To be called by land.html + */ + public finishLogin(callback: (previousURL: string) => void) { + this.auth.authenticate(function() { + // Fully authed at this point + console.log("Authentication successful!"); + const previousLocation = LocalStorageSource.Get("location_before_login"); + callback(previousLocation.data); + }); + } - this.auth = new osmAuth({ - client_id: this._oauth_config.oauth_client_id, - url: this._oauth_config.url, - scope: "read_prefs write_prefs write_api write_gpx write_notes", - redirect_uri: Utils.runningFromConsole - ? "https://mapcomplete.org/land.html" - : window.location.protocol + "//" + window.location.host + "/land.html", - singlepage: !standalone, - auto: true, - }) + private updateAuthObject() { + let pwaStandAloneMode = false; + try { + if (Utils.runningFromConsole) { + pwaStandAloneMode = true; + } else if ( + window.matchMedia("(display-mode: standalone)").matches || + window.matchMedia("(display-mode: fullscreen)").matches + ) { + pwaStandAloneMode = true; + } + } catch (e) { + console.warn( + "Detecting standalone mode failed", + e, + ". Assuming in browser and not worrying furhter" + ); } + const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage; - /** - * To be called by land.html - */ - public finishLogin(callback: (previousURL: string) => void) { - this.auth.authenticate(function () { - // Fully authed at this point - console.log("Authentication successful!") - const previousLocation = LocalStorageSource.Get("location_before_login") - callback(previousLocation.data) - }) - } + // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... + // Same for an iframe... - private CheckForMessagesContinuously() { - const self = this - if (this.isChecking) { - return - } - this.isChecking = true - Stores.Chronic(5 * 60 * 1000).addCallback((_) => { - if (self.isLoggedIn.data) { - console.log("Checking for messages") - self.AttemptLogin() - } - }) - } + this.auth = new osmAuth({ + client_id: this._oauth_config.oauth_client_id, + url: this._oauth_config.url, + scope: "read_prefs write_prefs write_api write_gpx write_notes", + redirect_uri: Utils.runningFromConsole + ? "https://mapcomplete.org/land.html" + : window.location.protocol + "//" + window.location.host + "/land.html", + singlepage: !standalone, + auto: true + }); + } - private UpdateCapabilities(): void { - const self = this - this.FetchCapabilities().then(({ api, gpx }) => { - self.apiIsOnline.setData(api) - self.gpxServiceIsOnline.setData(gpx) - }) + private CheckForMessagesContinuously() { + const self = this; + if (this.isChecking) { + return; } + this.isChecking = true; + Stores.Chronic(5 * 60 * 1000).addCallback((_) => { + if (self.isLoggedIn.data) { + console.log("Checking for messages"); + self.AttemptLogin(); + } + }); + } - private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { - if (Utils.runningFromConsole) { - return { api: "online", gpx: "online" } - } - const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") - if (result["content"] === undefined) { - console.log("Something went wrong:", result) - return { api: "unreachable", gpx: "unreachable" } - } - const xmlRaw = result["content"] - const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") - const statusEl = parsed.getElementsByTagName("status")[0] - const api = statusEl.getAttribute("api") - const gpx = statusEl.getAttribute("gpx") - return { api, gpx } + private UpdateCapabilities(): void { + const self = this; + this.FetchCapabilities().then(({ api, gpx }) => { + self.apiIsOnline.setData(api); + self.gpxServiceIsOnline.setData(gpx); + }); + } + + private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { + if (Utils.runningFromConsole) { + return { api: "online", gpx: "online" }; } + const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities"); + if (result["content"] === undefined) { + console.log("Something went wrong:", result); + return { api: "unreachable", gpx: "unreachable" }; + } + const xmlRaw = result["content"]; + const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml"); + const statusEl = parsed.getElementsByTagName("status")[0]; + const api = statusEl.getAttribute("api"); + const gpx = statusEl.getAttribute("gpx"); + return { api, gpx }; + } } diff --git a/src/Logic/State/FeatureSwitchState.ts b/src/Logic/State/FeatureSwitchState.ts index 389c966a8..a776ae6af 100644 --- a/src/Logic/State/FeatureSwitchState.ts +++ b/src/Logic/State/FeatureSwitchState.ts @@ -198,7 +198,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { this.backgroundLayerId = QueryParameters.GetQueryParameter( "background", - layoutToUse?.defaultBackgroundId ?? "osm", + layoutToUse?.defaultBackgroundId, "The id of the background layer to start with" ) } diff --git a/src/Logic/State/GeoLocationState.ts b/src/Logic/State/GeoLocationState.ts index e88b6bfa7..fe395fde0 100644 --- a/src/Logic/State/GeoLocationState.ts +++ b/src/Logic/State/GeoLocationState.ts @@ -1,13 +1,13 @@ -import { UIEventSource } from "../UIEventSource" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import { QueryParameters } from "../Web/QueryParameters" +import { UIEventSource } from "../UIEventSource"; +import { LocalStorageSource } from "../Web/LocalStorageSource"; +import { QueryParameters } from "../Web/QueryParameters"; export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" export interface GeoLocationPointProperties extends GeolocationCoordinates { - id: "gps" - "user:location": "yes" - date: string + id: "gps"; + "user:location": "yes"; + date: string; } /** @@ -23,22 +23,22 @@ export class GeoLocationState { */ public readonly permission: UIEventSource = new UIEventSource( "prompt" - ) + ); /** * Important to determine e.g. if we move automatically on fix or not */ - public readonly requestMoment: UIEventSource = new UIEventSource(undefined) + public readonly requestMoment: UIEventSource = new UIEventSource(undefined); /** * If true: the map will center (and re-center) to this location */ - public readonly allowMoving: UIEventSource = new UIEventSource(true) + public readonly allowMoving: UIEventSource = new UIEventSource(true); /** * The latest GeoLocationCoordinates, as given by the WebAPI */ public readonly currentGPSLocation: UIEventSource = - new UIEventSource(undefined) + new UIEventSource(undefined); /** * A small flag on localstorage. If the user previously granted the geolocation, it will be set. @@ -50,69 +50,49 @@ export class GeoLocationState { */ private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = ( LocalStorageSource.Get("geolocation-permissions") - ) + ); /** * Used to detect a permission retraction */ - private readonly _grantedThisSession: UIEventSource = new UIEventSource(false) + private readonly _grantedThisSession: UIEventSource = new UIEventSource(false); + constructor() { - const self = this + const self = this; this.permission.addCallbackAndRunD(async (state) => { if (state === "granted") { - self._previousLocationGrant.setData("true") - self._grantedThisSession.setData(true) + self._previousLocationGrant.setData("true"); + self._grantedThisSession.setData(true); } if (state === "prompt" && self._grantedThisSession.data) { // This is _really_ weird: we had a grant earlier, but it's 'prompt' now? // This means that the rights have been revoked again! - // self.permission.setData("denied") - self._previousLocationGrant.setData("false") - self.permission.setData("denied") - self.currentGPSLocation.setData(undefined) - console.warn("Detected a downgrade in permissions!") + self._previousLocationGrant.setData("false"); + self.permission.setData("denied"); + self.currentGPSLocation.setData(undefined); + console.warn("Detected a downgrade in permissions!"); } if (state === "denied") { - self._previousLocationGrant.setData("false") + self._previousLocationGrant.setData("false"); } - }) - console.log("Previous location grant:", this._previousLocationGrant.data) + }); + console.log("Previous location grant:", this._previousLocationGrant.data); if (this._previousLocationGrant.data === "true") { // A previous visit successfully granted permission. Chance is high that we are allowed to use it again! // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them - this._previousLocationGrant.setData("false") - console.log("Requesting access to GPS as this was previously granted") + this._previousLocationGrant.setData("false"); + console.log("Requesting access to GPS as this was previously granted"); const latLonGivenViaUrl = - QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") + QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon"); if (!latLonGivenViaUrl) { - this.requestMoment.setData(new Date()) + this.requestMoment.setData(new Date()); } - this.requestPermission() + this.requestPermission(); } } - /** - * Installs the listener for updates - * @private - */ - private async startWatching() { - const self = this - navigator.geolocation.watchPosition( - function (position) { - self.currentGPSLocation.setData(position.coords) - self._previousLocationGrant.setData("true") - }, - function () { - console.warn("Could not get location with navigator.geolocation") - }, - { - enableHighAccuracy: true, - } - ) - } - /** * Requests the user to allow access to their position. * When granted, will be written to the 'geolocationState'. @@ -121,33 +101,57 @@ export class GeoLocationState { public requestPermission() { if (typeof navigator === "undefined") { // Not compatible with this browser - this.permission.setData("denied") - return + this.permission.setData("denied"); + return; } if (this.permission.data !== "prompt" && this.permission.data !== "requested") { // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well // Hence that we continue the flow if it is "requested" - return + return; } - this.permission.setData("requested") + this.permission.setData("requested"); try { navigator?.permissions ?.query({ name: "geolocation" }) .then((status) => { - console.log("Status update: received geolocation permission is ", status.state) - this.permission.setData(status.state) - const self = this - status.onchange = function () { + const self = this; + if(status.state === "granted" || status.state === "denied"){ self.permission.setData(status.state) + return } + status.addEventListener("change", (e) => { + self.permission.setData(status.state); + + }); + // The code above might have reset it to 'prompt', but we _did_ request permission! this.permission.setData("requested") // We _must_ call 'startWatching', as that is the actual trigger for the popup... - self.startWatching() + self.startWatching(); }) - .catch((e) => console.error("Could not get geopermission", e)) + .catch((e) => console.error("Could not get geopermission", e)); } catch (e) { - console.error("Could not get permission:", e) + console.error("Could not get permission:", e); } } + + /** + * Installs the listener for updates + * @private + */ + private async startWatching() { + const self = this; + navigator.geolocation.watchPosition( + function(position) { + self.currentGPSLocation.setData(position.coords); + self._previousLocationGrant.setData("true"); + }, + function() { + console.warn("Could not get location with navigator.geolocation"); + }, + { + enableHighAccuracy: true + } + ); + } } diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index 9af935ddf..867ef45d5 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -1,22 +1,23 @@ -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { OsmConnection } from "../Osm/OsmConnection" -import { MangroveIdentity } from "../Web/MangroveReviews" -import { Store, Stores, UIEventSource } from "../UIEventSource" -import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" -import { FeatureSource } from "../FeatureSource/FeatureSource" -import { Feature } from "geojson" -import { Utils } from "../../Utils" -import translators from "../../assets/translators.json" -import codeContributors from "../../assets/contributors.json" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" -import usersettings from "../../../src/assets/generated/layers/usersettings.json" -import Locale from "../../UI/i18n/Locale" -import LinkToWeblate from "../../UI/Base/LinkToWeblate" -import FeatureSwitchState from "./FeatureSwitchState" -import Constants from "../../Models/Constants" -import { QueryParameters } from "../Web/QueryParameters" -import { ThemeMetaTagging } from "./UserSettingsMetaTagging" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import { OsmConnection } from "../Osm/OsmConnection"; +import { MangroveIdentity } from "../Web/MangroveReviews"; +import { Store, Stores, UIEventSource } from "../UIEventSource"; +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; +import { FeatureSource } from "../FeatureSource/FeatureSource"; +import { Feature } from "geojson"; +import { Utils } from "../../Utils"; +import translators from "../../assets/translators.json"; +import codeContributors from "../../assets/contributors.json"; +import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import usersettings from "../../../src/assets/generated/layers/usersettings.json"; +import Locale from "../../UI/i18n/Locale"; +import LinkToWeblate from "../../UI/Base/LinkToWeblate"; +import FeatureSwitchState from "./FeatureSwitchState"; +import Constants from "../../Models/Constants"; +import { QueryParameters } from "../Web/QueryParameters"; +import { ThemeMetaTagging } from "./UserSettingsMetaTagging"; +import { MapProperties } from "../../Models/MapProperties"; /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -41,6 +42,8 @@ export default class UserRelatedState { public readonly fixateNorth: UIEventSource public readonly homeLocation: FeatureSource public readonly language: UIEventSource + public readonly preferredBackgroundLayer: UIEventSource + public readonly imageLicense : UIEventSource /** * The number of seconds that the GPS-locations are stored in memory. * Time in seconds @@ -52,16 +55,23 @@ export default class UserRelatedState { /** * Preferences as tags exposes many preferences and state properties as record. * This is used to bridge the internal state with the usersettings.json layerconfig file + * + * Some metainformation that should not be edited starts with a single underscore + * Constants and query parameters start with two underscores + * Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource */ public readonly preferencesAsTags: UIEventSource> + private readonly _mapProperties: MapProperties; constructor( osmConnection: OsmConnection, availableLanguages?: string[], layout?: LayoutConfig, - featureSwitches?: FeatureSwitchState + featureSwitches?: FeatureSwitchState, + mapProperties?: MapProperties ) { this.osmConnection = osmConnection + this._mapProperties = mapProperties; { const translationMode: UIEventSource = this.osmConnection.GetPreference("translation-mode", "false") @@ -90,11 +100,17 @@ export default class UserRelatedState { ) this.language = this.osmConnection.GetPreference("language") this.showTags = >this.osmConnection.GetPreference("show_tags") - this.fixateNorth = this.osmConnection.GetPreference("fixate-north") + this.fixateNorth = >this.osmConnection.GetPreference("fixate-north") this.mangroveIdentity = new MangroveIdentity( this.osmConnection.GetLongPreference("identity", "mangrove") ) + this.preferredBackgroundLayer= this.osmConnection.GetPreference("preferred-background-layer", undefined, { + documentation: "The ID of a layer or layer category that MapComplete uses by default" + }) + this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { + documentation: "The license under which new images are uploaded" + }) this.installedUserThemes = this.InitInstalledUserThemes() this.homeLocation = this.initHomeLocation() @@ -246,6 +262,7 @@ export default class UserRelatedState { ): UIEventSource> { const amendedPrefs = new UIEventSource>({ _theme: layout?.id, + "_theme:backgroundLayer": layout?.defaultBackgroundId, _backend: this.osmConnection.Backend(), _applicationOpened: new Date().toISOString(), _supports_sharing: @@ -260,6 +277,7 @@ export default class UserRelatedState { amendedPrefs.data["__url_parameter_initialized:" + key] = "yes" } + const osmConnection = this.osmConnection osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { for (const k in newPrefs) { @@ -280,7 +298,6 @@ export default class UserRelatedState { amendedPrefs.ping() console.log("Amended prefs are:", amendedPrefs.data) }) - const usersettingsConfig = UserRelatedState.usersettingsConfig const translationMode = osmConnection.GetPreference("translation-mode") Locale.language.mapD( @@ -335,25 +352,6 @@ export default class UserRelatedState { } usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data }) - /*for (const [name, code, _] of usersettingsConfig.calculatedTags) { - try { - let result = new Function("feat", "return " + code + ";")({ - properties: amendedPrefs.data, - }) - if (result !== undefined && result !== "" && result !== null) { - if (typeof result !== "string") { - result = JSON.stringify(result) - } - amendedPrefs.data[name] = result - } - } catch (e) { - console.error( - "Calculating a tag for userprofile-settings failed for variable", - name, - e - ) - } - }*/ const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "") const isTranslator = translators.contributors.find( @@ -407,6 +405,13 @@ export default class UserRelatedState { } } + + this._mapProperties?.rasterLayer?.addCallbackAndRun(l => { + amendedPrefs.data["__current_background"] = l?.properties?.id + amendedPrefs.ping() + }) + + return amendedPrefs } } diff --git a/src/Logic/State/UserSettingsMetaTagging.ts b/src/Logic/State/UserSettingsMetaTagging.ts index 5cf39177e..33a5ae85b 100644 --- a/src/Logic/State/UserSettingsMetaTagging.ts +++ b/src/Logic/State/UserSettingsMetaTagging.ts @@ -9,5 +9,6 @@ export class ThemeMetaTagging { Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) + feat.properties['__current_backgroun'] = 'initial_value' } } \ No newline at end of file diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 91ce6c5e1..f319f1316 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -98,7 +98,7 @@ export class TagUtils { "Invalid type to flatten the multiAnswer: key is a regex too", tagsFilter ) - throw "Invalid type to FlattenMultiAnswer" + throw "Invalid type to FlattenMultiAnswer: key is a regex too" } const keystr = key if (keyValues[keystr] === undefined) { @@ -109,7 +109,10 @@ export class TagUtils { } console.error("Invalid type to flatten the multiAnswer", tagsFilter) - throw "Invalid type to FlattenMultiAnswer" + throw ( + "Invalid type to FlattenMultiAnswer, not one of RegexTag, Tag or And: " + + tagsFilter.asHumanString(false, false, {}) + ) } return keyValues } diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 39db7dd4e..bb22df11c 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -357,14 +357,18 @@ class ListenerTracker { let toDelete = undefined let startTime = new Date().getTime() / 1000 for (const callback of this._callbacks) { - if (callback(data) === true) { - // This callback wants to be deleted - // Note: it has to return precisely true in order to avoid accidental deletions - if (toDelete === undefined) { - toDelete = [callback] - } else { - toDelete.push(callback) + try { + if (callback(data) === true) { + // This callback wants to be deleted + // Note: it has to return precisely true in order to avoid accidental deletions + if (toDelete === undefined) { + toDelete = [callback] + } else { + toDelete.push(callback) + } } + } catch (e) { + console.error("Got an error while running a callback:", e) } } let endTime = new Date().getTime() / 1000 @@ -511,7 +515,7 @@ class MappedStore extends Store { } private unregisterFromUpstream() { - console.log("Unregistering callbacks for", this.tag) + console.debug("Unregistering callbacks for", this.tag) this._callbacksAreRegistered = false this._unregisterFromUpstream() this._unregisterFromExtraStores?.forEach((unr) => unr()) diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts index 7a5f31e1d..dbd34894b 100644 --- a/src/Models/RasterLayers.ts +++ b/src/Models/RasterLayers.ts @@ -1,43 +1,43 @@ -import { Feature, Polygon } from "geojson" -import * as editorlayerindex from "../assets/editor-layer-index.json" -import * as globallayers from "../assets/global-raster-layers.json" -import { BBox } from "../Logic/BBox" -import { Store, Stores } from "../Logic/UIEventSource" -import { GeoOperations } from "../Logic/GeoOperations" -import { RasterLayerProperties } from "./RasterLayerProperties" +import { Feature, Polygon } from "geojson"; +import * as editorlayerindex from "../assets/editor-layer-index.json"; +import * as globallayers from "../assets/global-raster-layers.json"; +import { BBox } from "../Logic/BBox"; +import { Store, Stores } from "../Logic/UIEventSource"; +import { GeoOperations } from "../Logic/GeoOperations"; +import { RasterLayerProperties } from "./RasterLayerProperties"; export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & - RasterLayerPolygon)[] = editorlayerindex.features + RasterLayerPolygon)[] = editorlayerindex.features; public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( (properties) => { type: "Feature", properties, - geometry: BBox.global.asGeometry(), + geometry: BBox.global.asGeometry() } - ) + ); public static readonly osmCartoProperties: RasterLayerProperties = { id: "osm", name: "OpenStreetMap", url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: { text: "OpenStreetMap", - url: "https://openStreetMap.org/copyright", + url: "https://openStreetMap.org/copyright" }, best: true, max_zoom: 19, min_zoom: 0, - category: "osmbasedmap", - } + category: "osmbasedmap" + }; public static readonly osmCarto: RasterLayerPolygon = { type: "Feature", properties: AvailableRasterLayers.osmCartoProperties, - geometry: BBox.global.asGeometry(), - } + geometry: BBox.global.asGeometry() + }; - public static readonly maplibre: RasterLayerPolygon = { + public static readonly maptilerDefaultLayer: RasterLayerPolygon = { type: "Feature", properties: { name: "MapTiler", @@ -47,12 +47,43 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Maptiler", - url: "https://www.maptiler.com/copyright/", - }, + url: "https://www.maptiler.com/copyright/" + } }, - geometry: BBox.global.asGeometry(), - } + geometry: BBox.global.asGeometry() + }; + public static readonly maptilerCarto: RasterLayerPolygon = { + type: "Feature", + properties: { + name: "MapTiler Carto", + url: "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy", + category: "osmbasedmap", + id: "maptiler.carto", + type: "vector", + attribution: { + text: "Maptiler", + url: "https://www.maptiler.com/copyright/" + } + }, + geometry: BBox.global.asGeometry() + }; + + public static readonly maptilerBackdrop: RasterLayerPolygon = { + type: "Feature", + properties: { + name: "MapTiler Backdrop", + url: "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy", + category: "osmbasedmap", + id: "maptiler.backdrop", + type: "vector", + attribution: { + text: "Maptiler", + url: "https://www.maptiler.com/copyright/" + } + }, + geometry: BBox.global.asGeometry() + }; public static readonly americana: RasterLayerPolygon = { type: "Feature", properties: { @@ -63,41 +94,43 @@ export class AvailableRasterLayers { type: "vector", attribution: { text: "Americana", - url: "https://github.com/ZeLonewolf/openstreetmap-americana/", - }, + url: "https://github.com/ZeLonewolf/openstreetmap-americana/" + } }, - geometry: BBox.global.asGeometry(), - } + geometry: BBox.global.asGeometry() + }; public static layersAvailableAt( location: Store<{ lon: number; lat: number }> ): Store { const availableLayersBboxes = Stores.ListStabilized( location.mapD((loc) => { - const lonlat: [number, number] = [loc.lon, loc.lat] + const lonlat: [number, number] = [loc.lon, loc.lat]; return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => BBox.get(eliPolygon).contains(lonlat) - ) + ); }) - ) + ); const available = Stores.ListStabilized( availableLayersBboxes.map((eliPolygons) => { - const loc = location.data - const lonlat: [number, number] = [loc.lon, loc.lat] + const loc = location.data; + const lonlat: [number, number] = [loc.lon, loc.lat]; const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { if (eliPolygon.geometry === null) { - return true // global ELI-layer + return true; // global ELI-layer } - return GeoOperations.inside(lonlat, eliPolygon) - }) - matching.unshift(AvailableRasterLayers.osmCarto) - matching.unshift(AvailableRasterLayers.americana) - matching.unshift(AvailableRasterLayers.maplibre) - matching.push(...AvailableRasterLayers.globalLayers) - return matching + return GeoOperations.inside(lonlat, eliPolygon); + }); + matching.push(...AvailableRasterLayers.globalLayers); + matching.unshift(AvailableRasterLayers.maptilerDefaultLayer, + AvailableRasterLayers.osmCarto, + AvailableRasterLayers.maptilerCarto, + AvailableRasterLayers.maptilerBackdrop, + AvailableRasterLayers.americana); + return matching; }) - ) - return available + ); + return available; } } @@ -115,22 +148,22 @@ export class RasterLayerUtils { preferredCategory: string, ignoreLayer?: RasterLayerPolygon ): RasterLayerPolygon { - let secondBest: RasterLayerPolygon = undefined + let secondBest: RasterLayerPolygon = undefined; for (const rasterLayer of available) { if (rasterLayer === ignoreLayer) { - continue + continue; } - const p = rasterLayer.properties + const p = rasterLayer.properties; if (p.category === preferredCategory) { if (p.best) { - return rasterLayer + return rasterLayer; } if (!secondBest) { - secondBest = rasterLayer + secondBest = rasterLayer; } } } - return secondBest + return secondBest; } } @@ -146,11 +179,11 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { /** * The name of the imagery source */ - readonly name: string + readonly name: string; /** * Whether the imagery name should be translated */ - readonly i18n?: boolean + readonly i18n?: boolean; readonly type: | "tms" | "wms" @@ -158,7 +191,7 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { | "scanex" | "wms_endpoint" | "wmts" - | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */ + | "vector"; /* Vector is not actually part of the ELI-spec, we add it for vector layers */ /** * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. */ @@ -170,53 +203,53 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { | "historicphoto" | "qa" | "elevation" - | "other" + | "other"; /** * A URL template for imagery tiles */ - readonly url: string - readonly min_zoom?: number - readonly max_zoom?: number + readonly url: string; + readonly min_zoom?: number; + readonly max_zoom?: number; /** * explicit/implicit permission by the owner for use in OSM */ - readonly permission_osm?: "explicit" | "implicit" | "no" + readonly permission_osm?: "explicit" | "implicit" | "no"; /** * A URL for the license or permissions for the imagery */ - readonly license_url?: string + readonly license_url?: string; /** * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. */ - readonly privacy_policy_url?: string | boolean + readonly privacy_policy_url?: string | boolean; /** * A unique identifier for the source; used in imagery_used changeset tag */ - readonly id: string + readonly id: string; /** * A short English-language description of the source */ - readonly description?: string + readonly description?: string; /** * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. */ - readonly country_code?: string + readonly country_code?: string; /** * Whether this imagery should be shown in the default world-wide menu */ - readonly default?: boolean + readonly default?: boolean; /** * Whether this imagery is the best source for the region */ - readonly best?: boolean + readonly best?: boolean; /** * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one */ - readonly start_date?: string + readonly start_date?: string; /** * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one */ - readonly end_date?: string + readonly end_date?: string; /** * HTTP header to check for information if the tile is invalid */ @@ -226,61 +259,61 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { * via the `patternProperty` "^.*$". */ [k: string]: string[] | null - } + }; /** * 'true' if tiles are transparent and can be overlaid on another source */ - readonly overlay?: boolean & string - readonly available_projections?: string[] + readonly overlay?: boolean & string; + readonly available_projections?: string[]; readonly attribution?: { readonly url?: string readonly text?: string readonly html?: string readonly required?: boolean - } + }; /** * A URL for an image, that can be displayed in the list of imagery layers next to the name */ - readonly icon?: string + readonly icon?: string; /** * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. */ - readonly eula?: string + readonly eula?: string; /** * A URL for an image, that is displayed in the mapview for attribution */ - readonly "logo-image"?: string + readonly "logo-image"?: string; /** * Customized text for the terms of use link (default is "Background Terms of Use") */ - readonly "terms-of-use-text"?: string + readonly "terms-of-use-text"?: string; /** * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. */ - readonly "no-tile-checksum"?: string + readonly "no-tile-checksum"?: string; /** * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog */ - readonly "metadata-header"?: string + readonly "metadata-header"?: string; /** * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. */ - readonly "valid-georeference"?: boolean + readonly "valid-georeference"?: boolean; /** * Size of individual tiles delivered by a TMS service */ - readonly "tile-size"?: number + readonly "tile-size"?: number; /** * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. */ - readonly "mod-tile-features"?: string + readonly "mod-tile-features"?: string; /** * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. */ readonly "custom-http-headers"?: { readonly "header-name"?: string readonly "header-value"?: string - } + }; /** * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) */ @@ -291,17 +324,17 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties { [k: string]: unknown } [k: string]: unknown - }[] + }[]; /** * format to use when connecting tile server (when using WMS_ENDPOINT type) */ - readonly format?: string + readonly format?: string; /** * If `true` transparent tiles will be requested from WMS server */ - readonly transparent?: boolean & string + readonly transparent?: boolean & string; /** * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid */ - readonly "minimum-tile-expire"?: number + readonly "minimum-tile-expire"?: number; } diff --git a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 125da4f32..7e33de4bb 100644 --- a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -4,6 +4,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson" import { DesugaringStep, Each, Fuse, On } from "./Conversion" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" +import { del } from "idb-keyval"; export class UpdateLegacyLayer extends DesugaringStep< LayerConfigJson | string | { builtin; override } @@ -41,7 +42,6 @@ export class UpdateLegacyLayer extends DesugaringStep< delete preset["preciseInput"] } else if (preciseInput !== undefined) { delete preciseInput["preferredBackground"] - console.log("Precise input:", preciseInput) preset.snapToLayer = preciseInput.snapToLayer delete preciseInput.snapToLayer if (preciseInput.maxSnapDistance) { @@ -146,7 +146,6 @@ export class UpdateLegacyLayer extends DesugaringStep< } const pr = rendering let iconSize = pr.iconSize - console.log("Iconsize is", iconSize) if (Object.keys(pr.iconSize).length === 1 && pr.iconSize["render"] !== undefined) { iconSize = pr.iconSize["render"] @@ -198,6 +197,10 @@ class UpdateLegacyTheme extends DesugaringStep { delete oldThemeConfig.socialImage } + if(oldThemeConfig.defaultBackgroundId === "osm"){ + console.log("Removing old background in", json.id) + } + if (oldThemeConfig["roamingRenderings"] !== undefined) { if (oldThemeConfig["roamingRenderings"].length == 0) { delete oldThemeConfig["roamingRenderings"] diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index ad7a30b57..33e35f3ee 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -18,6 +18,8 @@ import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRende import Validators from "../../../UI/InputElement/Validators" import TagRenderingConfig from "../TagRenderingConfig" import { parse as parse_html } from "node-html-parser" +import PresetConfig from "../PresetConfig" +import { TagsFilter } from "../../../Logic/Tags/TagsFilter" class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] @@ -167,9 +169,9 @@ class ValidateTheme extends DesugaringStep { json: LayoutConfigJson, context: string ): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } { - const errors = [] - const warnings = [] - const information = [] + const errors: string[] = [] + const warnings: string[] = [] + const information: string[] = [] const theme = new LayoutConfig(json, this._isBuiltin) @@ -245,7 +247,7 @@ class ValidateTheme extends DesugaringStep { information ) } - const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])) + const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"])) if (dups.length > 0) { errors.push( `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` @@ -275,6 +277,10 @@ class ValidateTheme extends DesugaringStep { errors.push(e) } + if (theme.id !== "personal") { + new DetectDuplicatePresets().convertJoin(theme, context, errors, warnings, information) + } + return { result: json, errors, @@ -889,7 +895,7 @@ export class ValidateLayer extends DesugaringStep { { // duplicate ids in tagrenderings check const duplicates = Utils.Dedup( - Utils.Dupiclates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) + Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) ) if (duplicates.length > 0) { console.log(json.tagRenderings) @@ -985,7 +991,7 @@ export class ValidateLayer extends DesugaringStep { ) } - const duplicateIds = Utils.Dupiclates( + const duplicateIds = Utils.Duplicates( (json.tagRenderings ?? []) ?.map((f) => f["id"]) .filter((id) => id !== "questions") @@ -1243,3 +1249,68 @@ export class DetectDuplicateFilters extends DesugaringStep<{ } } } + +export class DetectDuplicatePresets extends DesugaringStep { + constructor() { + super( + "Detects mappings which have identical (english) names or identical mappings.", + ["presets"], + "DetectDuplicatePresets" + ) + } + convert( + json: LayoutConfig, + context: string + ): { + result: LayoutConfig + errors?: string[] + warnings?: string[] + information?: string[] + } { + const presets: PresetConfig[] = [].concat(...json.layers.map((l) => l.presets)) + + const errors = [] + const enNames = presets.map((p) => p.title.textFor("en")) + if (new Set(enNames).size != enNames.length) { + const dups = Utils.Duplicates(enNames) + const layersWithDup = json.layers.filter((l) => + l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0) + ) + const layerIds = layersWithDup.map((l) => l.id) + errors.push( + `At ${context}: this themes has multiple presets which are named:${dups}, namely layers ${layerIds.join( + ", " + )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets` + ) + } + + const optimizedTags = presets.map((p) => new And(p.tags).optimize()) + for (let i = 0; i < presets.length; i++) { + const presetATags = optimizedTags[i] + const presetA = presets[i] + for (let j = i + 1; j < presets.length; j++) { + const presetBTags = optimizedTags[j] + const presetB = presets[j] + if ( + Utils.SameObject(presetATags, presetBTags) && + Utils.sameList( + presetA.preciseInput.snapToLayers, + presetB.preciseInput.snapToLayers + ) + ) { + errors.push( + `At ${context}: this themes has multiple presets with the same tags: ${presetATags.asHumanString( + false, + false, + {} + )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ + j + ].title.textFor("en")}'` + ) + } + } + } + + return { errors, result: json } + } +} diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 06d87f7a9..13e0b84ef 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -359,7 +359,7 @@ export default class LayerConfig extends WithContextLoader { } { - const duplicateIds = Utils.Dupiclates(this.filters.map((f) => f.id)) + const duplicateIds = Utils.Duplicates(this.filters.map((f) => f.id)) if (duplicateIds.length > 0) { throw `Some filters have a duplicate id: ${duplicateIds} (at ${context}.filters)` } diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 77f7a5dd4..b6f13012b 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -1,59 +1,58 @@ -import LayoutConfig from "./ThemeConfig/LayoutConfig" -import { SpecialVisualizationState } from "../UI/SpecialVisualization" -import { Changes } from "../Logic/Osm/Changes" -import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource" +import LayoutConfig from "./ThemeConfig/LayoutConfig"; +import { SpecialVisualizationState } from "../UI/SpecialVisualization"; +import { Changes } from "../Logic/Osm/Changes"; +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { ExportableMap, MapProperties } from "./MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Point, Polygon } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { Map as MlMap } from "maplibre-gl"; +import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"; +import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"; +import { GeoLocationState } from "../Logic/State/GeoLocationState"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { QueryParameters } from "../Logic/Web/QueryParameters"; +import UserRelatedState from "../Logic/State/UserRelatedState"; +import LayerConfig from "./ThemeConfig/LayerConfig"; +import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"; +import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"; +import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"; +import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; +import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"; +import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"; +import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"; +import ShowDataLayer from "../UI/Map/ShowDataLayer"; +import TitleHandler from "../Logic/Actors/TitleHandler"; +import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"; +import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"; +import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; +import { BBox } from "../Logic/BBox"; +import Constants from "./Constants"; +import Hotkeys from "../UI/Base/Hotkeys"; +import Translations from "../UI/i18n/Translations"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"; +import { MenuState } from "./MenuState"; +import MetaTagging from "../Logic/MetaTagging"; +import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"; import { - FeatureSource, - IndexedFeatureSource, - WritableFeatureSource, -} from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { ExportableMap, MapProperties } from "./MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Point, Polygon } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { Map as MlMap } from "maplibre-gl" -import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" -import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" -import { GeoLocationState } from "../Logic/State/GeoLocationState" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { QueryParameters } from "../Logic/Web/QueryParameters" -import UserRelatedState from "../Logic/State/UserRelatedState" -import LayerConfig from "./ThemeConfig/LayerConfig" -import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers" -import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" -import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" -import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" -import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" -import ShowDataLayer from "../UI/Map/ShowDataLayer" -import TitleHandler from "../Logic/Actors/TitleHandler" -import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" -import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" -import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" -import { BBox } from "../Logic/BBox" -import Constants from "./Constants" -import Hotkeys from "../UI/Base/Hotkeys" -import Translations from "../UI/i18n/Translations" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" -import { MenuState } from "./MenuState" -import MetaTagging from "../Logic/MetaTagging" -import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" -import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" -import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" -import { Utils } from "../Utils" -import { EliCategory } from "./RasterLayerProperties" -import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" -import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" -import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" -import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" -import NoElementsInViewDetector, { - FeatureViewState, -} from "../Logic/Actors/NoElementsInViewDetector" -import FilteredLayer from "./FilteredLayer" + NewGeometryFromChangesFeatureSource +} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"; +import { Utils } from "../Utils"; +import { EliCategory } from "./RasterLayerProperties"; +import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"; +import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"; +import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; +import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"; +import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"; +import FilteredLayer from "./FilteredLayer"; +import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { Imgur } from "../Logic/ImageProviders/Imgur"; /** * @@ -64,69 +63,71 @@ import FilteredLayer from "./FilteredLayer" * It ties up all the needed elements and starts some actors. */ export default class ThemeViewState implements SpecialVisualizationState { - readonly layout: LayoutConfig - readonly map: UIEventSource - readonly changes: Changes - readonly featureSwitches: FeatureSwitchState - readonly featureSwitchIsTesting: Store - readonly featureSwitchUserbadge: Store + readonly layout: LayoutConfig; + readonly map: UIEventSource; + readonly changes: Changes; + readonly featureSwitches: FeatureSwitchState; + readonly featureSwitchIsTesting: Store; + readonly featureSwitchUserbadge: Store; - readonly featureProperties: FeaturePropertiesStore + readonly featureProperties: FeaturePropertiesStore; - readonly osmConnection: OsmConnection - readonly selectedElement: UIEventSource - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> - readonly mapProperties: MapProperties & ExportableMap - readonly osmObjectDownloader: OsmObjectDownloader + readonly osmConnection: OsmConnection; + readonly selectedElement: UIEventSource; + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; + readonly mapProperties: MapProperties & ExportableMap; + readonly osmObjectDownloader: OsmObjectDownloader; - readonly dataIsLoading: Store + readonly dataIsLoading: Store; /** * Indicates if there is _some_ data in view, even if it is not shown due to the filters */ - readonly hasDataInView: Store + readonly hasDataInView: Store; - readonly guistate: MenuState - readonly fullNodeDatabase?: FullNodeDatabaseSource + readonly guistate: MenuState; + readonly fullNodeDatabase?: FullNodeDatabaseSource; - readonly historicalUserLocations: WritableFeatureSource> - readonly indexedFeatures: IndexedFeatureSource & LayoutSource - readonly currentView: FeatureSource> - readonly featuresInView: FeatureSource - readonly newFeatures: WritableFeatureSource - readonly layerState: LayerState - readonly perLayer: ReadonlyMap - readonly perLayerFiltered: ReadonlyMap + readonly historicalUserLocations: WritableFeatureSource>; + readonly indexedFeatures: IndexedFeatureSource & LayoutSource; + readonly currentView: FeatureSource>; + readonly featuresInView: FeatureSource; + readonly newFeatures: WritableFeatureSource; + readonly layerState: LayerState; + readonly perLayer: ReadonlyMap; + readonly perLayerFiltered: ReadonlyMap; - readonly availableLayers: Store - readonly selectedLayer: UIEventSource - readonly userRelatedState: UserRelatedState - readonly geolocation: GeoLocationHandler + readonly availableLayers: Store; + readonly selectedLayer: UIEventSource; + readonly userRelatedState: UserRelatedState; + readonly geolocation: GeoLocationHandler; - readonly lastClickObject: WritableFeatureSource + readonly imageUploadManager: ImageUploadManager + + readonly lastClickObject: WritableFeatureSource; readonly overlayLayerStates: ReadonlyMap< string, { readonly isDisplayed: UIEventSource } - > + >; /** * All 'level'-tags that are available with the current features */ - readonly floors: Store + readonly floors: Store; constructor(layout: LayoutConfig) { - Utils.initDomPurify() - this.layout = layout - this.featureSwitches = new FeatureSwitchState(layout) + Utils.initDomPurify(); + this.layout = layout; + this.featureSwitches = new FeatureSwitchState(layout); this.guistate = new MenuState( this.featureSwitches.featureSwitchWelcomeMessage.data, layout.id - ) - this.map = new UIEventSource(undefined) - const initial = new InitialMapPositioning(layout) - this.mapProperties = new MapLibreAdaptor(this.map, initial) - const geolocationState = new GeoLocationState() + ); + this.map = new UIEventSource(undefined); + const initial = new InitialMapPositioning(layout); + this.mapProperties = new MapLibreAdaptor(this.map, initial); + const geolocationState = new GeoLocationState(); - this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting - this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin + this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting; + this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin; this.osmConnection = new OsmConnection({ dryRun: this.featureSwitches.featureSwitchIsTesting, @@ -136,65 +137,68 @@ export default class ThemeViewState implements SpecialVisualizationState { undefined, "Used to complete the login" ), - osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, - }) + osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data + }); this.userRelatedState = new UserRelatedState( this.osmConnection, layout?.language, layout, - this.featureSwitches - ) + this.featureSwitches, + this.mapProperties + ); this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { - this.mapProperties.allowRotating.setData(fixated !== "yes") - }) - this.selectedElement = new UIEventSource(undefined, "Selected element") - this.selectedLayer = new UIEventSource(undefined, "Selected layer") + this.mapProperties.allowRotating.setData(fixated !== "yes"); + }); + this.selectedElement = new UIEventSource(undefined, "Selected element"); + this.selectedLayer = new UIEventSource(undefined, "Selected layer"); this.selectedElementAndLayer = this.selectedElement.mapD( (feature) => { - const layer = this.selectedLayer.data + const layer = this.selectedLayer.data; if (!layer) { - return undefined + return undefined; } - return { layer, feature } + return { layer, feature }; }, [this.selectedLayer] - ) + ); this.geolocation = new GeoLocationHandler( geolocationState, this.selectedElement, this.mapProperties, this.userRelatedState.gpsLocationHistoryRetentionTime - ) + ); - this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) + this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location); - const self = this - this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) + + const self = this; + this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id); { - const overlayLayerStates = new Map }>() + const overlayLayerStates = new Map }>(); for (const rasterInfo of this.layout.tileLayerSources) { const isDisplayed = QueryParameters.GetBooleanQueryParameter( "overlay-" + rasterInfo.id, rasterInfo.defaultState ?? true, "Wether or not overlayer layer " + rasterInfo.id + " is shown" - ) - const state = { isDisplayed } - overlayLayerStates.set(rasterInfo.id, state) - new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) + ); + const state = { isDisplayed }; + overlayLayerStates.set(rasterInfo.id, state); + new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state); } - this.overlayLayerStates = overlayLayerStates + this.overlayLayerStates = overlayLayerStates; } + { /* Setup the layout source * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too */ if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) { - this.fullNodeDatabase = new FullNodeDatabaseSource() + this.fullNodeDatabase = new FullNodeDatabaseSource(); } const layoutSource = new LayoutSource( @@ -204,49 +208,49 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection.Backend(), (id) => self.layerState.filteredLayers.get(id).isDisplayed, this.fullNodeDatabase - ) + ); - this.indexedFeatures = layoutSource + this.indexedFeatures = layoutSource; - const empty = [] - let currentViewIndex = 0 + const empty = []; + let currentViewIndex = 0; this.currentView = new StaticFeatureSource( this.mapProperties.bounds.map((bbox) => { if (!bbox) { - return empty + return empty; } - currentViewIndex++ + currentViewIndex++; return [ bbox.asGeoJson({ zoom: this.mapProperties.zoom.data, ...this.mapProperties.location.data, - id: "current_view", - }), - ] + id: "current_view" + }) + ]; }) - ) - this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) - this.dataIsLoading = layoutSource.isLoading + ); + this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds); + this.dataIsLoading = layoutSource.isLoading; - const indexedElements = this.indexedFeatures - this.featureProperties = new FeaturePropertiesStore(indexedElements) + const indexedElements = this.indexedFeatures; + this.featureProperties = new FeaturePropertiesStore(indexedElements); this.changes = new Changes( { dryRun: this.featureSwitches.featureSwitchIsTesting, allElements: indexedElements, featurePropertiesStore: this.featureProperties, osmConnection: this.osmConnection, - historicalUserLocations: this.geolocation.historicalUserLocations, + historicalUserLocations: this.geolocation.historicalUserLocations }, layout?.isLeftRightSensitive() ?? false - ) - this.historicalUserLocations = this.geolocation.historicalUserLocations + ); + this.historicalUserLocations = this.geolocation.historicalUserLocations; this.newFeatures = new NewGeometryFromChangesFeatureSource( this.changes, indexedElements, this.featureProperties - ) - layoutSource.addSource(this.newFeatures) + ); + layoutSource.addSource(this.newFeatures); const perLayer = new PerLayerFeatureSourceSplitter( Array.from(this.layerState.filteredLayers.values()).filter( @@ -262,11 +266,11 @@ export default class ThemeViewState implements SpecialVisualizationState { features.length, "leftover features, such as", features[0].properties - ) - }, + ); + } } - ) - this.perLayer = perLayer.perLayer + ); + this.perLayer = perLayer.perLayer; } this.perLayer.forEach((fs) => { new SaveFeatureSourceToLocalStorage( @@ -276,73 +280,74 @@ export default class ThemeViewState implements SpecialVisualizationState { fs, this.featureProperties, fs.layer.layerDef.maxAgeOfCache - ) - }) + ); + }); this.floors = this.featuresInView.features.stabilized(500).map((features) => { if (!features) { - return [] + return []; } - const floors = new Set() + const floors = new Set(); for (const feature of features) { - const level = feature.properties["level"] + const level = feature.properties["level"]; if (level) { - const levels = level.split(";") + const levels = level.split(";"); for (const l of levels) { - floors.add(l) + floors.add(l); } } else { - floors.add("0") // '0' is the default and is thus _always_ present + floors.add("0"); // '0' is the default and is thus _always_ present } } - const sorted = Array.from(floors) + const sorted = Array.from(floors); // Sort alphabetically first, to deal with floor "A", "B" and "C" - sorted.sort() + sorted.sort(); sorted.sort((a, b) => { // We use the laxer 'parseInt' to deal with floor '1A' - const na = parseInt(a) - const nb = parseInt(b) + const na = parseInt(a); + const nb = parseInt(b); if (isNaN(na) || isNaN(nb)) { - return 0 + return 0; } - return na - nb - }) - sorted.reverse(/* new list, no side-effects */) - return sorted - }) + return na - nb; + }); + sorted.reverse(/* new list, no side-effects */); + return sorted; + }); const lastClick = (this.lastClickObject = new LastClickFeatureSource( this.mapProperties.lastClickLocation, this.layout - )) + )); this.osmObjectDownloader = new OsmObjectDownloader( this.osmConnection.Backend(), this.changes - ) + ); - this.perLayerFiltered = this.showNormalDataOn(this.map) + this.perLayerFiltered = this.showNormalDataOn(this.map); - this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView + this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView; + this.imageUploadManager = new ImageUploadManager(layout, Imgur.singleton, this.featureProperties, this.osmConnection, this.changes) - this.initActors() - this.addLastClick(lastClick) - this.drawSpecialLayers() - this.initHotkeys() - this.miscSetup() + this.initActors(); + this.addLastClick(lastClick); + this.drawSpecialLayers(); + this.initHotkeys(); + this.miscSetup(); if (!Utils.runningFromConsole) { - console.log("State setup completed", this) + console.log("State setup completed", this); } } public showNormalDataOn(map: Store): ReadonlyMap { - const filteringFeatureSource = new Map() + const filteringFeatureSource = new Map(); this.perLayer.forEach((fs, layerName) => { const doShowLayer = this.mapProperties.zoom.map( (z) => (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), [fs.layer.isDisplayed] - ) + ); if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) @@ -352,15 +357,15 @@ export default class ThemeViewState implements SpecialVisualizationState { * Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer! * */ - return + return; } const filtered = new FilteringFeatureSource( fs.layer, fs, (id) => this.featureProperties.getStore(id), this.layerState.globalFilters - ) - filteringFeatureSource.set(layerName, filtered) + ); + filteringFeatureSource.set(layerName, filtered); new ShowDataLayer(map, { layer: fs.layer.layerDef, @@ -368,30 +373,30 @@ export default class ThemeViewState implements SpecialVisualizationState { doShowLayer, selectedElement: this.selectedElement, selectedLayer: this.selectedLayer, - fetchStore: (id) => this.featureProperties.getStore(id), - }) - }) - return filteringFeatureSource + fetchStore: (id) => this.featureProperties.getStore(id) + }); + }); + return filteringFeatureSource; } /** * Various small methods that need to be called */ private miscSetup() { - this.userRelatedState.markLayoutAsVisited(this.layout) + this.userRelatedState.markLayoutAsVisited(this.layout); this.selectedElement.addCallbackAndRunD((feature) => { // As soon as we have a selected element, we clear the selected element // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear if (feature.properties.id === "last_click") { - return + return; } - this.lastClickObject.features.setData([]) - }) + this.lastClickObject.features.setData([]); + }); if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { - Utils.LoadCustomCss(this.layout.customCss) + Utils.LoadCustomCss(this.layout.customCss); } } @@ -400,74 +405,74 @@ export default class ThemeViewState implements SpecialVisualizationState { { nomod: "Escape", onUp: true }, Translations.t.hotkeyDocumentation.closeSidebar, () => { - this.selectedElement.setData(undefined) - this.guistate.closeAll() + this.selectedElement.setData(undefined); + this.guistate.closeAll(); } - ) + ); Hotkeys.RegisterHotkey( { - nomod: "b", + nomod: "b" }, Translations.t.hotkeyDocumentation.openLayersPanel, () => { if (this.featureSwitches.featureSwitchFilter.data) { - this.guistate.openFilterView() + this.guistate.openFilterView(); } } - ) + ); Hotkeys.RegisterHotkey( { shift: "O" }, Translations.t.hotkeyDocumentation.selectMapnik, () => { - this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) + this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto); } - ) + ); const setLayerCategory = (category: EliCategory) => { - const available = this.availableLayers.data - const current = this.mapProperties.rasterLayer + const available = this.availableLayers.data; + const current = this.mapProperties.rasterLayer; const best = RasterLayerUtils.SelectBestLayerAccordingTo( available, category, current.data - ) - console.log("Best layer for category", category, "is", best.properties.id) - current.setData(best) - } + ); + console.log("Best layer for category", category, "is", best.properties.id); + current.setData(best); + }; Hotkeys.RegisterHotkey( { nomod: "O" }, Translations.t.hotkeyDocumentation.selectOsmbasedmap, () => setLayerCategory("osmbasedmap") - ) + ); Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => setLayerCategory("map") - ) + ); Hotkeys.RegisterHotkey( { nomod: "P" }, Translations.t.hotkeyDocumentation.selectAerial, () => setLayerCategory("photo") - ) + ); } private addLastClick(last_click: LastClickFeatureSource) { // The last_click gets a _very_ special treatment as it interacts with various parts - const last_click_layer = this.layerState.filteredLayers.get("last_click") - this.featureProperties.trackFeatureSource(last_click) - this.indexedFeatures.addSource(last_click) + const last_click_layer = this.layerState.filteredLayers.get("last_click"); + this.featureProperties.trackFeatureSource(last_click); + this.indexedFeatures.addSource(last_click); last_click.features.addCallbackAndRunD((features) => { if (this.selectedLayer.data?.id === "last_click") { // The last-click location moved, but we have selected the last click of the previous location // So, we update _after_ clearing the selection to make sure no stray data is sticking around - this.selectedElement.setData(undefined) - this.selectedElement.setData(features[0]) + this.selectedElement.setData(undefined); + this.selectedElement.setData(features[0]); } - }) + }); new ShowDataLayer(this.map, { features: new FilteringFeatureSource(last_click_layer, last_click), @@ -479,18 +484,18 @@ export default class ThemeViewState implements SpecialVisualizationState { if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { this.map.data.flyTo({ zoom: Constants.minZoomLevelToAddNewPoint, - center: this.mapProperties.lastClickLocation.data, - }) - return + center: this.mapProperties.lastClickLocation.data + }); + return; } // We first clear the selection to make sure no weird state is around - this.selectedLayer.setData(undefined) - this.selectedElement.setData(undefined) + this.selectedLayer.setData(undefined); + this.selectedElement.setData(undefined); - this.selectedElement.setData(feature) - this.selectedLayer.setData(last_click_layer.layerDef) - }, - }) + this.selectedElement.setData(feature); + this.selectedLayer.setData(last_click_layer.layerDef); + } + }); } /** @@ -498,7 +503,7 @@ export default class ThemeViewState implements SpecialVisualizationState { */ private drawSpecialLayers() { type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] - const empty = [] + const empty = []; /** * A listing which maps the layerId onto the featureSource */ @@ -518,21 +523,21 @@ export default class ThemeViewState implements SpecialVisualizationState { bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })] ) ), - current_view: this.currentView, - } + current_view: this.currentView + }; if (this.layout?.lockLocation) { - const bbox = new BBox(this.layout.lockLocation) - this.mapProperties.maxbounds.setData(bbox) + const bbox = new BBox(this.layout.lockLocation); + this.mapProperties.maxbounds.setData(bbox); ShowDataLayer.showRange( this.map, new StaticFeatureSource([bbox.asGeoJson({})]), this.featureSwitches.featureSwitchIsTesting - ) + ); } - const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") + const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view"); if (currentViewLayer?.tagRenderings?.length > 0) { - const params = MetaTagging.createExtraFuncParams(this) - this.featureProperties.trackFeatureSource(specialLayers.current_view) + const params = MetaTagging.createExtraFuncParams(this); + this.featureProperties.trackFeatureSource(specialLayers.current_view); specialLayers.current_view.features.addCallbackAndRunD((features) => { MetaTagging.addMetatags( features, @@ -541,37 +546,37 @@ export default class ThemeViewState implements SpecialVisualizationState { this.layout, this.osmObjectDownloader, this.featureProperties - ) - }) + ); + }); } - const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") + const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range"); - const rangeIsDisplayed = rangeFLayer?.isDisplayed + const rangeIsDisplayed = rangeFLayer?.isDisplayed; if ( !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) ) { - rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) + rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true); } this.layerState.filteredLayers.forEach((flayer) => { - const id = flayer.layerDef.id - const features: FeatureSource = specialLayers[id] + const id = flayer.layerDef.id; + const features: FeatureSource = specialLayers[id]; if (features === undefined) { - return + return; } - this.featureProperties.trackFeatureSource(features) + this.featureProperties.trackFeatureSource(features); // this.indexedFeatures.addSource(features) new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, layer: flayer.layerDef, selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - }) - }) + selectedLayer: this.selectedLayer + }); + }); } /** @@ -580,29 +585,30 @@ export default class ThemeViewState implements SpecialVisualizationState { private initActors() { // Unselect the selected element if it is panned out of view this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { - const selected = this.selectedElement.data + const selected = this.selectedElement.data; if (selected === undefined) { - return + return; } - const bbox = BBox.get(selected) + const bbox = BBox.get(selected); if (!bbox.overlapsWith(bounds)) { - this.selectedElement.setData(undefined) + this.selectedElement.setData(undefined); } - }) + }); this.selectedElement.addCallback((selected) => { if (selected === undefined) { // We did _unselect_ an item - we always remove the lastclick-object - this.lastClickObject.features.setData([]) - this.selectedLayer.setData(undefined) + this.lastClickObject.features.setData([]); + this.selectedLayer.setData(undefined); } - }) - new ThemeViewStateHashActor(this) - new MetaTagging(this) - new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) - new ChangeToElementsActor(this.changes, this.featureProperties) - new PendingChangesUploader(this.changes, this.selectedElement) - new SelectedElementTagsUpdater(this) - new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) + }); + new ThemeViewStateHashActor(this); + new MetaTagging(this); + new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this); + new ChangeToElementsActor(this.changes, this.featureProperties); + new PendingChangesUploader(this.changes, this.selectedElement); + new SelectedElementTagsUpdater(this); + new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers); + new PreferredRasterLayerSelector(this.mapProperties.rasterLayer, this.availableLayers, this.featureSwitches.backgroundLayerId, this.userRelatedState.preferredBackgroundLayer) } } diff --git a/src/UI/Base/FileSelector.svelte b/src/UI/Base/FileSelector.svelte new file mode 100644 index 000000000..fa4dc2ad6 --- /dev/null +++ b/src/UI/Base/FileSelector.svelte @@ -0,0 +1,40 @@ + + +
+ + { + drawAttention = false; + dispatcher("submit", inputElement.files)}} + + on:dragend={ () => {drawAttention = false}} + on:dragover|preventDefault|stopPropagation={(e) => { + console.log("Dragging over!") + drawAttention = true + e.dataTransfer.drop = "copy" + }} + on:dragstart={ () => {drawAttention = false}} + on:drop|preventDefault|stopPropagation={(e) => { + console.log("Got a 'drop'") + drawAttention = false + dispatcher("submit", e.dataTransfer.files) + }} + type="file" + > +
diff --git a/src/UI/Base/Loading.svelte b/src/UI/Base/Loading.svelte index 097bc4472..ff8a622d7 100644 --- a/src/UI/Base/Loading.svelte +++ b/src/UI/Base/Loading.svelte @@ -1,9 +1,12 @@ - -
+
diff --git a/src/UI/BigComponents/BackgroundSwitcher.svelte b/src/UI/BigComponents/BackgroundSwitcher.svelte index 3ed280b2f..a92bab52f 100644 --- a/src/UI/BigComponents/BackgroundSwitcher.svelte +++ b/src/UI/BigComponents/BackgroundSwitcher.svelte @@ -37,7 +37,7 @@ function updatedAltLayer() { const available = availableRasterLayers.data const current = rasterLayer.data - const defaultLayer = AvailableRasterLayers.maplibre + const defaultLayer = AvailableRasterLayers.maptilerDefaultLayer const firstOther = available.find((l) => l !== defaultLayer) const secondOther = available.find((l) => l !== defaultLayer && l !== firstOther) raster0.setData(firstOther === current ? defaultLayer : firstOther) diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index 8806d21fe..2e43de9b0 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -1,40 +1,44 @@ @@ -58,12 +62,24 @@
- p === "denied")}> + {#if $currentGPSLocation !== undefined || $geopermission === "prompt"} - + + {:else if $geopermission === "requested"} + + {:else if $geopermission !== "denied"} + + {/if}
diff --git a/src/UI/DownloadFlow/DownloadPdf.svelte b/src/UI/DownloadFlow/DownloadPdf.svelte index 9a807c1ee..379950b8b 100644 --- a/src/UI/DownloadFlow/DownloadPdf.svelte +++ b/src/UI/DownloadFlow/DownloadPdf.svelte @@ -29,7 +29,7 @@ const templateUrls = SvgToPdf.templates[templateName].pages const templates: string[] = await Promise.all(templateUrls.map((url) => Utils.download(url))) console.log("Templates are", templates) - const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maplibre + const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maptilerDefaultLayer const creator = new SvgToPdf(title, templates, { state, freeComponentId: "belowmap", diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts deleted file mode 100644 index 5c3f6b5c6..000000000 --- a/src/UI/Image/ImageUploadFlow.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Translations from "../i18n/Translations" -import Svg from "../../Svg" -import { Tag } from "../../Logic/Tags/Tag" -import BaseUIElement from "../BaseUIElement" -import Toggle from "../Input/Toggle" -import FileSelectorButton from "../Input/FileSelectorButton" -import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader" -import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { FixedUiElement } from "../Base/FixedUiElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import Loading from "../Base/Loading" -import { LoginToggle } from "../Popup/LoginButton" -import Constants from "../../Models/Constants" -import { SpecialVisualizationState } from "../SpecialVisualization" - -export class ImageUploadFlow extends Toggle { - private static readonly uploadCountsPerId = new Map>() - - constructor( - tagsSource: Store, - state: SpecialVisualizationState, - imagePrefix: string = "image", - text: string = undefined - ) { - const perId = ImageUploadFlow.uploadCountsPerId - const id = tagsSource.data.id - if (!perId.has(id)) { - perId.set(id, new UIEventSource(0)) - } - const uploadedCount = perId.get(id) - const uploader = new ImgurUploader(async (url) => { - // A file was uploaded - we add it to the tags of the object - - const tags = tagsSource.data - let key = imagePrefix - if (tags[imagePrefix] !== undefined) { - let freeIndex = 0 - while (tags[imagePrefix + ":" + freeIndex] !== undefined) { - freeIndex++ - } - key = imagePrefix + ":" + freeIndex - } - - await state.changes.applyAction( - new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, { - changeType: "add-image", - theme: state.layout.id, - }) - ) - console.log("Adding image:" + key, url) - uploadedCount.data++ - uploadedCount.ping() - }) - - const t = Translations.t.image - - let labelContent: BaseUIElement - if (text === undefined) { - labelContent = Translations.t.image.addPicture - .Clone() - .SetClass("block align-middle mt-1 ml-3 text-4xl ") - } else { - labelContent = new FixedUiElement(text).SetClass( - "block align-middle mt-1 ml-3 text-2xl " - ) - } - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - labelContent, - ]).SetClass("w-full flex justify-center items-center") - - const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0") - - const fileSelector = new FileSelectorButton(label, { - acceptType: "image/*", - allowMultiple: true, - labelClasses: "rounded-full border-2 border-black font-bold", - }) - /* fileSelector.SetClass( - "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" - ) - .SetStyle(" border-color: var(--foreground-color);")*/ - fileSelector.GetValue().addCallback((filelist) => { - if (filelist === undefined || filelist.length === 0) { - return - } - - for (var i = 0; i < filelist.length; i++) { - const sizeInBytes = filelist[i].size - console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes") - if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) { - alert( - Translations.t.image.toBig.Subs({ - actual_size: Math.floor(sizeInBytes / 1000000) + "MB", - max_size: uploader.maxFileSizeInMegabytes + "MB", - }).txt - ) - return - } - } - - const license = licenseStore?.data ?? "CC0" - - const tags = tagsSource.data - - const layout = state?.layout - let matchingLayer: LayerConfig = undefined - for (const layer of layout?.layers ?? []) { - if (layer.source.osmTags.matchesProperties(tags)) { - matchingLayer = layer - break - } - } - - const title = - matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement() - ?.textContent ?? - tags.name ?? - "https//osm.org/" + tags.id - const description = [ - "author:" + state.osmConnection.userDetails.data.name, - "license:" + license, - "osmid:" + tags.id, - ].join("\n") - - uploader.uploadMany(title, description, filelist) - }) - - const uploadFlow: BaseUIElement = new Combine([ - new VariableUiElement( - uploader.queue - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return new Loading(t.uploadingPicture).SetClass("alert") - } else { - return new Loading( - t.uploadingMultiple.Subs({ count: "" + l }) - ).SetClass("alert") - } - }) - ), - new VariableUiElement( - uploader.failed - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - console.log(l) - return t.uploadFailed.SetClass("block alert") - }) - ), - new VariableUiElement( - uploadedCount.map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return t.uploadDone.Clone().SetClass("thanks block") - } - return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block") - }) - ), - - fileSelector, - new Combine([ - Translations.t.image.respectPrivacy, - new VariableUiElement( - licenseStore.map((license) => - Translations.t.image.currentLicense.Subs({ license }) - ) - ) - .onClick(() => { - console.log("Opening the license settings... ") - state.guistate.openUsersettings("picture-license") - }) - .SetClass("underline"), - ]).SetStyle("font-size:small;"), - ]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") - - super( - new LoginToggle( - /*We can show the actual upload button!*/ - uploadFlow, - /* User not logged in*/ t.pleaseLogin.Clone(), - state - ), - undefined /* Nothing as the user badge is disabled*/, - state?.featureSwitchUserbadge - ) - } -} diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte new file mode 100644 index 000000000..23408e778 --- /dev/null +++ b/src/UI/Image/UploadImage.svelte @@ -0,0 +1,77 @@ + + + + + + +
+ + + handleFiles(e.detail)}> +
+ + {#if image !== undefined} + + {:else} + + {/if} + {#if labelText} + {labelText} + {:else} + + {/if} +
+
+ +
+ +
diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte new file mode 100644 index 000000000..0c1b6f777 --- /dev/null +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -0,0 +1,67 @@ + + +{#if $uploadStarted == 1} + {#if $uploadFinished == 1 } + + {:else if $failed == 1} +
+ + + +
+ {:else if $retried == 1} + + + + {:else } + + + + {/if} +{:else if $uploadStarted > 1} + {#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0} + + {:else if $uploadFinished == 0} + + + + {:else if $uploadFinished > 0} + + + + {/if} + {#if $failed > 0} +
+ {#if failed === 1} + + {:else} + + + {/if} + + +
+ {/if} +{/if} diff --git a/src/UI/Input/FileSelectorButton.ts b/src/UI/Input/FileSelectorButton.ts deleted file mode 100644 index c3f56d297..000000000 --- a/src/UI/Input/FileSelectorButton.ts +++ /dev/null @@ -1,111 +0,0 @@ -import BaseUIElement from "../BaseUIElement" -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class FileSelectorButton extends InputElement { - private static _nextid = 0 - private readonly _value = new UIEventSource(undefined) - private readonly _label: BaseUIElement - private readonly _acceptType: string - private readonly allowMultiple: boolean - private readonly _labelClasses: string - - constructor( - label: BaseUIElement, - options?: { - acceptType: "image/*" | string - allowMultiple: true | boolean - labelClasses?: string - } - ) { - super() - this._label = label - this._acceptType = options?.acceptType ?? "image/*" - this._labelClasses = options?.labelClasses ?? "" - this.SetClass("block cursor-pointer") - label.SetClass("cursor-pointer") - this.allowMultiple = options?.allowMultiple ?? true - } - - GetValue(): UIEventSource { - return this._value - } - - IsValid(t: FileList): boolean { - return true - } - - protected InnerConstructElement(): HTMLElement { - const self = this - const el = document.createElement("form") - const label = document.createElement("label") - label.appendChild(this._label.ConstructElement()) - label.classList.add(...this._labelClasses.split(" ").filter((t) => t !== "")) - el.appendChild(label) - - const actualInputElement = document.createElement("input") - actualInputElement.style.cssText = "display:none" - actualInputElement.type = "file" - actualInputElement.accept = this._acceptType - actualInputElement.name = "picField" - actualInputElement.multiple = this.allowMultiple - actualInputElement.id = "fileselector" + FileSelectorButton._nextid - FileSelectorButton._nextid++ - - label.htmlFor = actualInputElement.id - - actualInputElement.onchange = () => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - } - - el.addEventListener("submit", (e) => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - actualInputElement.classList.remove("glowing-shadow") - - e.preventDefault() - }) - - el.appendChild(actualInputElement) - - function setDrawAttention(isOn: boolean) { - if (isOn) { - label.classList.add("glowing-shadow") - } else { - label.classList.remove("glowing-shadow") - } - } - - el.addEventListener("dragover", (event) => { - event.stopPropagation() - event.preventDefault() - setDrawAttention(true) - // Style the drag-and-drop as a "copy file" operation. - event.dataTransfer.dropEffect = "copy" - }) - - window.document.addEventListener("dragenter", () => { - setDrawAttention(true) - }) - - window.document.addEventListener("dragend", () => { - setDrawAttention(false) - }) - - el.addEventListener("drop", (event) => { - event.stopPropagation() - event.preventDefault() - label.classList.remove("glowing-shadow") - const fileList = event.dataTransfer.files - this._value.setData(fileList) - }) - - return el - } -} diff --git a/src/UI/Input/Slider.ts b/src/UI/Input/Slider.ts deleted file mode 100644 index 9fce626a7..000000000 --- a/src/UI/Input/Slider.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class Slider extends InputElement { - private readonly _value: UIEventSource - private readonly min: number - private readonly max: number - private readonly step: number - private readonly vertical: boolean - - /** - * Constructs a slider input element for natural numbers - * @param min: the minimum value that is allowed, inclusive - * @param max: the max value that is allowed, inclusive - * @param options: value: injectable value; step: the step size of the slider - */ - constructor( - min: number, - max: number, - options?: { - value?: UIEventSource - step?: 1 | number - vertical?: false | boolean - } - ) { - super() - this.max = max - this.min = min - this._value = options?.value ?? new UIEventSource(min) - this.step = options?.step ?? 1 - this.vertical = options?.vertical ?? false - } - - GetValue(): UIEventSource { - return this._value - } - - protected InnerConstructElement(): HTMLElement { - const el = document.createElement("input") - el.type = "range" - el.min = "" + this.min - el.max = "" + this.max - el.step = "" + this.step - const valuestore = this._value - el.oninput = () => { - valuestore.setData(Number(el.value)) - } - if (this.vertical) { - el.classList.add("vertical") - el.setAttribute("orient", "vertical") // firefox only workaround... - } - valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data)) - return el - } - - IsValid(t: number): boolean { - return Math.round(t) == t && t >= this.min && t <= this.max - } -} diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 4af8ac980..3843108ce 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -92,7 +92,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { - self.setBackground() + map.resize() self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) @@ -102,8 +102,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMinzoom(self.minzoom.data) self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) + self.setBackground() this.updateStores(true) }) + map.resize() self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) @@ -113,6 +115,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMinzoom(self.minzoom.data) self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) + self.setBackground() this.updateStores(true) map.on("moveend", () => this.updateStores()) map.on("click", (e) => { @@ -126,7 +129,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { }) }) - this.rasterLayer.addCallback((_) => + this.rasterLayer.addCallbackAndRun((_) => self.setBackground().catch((_) => { console.error("Could not set background") }) @@ -402,7 +405,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { this.removeCurrentLayer(map) } else { // Make sure that the default maptiler style is loaded as it gives an overlay with roads - const maptiler = AvailableRasterLayers.maplibre.properties + const maptiler = AvailableRasterLayers.maptilerDefaultLayer.properties if (!map.getSource(maptiler.id)) { this.removeCurrentLayer(map) map.addSource(maptiler.id, MapLibreAdaptor.prepareWmsSource(maptiler)) @@ -417,7 +420,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { if (!map.getSource(background.id)) { map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) } - map.resize() if (!map.getLayer(background.id)) { map.addLayer( { @@ -430,7 +432,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { ) } await this.awaitStyleIsLoaded() - this.removeCurrentLayer(map) + if(this._currentRasterLayer !== background?.id){ + this.removeCurrentLayer(map) + } this._currentRasterLayer = background?.id } @@ -455,8 +459,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { map.rotateTo(0, { duration: 0 }) map.setPitch(0) map.dragRotate.disable() + map.touchZoomRotate.disableRotation(); } else { map.dragRotate.enable() + map.touchZoomRotate.enableRotation(); } } diff --git a/src/UI/Map/MaplibreMap.svelte b/src/UI/Map/MaplibreMap.svelte index bb03350d2..f882b6ead 100644 --- a/src/UI/Map/MaplibreMap.svelte +++ b/src/UI/Map/MaplibreMap.svelte @@ -24,7 +24,7 @@ writable({ lng: 0, lat: 0 }) export let zoom: Readable = writable(1) - const styleUrl = AvailableRasterLayers.maplibre.properties.url + const styleUrl = AvailableRasterLayers.maptilerDefaultLayer.properties.url let _map: Map onMount(() => { diff --git a/src/UI/Popup/CreateNewNote.svelte b/src/UI/Popup/CreateNewNote.svelte index 4bdb92b23..10eda741a 100644 --- a/src/UI/Popup/CreateNewNote.svelte +++ b/src/UI/Popup/CreateNewNote.svelte @@ -62,8 +62,13 @@ state.newFeatures.features.data.push(feature) state.newFeatures.features.ping() state.selectedElement?.setData(feature) + if(state.featureProperties.trackFeature){ + state.featureProperties.trackFeature(feature) + } comment.setData("") created = true + state.selectedElement.setData(feature) + state.selectedLayer.setData(state.layerState.filteredLayers.get("note")) } diff --git a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte index 8eada0a7f..2cbfb0850 100644 --- a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte +++ b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte @@ -38,7 +38,6 @@ const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined let currentState: "start" | "confirm" | "applying" | "deleted" = "start" $: { - console.log("Current state is", currentState, $canBeDeleted, canBeDeletedReason) deleteAbility.CheckDeleteability(true) } @@ -55,7 +54,6 @@ let actionToTake: OsmChangeAction const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {})) const deleteReason = changedProperties[DeleteConfig.deleteReasonKey] - console.log("Deleting! Hard?:", canBeDeleted.data, deleteReason) if (deleteReason) { // This is a proper, hard deletion actionToTake = new DeleteAction( diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte index ef04b9868..2aa280f98 100644 --- a/src/UI/Popup/LinkableImage.svelte +++ b/src/UI/Popup/LinkableImage.svelte @@ -6,7 +6,7 @@ import ToSvelte from "../Base/ToSvelte.svelte" import { AttributedImage } from "../Image/AttributedImage" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" - import LinkPicture from "../../Logic/Osm/Actions/LinkPicture" + import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" import { GeoOperations } from "../../Logic/GeoOperations" @@ -40,7 +40,7 @@ const key = Object.keys(image.osmTags)[0] const url = image.osmTags[key] if (isLinked) { - const action = new LinkPicture(currentTags.id, key, url, currentTags, { + const action = new LinkImageAction(currentTags.id, key, url, currentTags, { theme: state.layout.id, changeType: "link-image", }) diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 237568e0e..d50d0efcd 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -54,6 +54,7 @@ if ( confg.mappings?.length > 0 && + confg.multiAnswer && (checkedMappings === undefined || checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0)) ) { diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 4cb3aeb02..a4e00100b 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -1,113 +1,118 @@ -import { Store, UIEventSource } from "../Logic/UIEventSource" -import BaseUIElement from "./BaseUIElement" -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { Changes } from "../Logic/Osm/Changes" -import { ExportableMap, MapProperties } from "../Models/MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Geometry, Point } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { MangroveIdentity } from "../Logic/Web/MangroveReviews" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { MenuState } from "../Models/MenuState" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" -import { RasterLayerPolygon } from "../Models/RasterLayers" +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import BaseUIElement from "./BaseUIElement"; +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { Changes } from "../Logic/Osm/Changes"; +import { ExportableMap, MapProperties } from "../Models/MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Geometry, Point } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { MenuState } from "../Models/MenuState"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import { RasterLayerPolygon } from "../Models/RasterLayers"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { OsmTags } from "../Models/OsmFeature"; /** * The state needed to render a special Visualisation. */ export interface SpecialVisualizationState { - readonly guistate: MenuState - readonly layout: LayoutConfig - readonly featureSwitches: FeatureSwitchState + readonly guistate: MenuState; + readonly layout: LayoutConfig; + readonly featureSwitches: FeatureSwitchState; - readonly layerState: LayerState - readonly featureProperties: { getStore(id: string): UIEventSource> } + readonly layerState: LayerState; + readonly featureProperties: { getStore(id: string): UIEventSource>, trackFeature?(feature: { properties: OsmTags }) }; - readonly indexedFeatures: IndexedFeatureSource + readonly indexedFeatures: IndexedFeatureSource; - /** - * Some features will create a new element that should be displayed. - * These can be injected by appending them to this featuresource (and pinging it) - */ - readonly newFeatures: WritableFeatureSource + /** + * Some features will create a new element that should be displayed. + * These can be injected by appending them to this featuresource (and pinging it) + */ + readonly newFeatures: WritableFeatureSource; - readonly historicalUserLocations: WritableFeatureSource> + readonly historicalUserLocations: WritableFeatureSource>; - readonly osmConnection: OsmConnection - readonly featureSwitchUserbadge: Store - readonly featureSwitchIsTesting: Store - readonly changes: Changes - readonly osmObjectDownloader: OsmObjectDownloader - /** - * State of the main map - */ - readonly mapProperties: MapProperties & ExportableMap + readonly osmConnection: OsmConnection; + readonly featureSwitchUserbadge: Store; + readonly featureSwitchIsTesting: Store; + readonly changes: Changes; + readonly osmObjectDownloader: OsmObjectDownloader; + /** + * State of the main map + */ + readonly mapProperties: MapProperties & ExportableMap; - readonly selectedElement: UIEventSource - /** - * Works together with 'selectedElement' to indicate what properties should be displayed - */ - readonly selectedLayer: UIEventSource - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> + readonly selectedElement: UIEventSource; + /** + * Works together with 'selectedElement' to indicate what properties should be displayed + */ + readonly selectedLayer: UIEventSource; + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; - /** - * If data is currently being fetched from external sources - */ - readonly dataIsLoading: Store - /** - * Only needed for 'ReplaceGeometryAction' - */ - readonly fullNodeDatabase?: FullNodeDatabaseSource + /** + * If data is currently being fetched from external sources + */ + readonly dataIsLoading: Store; + /** + * Only needed for 'ReplaceGeometryAction' + */ + readonly fullNodeDatabase?: FullNodeDatabaseSource; - readonly perLayer: ReadonlyMap - readonly userRelatedState: { - readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> - readonly mangroveIdentity: MangroveIdentity - readonly showAllQuestionsAtOnce: UIEventSource - readonly preferencesAsTags: Store> - readonly language: UIEventSource - } - readonly lastClickObject: WritableFeatureSource + readonly perLayer: ReadonlyMap; + readonly userRelatedState: { + readonly imageLicense: UIEventSource; + readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> + readonly mangroveIdentity: MangroveIdentity + readonly showAllQuestionsAtOnce: UIEventSource + readonly preferencesAsTags: Store> + readonly language: UIEventSource + }; + readonly lastClickObject: WritableFeatureSource; - readonly availableLayers: Store + readonly availableLayers: Store; + + readonly imageUploadManager: ImageUploadManager; } export interface SpecialVisualization { - readonly funcName: string - readonly docs: string | BaseUIElement - readonly example?: string + readonly funcName: string; + readonly docs: string | BaseUIElement; + readonly example?: string; - /** - * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included - */ - readonly needsNodeDatabase?: boolean - readonly args: { - name: string - defaultValue?: string - doc: string - required?: false | boolean - }[] - readonly getLayerDependencies?: (argument: string[]) => string[] + /** + * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included + */ + readonly needsNodeDatabase?: boolean; + readonly args: { + name: string + defaultValue?: string + doc: string + required?: false | boolean + }[]; + readonly getLayerDependencies?: (argument: string[]) => string[]; - structuredExamples?(): { feature: Feature>; args: string[] }[] + structuredExamples?(): { feature: Feature>; args: string[] }[]; - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement; } export type RenderingSpecification = - | string - | { - func: SpecialVisualization - args: string[] - style: string - } + | string + | { + func: SpecialVisualization + args: string[] + style: string +} diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 0d9e1d66f..23d1b57c8 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,79 +1,71 @@ -import Combine from "./Base/Combine" -import { FixedUiElement } from "./Base/FixedUiElement" -import BaseUIElement from "./BaseUIElement" -import Title from "./Base/Title" -import Table from "./Base/Table" -import { - RenderingSpecification, - SpecialVisualization, - SpecialVisualizationState, -} from "./SpecialVisualization" -import { HistogramViz } from "./Popup/HistogramViz" -import { MinimapViz } from "./Popup/MinimapViz" -import { ShareLinkViz } from "./Popup/ShareLinkViz" -import { UploadToOsmViz } from "./Popup/UploadToOsmViz" -import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" -import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" -import TagApplyButton from "./Popup/TagApplyButton" -import { CloseNoteButton } from "./Popup/CloseNoteButton" -import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" -import AllTagsPanel from "./Popup/AllTagsPanel.svelte" -import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" -import { ImageCarousel } from "./Image/ImageCarousel" -import { ImageUploadFlow } from "./Image/ImageUploadFlow" -import { VariableUiElement } from "./Base/VariableUIElement" -import { Utils } from "../Utils" -import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" -import { Translation } from "./i18n/Translation" -import Translations from "./i18n/Translations" -import ReviewForm from "./Reviews/ReviewForm" -import ReviewElement from "./Reviews/ReviewElement" -import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" -import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" -import { SubtleButton } from "./Base/SubtleButton" -import Svg from "../Svg" -import NoteCommentElement from "./Popup/NoteCommentElement" -import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" -import FileSelectorButton from "./Input/FileSelectorButton" -import { LoginToggle } from "./Popup/LoginButton" -import Toggle from "./Input/Toggle" -import { SubstitutedTranslation } from "./SubstitutedTranslation" -import List from "./Base/List" -import StatisticsPanel from "./BigComponents/StatisticsPanel" -import AutoApplyButton from "./Popup/AutoApplyButton" -import { LanguageElement } from "./Popup/LanguageElement" -import FeatureReviews from "../Logic/Web/MangroveReviews" -import Maproulette from "../Logic/Maproulette" -import SvelteUIElement from "./Base/SvelteUIElement" -import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" -import QuestionViz from "./Popup/QuestionViz" -import { Feature, Point } from "geojson" -import { GeoOperations } from "../Logic/GeoOperations" -import CreateNewNote from "./Popup/CreateNewNote.svelte" -import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" -import UserProfile from "./BigComponents/UserProfile.svelte" -import LanguagePicker from "./LanguagePicker" -import Link from "./Base/Link" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import { OsmTags, WayId } from "../Models/OsmFeature" -import MoveWizard from "./Popup/MoveWizard" -import SplitRoadWizard from "./Popup/SplitRoadWizard" -import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" -import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" -import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" -import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" -import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" -import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" -import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" -import { OpenJosm } from "./BigComponents/OpenJosm" -import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" -import FediverseValidator from "./InputElement/Validators/FediverseValidator" -import SendEmail from "./Popup/SendEmail.svelte" -import NearbyImages from "./Popup/NearbyImages.svelte" -import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" +import Combine from "./Base/Combine"; +import { FixedUiElement } from "./Base/FixedUiElement"; +import BaseUIElement from "./BaseUIElement"; +import Title from "./Base/Title"; +import Table from "./Base/Table"; +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; +import { HistogramViz } from "./Popup/HistogramViz"; +import { MinimapViz } from "./Popup/MinimapViz"; +import { ShareLinkViz } from "./Popup/ShareLinkViz"; +import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; +import { MultiApplyViz } from "./Popup/MultiApplyViz"; +import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; +import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; +import TagApplyButton from "./Popup/TagApplyButton"; +import { CloseNoteButton } from "./Popup/CloseNoteButton"; +import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; +import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; +import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; +import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; +import { ImageCarousel } from "./Image/ImageCarousel"; +import { VariableUiElement } from "./Base/VariableUIElement"; +import { Utils } from "../Utils"; +import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; +import { Translation } from "./i18n/Translation"; +import Translations from "./i18n/Translations"; +import ReviewForm from "./Reviews/ReviewForm"; +import ReviewElement from "./Reviews/ReviewElement"; +import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; +import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; +import { SubtleButton } from "./Base/SubtleButton"; +import Svg from "../Svg"; +import NoteCommentElement from "./Popup/NoteCommentElement"; +import { SubstitutedTranslation } from "./SubstitutedTranslation"; +import List from "./Base/List"; +import StatisticsPanel from "./BigComponents/StatisticsPanel"; +import AutoApplyButton from "./Popup/AutoApplyButton"; +import { LanguageElement } from "./Popup/LanguageElement"; +import FeatureReviews from "../Logic/Web/MangroveReviews"; +import Maproulette from "../Logic/Maproulette"; +import SvelteUIElement from "./Base/SvelteUIElement"; +import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; +import QuestionViz from "./Popup/QuestionViz"; +import { Feature, Point } from "geojson"; +import { GeoOperations } from "../Logic/GeoOperations"; +import CreateNewNote from "./Popup/CreateNewNote.svelte"; +import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; +import UserProfile from "./BigComponents/UserProfile.svelte"; +import LanguagePicker from "./LanguagePicker"; +import Link from "./Base/Link"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; +import { OsmTags, WayId } from "../Models/OsmFeature"; +import MoveWizard from "./Popup/MoveWizard"; +import SplitRoadWizard from "./Popup/SplitRoadWizard"; +import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; +import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; +import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; +import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; +import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; +import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; +import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; +import { OpenJosm } from "./BigComponents/OpenJosm"; +import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; +import FediverseValidator from "./InputElement/Validators/FediverseValidator"; +import SendEmail from "./Popup/SendEmail.svelte"; +import NearbyImages from "./Popup/NearbyImages.svelte"; +import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; +import UploadImage from "./Image/UploadImage.svelte"; class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -272,6 +264,7 @@ export default class SpecialVisualizations { SpecialVisualizations.specialVisualizations .map((sp) => sp.funcName + "()") .join(", ") + } } @@ -616,16 +609,18 @@ export default class SpecialVisualizations { { name: "image-key", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", - defaultValue: "image", + required: false }, { name: "label", doc: "The text to show on the button", - defaultValue: "Add image", + required: false }, ], constr: (state, tags, args) => { - return new ImageUploadFlow(tags, state, args[0], args[1]) + return new SvelteUIElement(UploadImage, { + state,tags, labelText: args[1], image: args[0] + }) }, }, { @@ -864,43 +859,11 @@ export default class SpecialVisualizations { }, ], constr: (state, tags, args) => { - const isUploading = new UIEventSource(false) - const t = Translations.t.notes const id = tags.data[args[0] ?? "id"] - - const uploader = new ImgurUploader(async (url) => { - isUploading.setData(false) - await state.osmConnection.addCommentToNote(id, url) - NoteCommentElement.addCommentTo(url, tags, state) - }) - - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - Translations.t.image.addPicture, - ]).SetClass( - "p-2 border-4 border-black rounded-full font-bold h-full align-center w-full flex justify-center" - ) - - const fileSelector = new FileSelectorButton(label) - fileSelector.GetValue().addCallback((filelist) => { - isUploading.setData(true) - uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist) - }) - const ti = Translations.t.image - const uploadPanel = new Combine([ - fileSelector, - ti.respectPrivacy.SetClass("text-sm"), - ]).SetClass("flex flex-col") - return new LoginToggle( - new Toggle( - Translations.t.image.uploadingPicture.SetClass("alert"), - uploadPanel, - isUploading - ), - t.loginToAddPicture, - state - ) - }, + tags = state.featureProperties.getStore(id) + console.log("Id is", id) + return new SvelteUIElement(UploadImage, {state, tags}) + } }, { funcName: "title", diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 54de3a637..a20628a3a 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -103,7 +103,7 @@ let currentViewLayer = layout.layers.find((l) => l.id === "current_view") let rasterLayer: Store = state.mapProperties.rasterLayer let rasterLayerName = - rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maplibre.properties.name + rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name onDestroy( rasterLayer.addCallbackAndRunD((l) => { rasterLayerName = l.properties.name diff --git a/src/Utils.ts b/src/Utils.ts index 415f81d20..85ab51f36 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -387,7 +387,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be return newArr } - public static Dupiclates(arr: string[]): string[] { + public static Duplicates(arr: string[]): string[] { if (arr === undefined) { return undefined } diff --git a/src/Utils/pngMapCreator.ts b/src/Utils/pngMapCreator.ts index 60d82eb05..670aad31e 100644 --- a/src/Utils/pngMapCreator.ts +++ b/src/Utils/pngMapCreator.ts @@ -46,7 +46,7 @@ export class PngMapCreator { const pixelRatio = 4 const mapElem = new MlMap({ container: div.id, - style: AvailableRasterLayers.maplibre.properties.url, + style: AvailableRasterLayers.maptilerDefaultLayer.properties.url, center: [l.lon, l.lat], zoom: settings.zoom.data, pixelRatio, From 144d203a5b49659d26787628693ce085f1ea4bed Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Mon, 25 Sep 2023 09:24:55 +0200 Subject: [PATCH 083/133] Standardize tagging, add items and translations --- .../bicycle_tube_vending_machine.json | 121 ++++++++++-------- .../vending_machine/vending_machine.json | 72 ++++++++++- langs/layers/en.json | 81 +++++++++++- langs/layers/nl.json | 48 ++++++- 4 files changed, 263 insertions(+), 59 deletions(-) diff --git a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json index bff0c569a..da66c7c34 100644 --- a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json +++ b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json @@ -32,7 +32,9 @@ "mappings": [ { "if": "name~*", - "then": "Bicycle tube vending machine {name}" + "then": { + "en": "Bicycle tube vending machine {name}" + } } ] }, @@ -70,8 +72,7 @@ }, "tags": [ "amenity=vending_machine", - "vending=bicycle_tube", - "vending:bicycle_tube=yes" + "vending=bicycle_tube" ] } ], @@ -176,65 +177,62 @@ "id": "Still in use?" }, { - "question": "How much does a bicycle tube cost?", - "render": "A bicycle tube costs {charge}", + "question": { + "en": "How much does a bicycle tube cost?" + }, + "render": { + "en": "A bicycle tube costs {charge}" + }, "freeform": { "key": "charge" }, "id": "bicycle_tube_vending_machine-charge" }, + "payment-options-split", { - "id": "vending-machine-payment-methods", - "question": "How can one pay at this tube vending machine?", - "mappings": [ - { - "if": "payment:coins=yes", - "ifnot": "payment:coins=no", - "then": "Payment with coins is possible" - }, - { - "if": "payment:notes=yes", - "ifnot": "payment:notes=no", - "then": "Payment with notes is possible" - }, - { - "if": "payment:cards=yes", - "ifnot": "payment:cards=no", - "then": "Payment with cards is possible" - } - ], - "multiAnswer": true - }, - { - "question": "Which brand of tubes are sold here?", + "question": { + "en": "Which brand of tubes are sold here?" + }, "freeform": { "key": "brand" }, - "render": "{brand} tubes are sold here", + "render": { + "en": "{brand} tubes are sold here" + }, "mappings": [ { "if": "brand=Continental", - "then": "Continental tubes are sold here" + "then": { + "en": "Continental tubes are sold here" + } }, { "if": "brand=Schwalbe", - "then": "Schwalbe tubes are sold here" + "then": { + "en": "Schwalbe tubes are sold here" + } } ], "multiAnswer": true, "id": "bicycle_tube_vending_machine-brand" }, { - "question": "Who maintains this vending machine?", + "question": { + "en": "Who maintains this vending machine?" + }, "render": "This vending machine is maintained by {operator}", "mappings": [ { "if": "operator=Schwalbe", - "then": "Maintained by Schwalbe" + "then": { + "en": "Maintained by Schwalbe" + } }, { "if": "operator=Continental", - "then": "Maintained by Continental" + "then": { + "en": "Maintained by Continental" + } } ], "freeform": { @@ -243,33 +241,52 @@ "id": "bicycle_tube_vending_machine-operator" }, { - "id": "bicycle_tube_vending_maching-other-items", - "question": "Are other bicycle bicycle accessories sold here?", + "id": "other-items-vending", + "question": { + "en": "Are other biycle accessories sold here?" + }, "mappings": [ { - "if": "vending:bicycle_light=yes", - "ifnot": "vending:bicycle_light=no", - "then": "Bicycle lights are sold here" + "if": "vending=bicycle_tube", + "then": { + "en": "Bicycle inner tubes are sold here", + "nl": "Fietsbinnenbanden worden hier verkocht" + } }, { - "if": "vending:gloves=yes", - "ifnot": "vending:gloves=no", - "then": "Gloves are sold here" + "if": "vending=bicycle_light", + "then": { + "en": "Bicycle lights are sold here", + "nl": "Fietslampjes worden hier verkocht" + } }, { - "if": "vending:bicycle_repair_kit=yes", - "ifnot": "vending:bicycle_repair_kit=no", - "then": "Bicycle repair kits are sold here" + "if": "vending=gloves", + "then": { + "en": "Gloves are sold here", + "nl": "Handschoenen worden hier verkocht" + } }, { - "if": "vending:bicycle_pump=yes", - "ifnot": "vending:bicycle_pump=no", - "then": "Bicycle pumps are sold here" + "if": "vending=bicycle_repair_kit", + "then": { + "en": "Bicycle repair kits are sold here", + "nl": "Fietsreparatiesets worden hier verkocht" + } }, { - "if": "vending:bicycle_lock=yes", - "ifnot": "vending:bicycle_lock=no", - "then": "Bicycle locks are sold here" + "if": "vending=bicycle_pump", + "then": { + "en": "Bicycle pumps are sold here", + "nl": "Fietspompen worden hier verkocht" + } + }, + { + "if": "vending=bicycle_lock", + "then": { + "en": "Bicycle locks are sold here", + "nl": "Fietssloten worden hier verkocht" + } } ], "multiAnswer": true @@ -322,4 +339,4 @@ "cs": "Vrstva zobrazující automaty na cyklistické duše (buď speciální automaty na cyklistické duše, nebo klasické automaty s cyklistickými dušemi a případně dalšími předměty souvisejícími s jízdními koly, jako jsou světla, rukavice, zámky, ...)", "ca": "Una capa que mostra màquines expenedores per a tubs de bicicleta (ja siguin màquines expenedores de tubs de bicicleta o màquines expenedores clàssiques amb tubs de bicicleta i opcionalment objectes addicionals relacionats amb la bicicleta com ara llums, guants, panys, ...)" } -} +} \ No newline at end of file diff --git a/assets/layers/vending_machine/vending_machine.json b/assets/layers/vending_machine/vending_machine.json index deb56d624..98ce6dbfd 100644 --- a/assets/layers/vending_machine/vending_machine.json +++ b/assets/layers/vending_machine/vending_machine.json @@ -290,12 +290,39 @@ "icon": "./assets/themes/stations/public_transport_tickets.svg" }, { - "if": "vending=meat", + "if": "vending=bicycle_light", "then": { - "en": "Meat products are being sold", - "nl": "Vleesproducten worden verkocht" - }, - "icon": "./assets/layers/id_presets/temaki-meat.svg" + "en": "Bicycle lights are sold", + "nl": "Fietslampjes worden verkocht" + } + }, + { + "if": "vending=gloves", + "then": { + "en": "Gloves are sold", + "nl": "Handschoenen worden verkocht" + } + }, + { + "if": "vending=bicycle_repair_kit", + "then": { + "en": "Bicycle repair kits are sold", + "nl": "Fietsreparatiesets worden verkocht" + } + }, + { + "if": "vending=bicycle_pump", + "then": { + "en": "Bicycle pumps are sold", + "nl": "Fietspompen worden verkocht" + } + }, + { + "if": "vending=bicycle_lock", + "then": { + "en": "Bicycle locks are sold", + "nl": "Fietssloten worden verkocht" + } } ], "multiAnswer": true @@ -839,6 +866,41 @@ "question": { "en": "Sale of public transport tickets" } + }, + { + "osmTags": "vending=bicycle_light", + "question": { + "en": "Sale of bicycle lights", + "nl": "Verkoop van fietslampjes" + } + }, + { + "osmTags": "vending=gloves", + "question": { + "en": "Sale of gloves", + "nl": "Verkoop van handschoenen" + } + }, + { + "osmTags": "vending=bicycle_repair_kit", + "question": { + "en": "Sale of bicycle repair kits", + "nl": "Verkoop van fietsreparatiesets" + } + }, + { + "osmTags": "vending=bicycle_pump", + "question": { + "en": "Sale of bicycle pumps", + "nl": "Verkoop van fietspompen" + } + }, + { + "osmTags": "vending=bicycle_lock", + "question": { + "en": "Sale of bicycle locks", + "nl": "Verkoop van fietssloten" + } } ] } diff --git a/langs/layers/en.json b/langs/layers/en.json index d76fa43e5..884b1f437 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -1034,9 +1034,61 @@ }, "question": "Is this vending machine still operational?", "render": "The operational status is {operational_status}" + }, + "bicycle_tube_vending_machine-brand": { + "mappings": { + "0": { + "then": "Continental tubes are sold here" + }, + "1": { + "then": "Schwalbe tubes are sold here" + } + }, + "question": "Which brand of tubes are sold here?", + "render": "{brand} tubes are sold here" + }, + "bicycle_tube_vending_machine-charge": { + "question": "How much does a bicycle tube cost?", + "render": "A bicycle tube costs {charge}" + }, + "bicycle_tube_vending_machine-operator": { + "mappings": { + "0": { + "then": "Maintained by Schwalbe" + }, + "1": { + "then": "Maintained by Continental" + } + }, + "question": "Who maintains this vending machine?" + }, + "other-items-vending": { + "mappings": { + "0": { + "then": "Bicycle lights are sold here" + }, + "1": { + "then": "Gloves are sold here" + }, + "2": { + "then": "Bicycle repair kits are sold here" + }, + "3": { + "then": "Bicycle pumps are sold here" + }, + "4": { + "then": "Bicycle locks are sold here" + } + }, + "question": "Are other biycle accessories sold here?" } }, "title": { + "mappings": { + "0": { + "then": "Bicycle tube vending machine {name}" + } + }, "render": "Bicycle tube vending machine" } }, @@ -9910,6 +9962,21 @@ }, "20": { "question": "Sale of public transport tickets" + }, + "21": { + "question": "Sale of bicycle lights" + }, + "22": { + "question": "Sale of gloves" + }, + "23": { + "question": "Sale of bicycle repair kits" + }, + "24": { + "question": "Sale of bicycle pumps" + }, + "25": { + "question": "Sale of bicycle locks" } } } @@ -10011,7 +10078,19 @@ "then": "Public transport tickets are sold" }, "20": { - "then": "Meat products are being sold" + "then": "Bicycle lights are sold" + }, + "21": { + "then": "Gloves are sold" + }, + "22": { + "then": "Bicycle repair kits are sold" + }, + "23": { + "then": "Bicycle pumps are sold" + }, + "24": { + "then": "Bicycle locks are sold" } }, "question": "What does this vending machine sell?", diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 817c577ec..c710940f9 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -930,6 +930,25 @@ }, "question": "Is deze verkoopsautomaat nog steeds werkende?", "render": "Deze verkoopsautomaat is {operational_status}" + }, + "other-items-vending": { + "mappings": { + "0": { + "then": "Fietslampjes worden hier verkocht" + }, + "1": { + "then": "Handschoenen worden hier verkocht" + }, + "2": { + "then": "Fietsreparatiesets worden hier verkocht" + }, + "3": { + "then": "Fietspompen worden hier verkocht" + }, + "4": { + "then": "Fietssloten worden hier verkocht" + } + } } }, "title": { @@ -9056,6 +9075,21 @@ }, "17": { "question": "Verkoop van bloemen" + }, + "21": { + "question": "Verkoop van fietslampjes" + }, + "22": { + "question": "Verkoop van handschoenen" + }, + "23": { + "question": "Verkoop van fietsreparatiesets" + }, + "24": { + "question": "Verkoop van fietspompen" + }, + "25": { + "question": "Verkoop van fietssloten" } } } @@ -9148,7 +9182,19 @@ "then": "Openbaar vervoerkaartjes worden verkocht" }, "20": { - "then": "Vleesproducten worden verkocht" + "then": "Fietslampjes worden verkocht" + }, + "21": { + "then": "Handschoenen worden verkocht" + }, + "22": { + "then": "Fietsreparatiesets worden verkocht" + }, + "23": { + "then": "Fietspompen worden verkocht" + }, + "24": { + "then": "Fietssloten worden verkocht" } }, "question": "Wat verkoopt deze verkoopautomaat?", From df88fd2f7188084a733804401638f65692f8a24e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 11:30:50 +0200 Subject: [PATCH 084/133] Deployment: improve deployment script --- scripts/hetzner/deployHetzner.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/hetzner/deployHetzner.sh b/scripts/hetzner/deployHetzner.sh index 70aec756f..8d692a40c 100755 --- a/scripts/hetzner/deployHetzner.sh +++ b/scripts/hetzner/deployHetzner.sh @@ -10,6 +10,8 @@ # unzip tiles.zip MAPCOMPLETE_CONFIGURATION="config_hetzner" +npm run reset:layeroverview +npm run test cp config.json config.json.bu && cp ./scripts/hetzner/config.json . && npm run prepare-deploy && @@ -17,4 +19,5 @@ mv config.json.bu config.json && zip dist.zip -r dist/* && scp -r dist.zip hetzner:/root/ && scp ./scripts/hetzner/config/* hetzner:/root/ -ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ mv dist public && caddy stop && caddy start" +ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" +rm dist.zip From abcdc174dfbbad1b277a03ab6008aab47096db03 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Mon, 25 Sep 2023 11:59:17 +0200 Subject: [PATCH 085/133] Reset translations --- langs/layers/en.json | 11 +++++++---- langs/layers/nl.json | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/langs/layers/en.json b/langs/layers/en.json index 884b1f437..455401996 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -1065,18 +1065,21 @@ "other-items-vending": { "mappings": { "0": { - "then": "Bicycle lights are sold here" + "then": "Bicycle inner tubes are sold here" }, "1": { - "then": "Gloves are sold here" + "then": "Bicycle lights are sold here" }, "2": { - "then": "Bicycle repair kits are sold here" + "then": "Gloves are sold here" }, "3": { - "then": "Bicycle pumps are sold here" + "then": "Bicycle repair kits are sold here" }, "4": { + "then": "Bicycle pumps are sold here" + }, + "5": { "then": "Bicycle locks are sold here" } }, diff --git a/langs/layers/nl.json b/langs/layers/nl.json index c710940f9..81f7c5cbd 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -934,18 +934,21 @@ "other-items-vending": { "mappings": { "0": { - "then": "Fietslampjes worden hier verkocht" + "then": "Fietsbinnenbanden worden hier verkocht" }, "1": { - "then": "Handschoenen worden hier verkocht" + "then": "Fietslampjes worden hier verkocht" }, "2": { - "then": "Fietsreparatiesets worden hier verkocht" + "then": "Handschoenen worden hier verkocht" }, "3": { - "then": "Fietspompen worden hier verkocht" + "then": "Fietsreparatiesets worden hier verkocht" }, "4": { + "then": "Fietspompen worden hier verkocht" + }, + "5": { "then": "Fietssloten worden hier verkocht" } } From ea19cca387fb24ad6a39c5aea00456d6a01fc67c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 17:29:17 +0200 Subject: [PATCH 086/133] Feature: automatically detect missing WebGL in generated themes --- src/index_theme.ts.template | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/index_theme.ts.template b/src/index_theme.ts.template index d20561019..c90fe9fba 100644 --- a/src/index_theme.ts.template +++ b/src/index_theme.ts.template @@ -4,9 +4,23 @@ import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte" import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; import MetaTagging from "./src/Logic/MetaTagging"; +function webgl_support() { + try { + var canvas = document.createElement("canvas") + return ( + !!window.WebGLRenderingContext && + (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")) + ) + } catch (e) { + return false + } +} -MetaTagging.setThemeMetatagging(new ThemeMetaTagging()) -const state = new ThemeViewState(new LayoutConfig( layout)) -const main = new SvelteUIElement(ThemeViewGUI, { state }) -main.AttachTo("maindiv") - +if (!webgl_support()) { + new FixedUiElement("WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this.").SetClass("block alert").AttachTo("maindiv") +}else{ + MetaTagging.setThemeMetatagging(new ThemeMetaTagging()) + const state = new ThemeViewState(new LayoutConfig( layout)) + const main = new SvelteUIElement(ThemeViewGUI, { state }) + main.AttachTo("maindiv") +} From 53918f20446837ca46639ebc1ce3ab7729de92cf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Sep 2023 19:07:11 +0200 Subject: [PATCH 087/133] Feature: support 'repeat_on', fix #1588 --- assets/layers/questions/questions.json | 15 +- src/Logic/SimpleMetaTagger.ts | 34 +- src/Logic/State/LayerState.ts | 6 +- src/Logic/Tags/TagUtils.ts | 9 + src/Models/ThemeViewState.ts | 521 +++++++++++++------------ 5 files changed, 317 insertions(+), 268 deletions(-) diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 2b351a49e..a3cf36052 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -1619,7 +1619,7 @@ }, { "id": "multilevels", - "builtin": "level", + "builtin": "single_level", "override": { "question": { "en": "What levels does this elevator go to?", @@ -1657,7 +1657,18 @@ } }, { - "id": "level", + "id": "repeated", + "labels": ["level"], + "condition": "repeat_on~*", + "render": { + "en": "Multiple, identical objects can be found on floors {repeat_on}.", + "nl": "Er zijn verschillende, identieke objecten op verdiepingen {repeat_on}." + } + }, + { + "id": "single_level", + "labels": ["level"], + "condition": "repeat_on=", "question": { "nl": "Op welke verdieping bevindt dit punt zich?", "en": "On what level is this feature located?", diff --git a/src/Logic/SimpleMetaTagger.ts b/src/Logic/SimpleMetaTagger.ts index f9f4997ff..e6c671328 100644 --- a/src/Logic/SimpleMetaTagger.ts +++ b/src/Logic/SimpleMetaTagger.ts @@ -339,21 +339,37 @@ export default class SimpleMetaTaggers { ) private static levels = new InlineMetaTagger( { - doc: "Extract the 'level'-tag into a normalized, ';'-separated value", + doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.", keys: ["_level"], }, (feature) => { - if (feature.properties["level"] === undefined) { - return false + let somethingChanged = false + if (feature.properties["level"] !== undefined) { + const l = feature.properties["level"] + const newValue = TagUtils.LevelsParser(l).join(";") + if (l !== newValue) { + feature.properties["level"] = newValue + somethingChanged = true + } } - const l = feature.properties["level"] - const newValue = TagUtils.LevelsParser(l).join(";") - if (l === newValue) { - return false + if (feature.properties["repeat_on"] !== undefined) { + const l = feature.properties["repeat_on"] + const newValue = TagUtils.LevelsParser(l).join(";") + if (l !== newValue) { + feature.properties["repeat_on"] = newValue + somethingChanged = true + } } - feature.properties["level"] = newValue - return true + + const combined = TagUtils.LevelsParser( + (feature.properties.repeat_on ?? "") + ";" + (feature.properties.level ?? "") + ).join(";") + if (feature.properties["_level"] !== combined) { + feature.properties["_level"] = combined + somethingChanged = true + } + return somethingChanged } ) private static canonicalize = new InlineMetaTagger( diff --git a/src/Logic/State/LayerState.ts b/src/Logic/State/LayerState.ts index 9354cb407..f8fb30761 100644 --- a/src/Logic/State/LayerState.ts +++ b/src/Logic/State/LayerState.ts @@ -66,11 +66,11 @@ export default class LayerState { } const t = Translations.t.general.levelSelection const conditionsOrred = [ - new Tag("level", "" + level), - new RegexTag("level", new RegExp("(.*;)?" + level + "(;.*)?")), + new Tag("_level", "" + level), + new RegexTag("_level", new RegExp("(.*;)?" + level + "(;.*)?")), ] if (level === "0") { - conditionsOrred.push(new Tag("level", "")) // No level tag is the same as level '0' + conditionsOrred.push(new Tag("_level", "")) // No level tag is the same as level '0' } console.log("Setting levels filter to", conditionsOrred) this.globalFilters.data.push({ diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index f319f1316..1c37457fe 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -483,13 +483,22 @@ export class TagUtils { * TagUtils.LevelsParser("-1") // => ["-1"] * TagUtils.LevelsParser("0;-1") // => ["0", "-1"] * TagUtils.LevelsParser(undefined) // => [] + * TagUtils.LevelsParser("") // => [] + * TagUtils.LevelsParser(";") // => [] + * */ public static LevelsParser(level: string): string[] { + if (level === undefined || level === null) { + return [] + } let spec = Utils.NoNull([level]) spec = [].concat(...spec.map((s) => s?.split(";"))) spec = [].concat( ...spec.map((s) => { s = s.trim() + if (s === "") { + return undefined + } if (s.indexOf("-") < 0 || s.startsWith("-")) { return s } diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index b6f13012b..1adc59b5a 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -1,58 +1,62 @@ -import LayoutConfig from "./ThemeConfig/LayoutConfig"; -import { SpecialVisualizationState } from "../UI/SpecialVisualization"; -import { Changes } from "../Logic/Osm/Changes"; -import { Store, UIEventSource } from "../Logic/UIEventSource"; -import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; -import { OsmConnection } from "../Logic/Osm/OsmConnection"; -import { ExportableMap, MapProperties } from "./MapProperties"; -import LayerState from "../Logic/State/LayerState"; -import { Feature, Point, Polygon } from "geojson"; -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; -import { Map as MlMap } from "maplibre-gl"; -import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"; -import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"; -import { GeoLocationState } from "../Logic/State/GeoLocationState"; -import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; -import { QueryParameters } from "../Logic/Web/QueryParameters"; -import UserRelatedState from "../Logic/State/UserRelatedState"; -import LayerConfig from "./ThemeConfig/LayerConfig"; -import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"; -import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"; -import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"; -import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; -import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"; -import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"; -import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"; -import ShowDataLayer from "../UI/Map/ShowDataLayer"; -import TitleHandler from "../Logic/Actors/TitleHandler"; -import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"; -import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"; -import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; -import { BBox } from "../Logic/BBox"; -import Constants from "./Constants"; -import Hotkeys from "../UI/Base/Hotkeys"; -import Translations from "../UI/i18n/Translations"; -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; -import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"; -import { MenuState } from "./MenuState"; -import MetaTagging from "../Logic/MetaTagging"; -import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"; +import LayoutConfig from "./ThemeConfig/LayoutConfig" +import { SpecialVisualizationState } from "../UI/SpecialVisualization" +import { Changes } from "../Logic/Osm/Changes" +import { Store, UIEventSource } from "../Logic/UIEventSource" import { - NewGeometryFromChangesFeatureSource -} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"; -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; -import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"; -import { Utils } from "../Utils"; -import { EliCategory } from "./RasterLayerProperties"; -import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"; -import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"; -import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; -import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"; -import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"; -import FilteredLayer from "./FilteredLayer"; -import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"; -import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; -import { Imgur } from "../Logic/ImageProviders/Imgur"; + FeatureSource, + IndexedFeatureSource, + WritableFeatureSource, +} from "../Logic/FeatureSource/FeatureSource" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import { ExportableMap, MapProperties } from "./MapProperties" +import LayerState from "../Logic/State/LayerState" +import { Feature, Point, Polygon } from "geojson" +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" +import { Map as MlMap } from "maplibre-gl" +import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" +import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" +import { GeoLocationState } from "../Logic/State/GeoLocationState" +import FeatureSwitchState from "../Logic/State/FeatureSwitchState" +import { QueryParameters } from "../Logic/Web/QueryParameters" +import UserRelatedState from "../Logic/State/UserRelatedState" +import LayerConfig from "./ThemeConfig/LayerConfig" +import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" +import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers" +import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" +import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" +import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" +import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" +import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" +import ShowDataLayer from "../UI/Map/ShowDataLayer" +import TitleHandler from "../Logic/Actors/TitleHandler" +import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" +import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" +import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" +import { BBox } from "../Logic/BBox" +import Constants from "./Constants" +import Hotkeys from "../UI/Base/Hotkeys" +import Translations from "../UI/i18n/Translations" +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" +import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" +import { MenuState } from "./MenuState" +import MetaTagging from "../Logic/MetaTagging" +import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" +import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" +import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" +import { Utils } from "../Utils" +import { EliCategory } from "./RasterLayerProperties" +import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" +import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" +import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" +import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" +import NoElementsInViewDetector, { + FeatureViewState, +} from "../Logic/Actors/NoElementsInViewDetector" +import FilteredLayer from "./FilteredLayer" +import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" +import { Imgur } from "../Logic/ImageProviders/Imgur" /** * @@ -63,71 +67,71 @@ import { Imgur } from "../Logic/ImageProviders/Imgur"; * It ties up all the needed elements and starts some actors. */ export default class ThemeViewState implements SpecialVisualizationState { - readonly layout: LayoutConfig; - readonly map: UIEventSource; - readonly changes: Changes; - readonly featureSwitches: FeatureSwitchState; - readonly featureSwitchIsTesting: Store; - readonly featureSwitchUserbadge: Store; + readonly layout: LayoutConfig + readonly map: UIEventSource + readonly changes: Changes + readonly featureSwitches: FeatureSwitchState + readonly featureSwitchIsTesting: Store + readonly featureSwitchUserbadge: Store - readonly featureProperties: FeaturePropertiesStore; + readonly featureProperties: FeaturePropertiesStore - readonly osmConnection: OsmConnection; - readonly selectedElement: UIEventSource; - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; - readonly mapProperties: MapProperties & ExportableMap; - readonly osmObjectDownloader: OsmObjectDownloader; + readonly osmConnection: OsmConnection + readonly selectedElement: UIEventSource + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> + readonly mapProperties: MapProperties & ExportableMap + readonly osmObjectDownloader: OsmObjectDownloader - readonly dataIsLoading: Store; + readonly dataIsLoading: Store /** * Indicates if there is _some_ data in view, even if it is not shown due to the filters */ - readonly hasDataInView: Store; + readonly hasDataInView: Store - readonly guistate: MenuState; - readonly fullNodeDatabase?: FullNodeDatabaseSource; + readonly guistate: MenuState + readonly fullNodeDatabase?: FullNodeDatabaseSource - readonly historicalUserLocations: WritableFeatureSource>; - readonly indexedFeatures: IndexedFeatureSource & LayoutSource; - readonly currentView: FeatureSource>; - readonly featuresInView: FeatureSource; - readonly newFeatures: WritableFeatureSource; - readonly layerState: LayerState; - readonly perLayer: ReadonlyMap; - readonly perLayerFiltered: ReadonlyMap; + readonly historicalUserLocations: WritableFeatureSource> + readonly indexedFeatures: IndexedFeatureSource & LayoutSource + readonly currentView: FeatureSource> + readonly featuresInView: FeatureSource + readonly newFeatures: WritableFeatureSource + readonly layerState: LayerState + readonly perLayer: ReadonlyMap + readonly perLayerFiltered: ReadonlyMap - readonly availableLayers: Store; - readonly selectedLayer: UIEventSource; - readonly userRelatedState: UserRelatedState; - readonly geolocation: GeoLocationHandler; + readonly availableLayers: Store + readonly selectedLayer: UIEventSource + readonly userRelatedState: UserRelatedState + readonly geolocation: GeoLocationHandler readonly imageUploadManager: ImageUploadManager - readonly lastClickObject: WritableFeatureSource; + readonly lastClickObject: WritableFeatureSource readonly overlayLayerStates: ReadonlyMap< string, { readonly isDisplayed: UIEventSource } - >; + > /** * All 'level'-tags that are available with the current features */ - readonly floors: Store; + readonly floors: Store constructor(layout: LayoutConfig) { - Utils.initDomPurify(); - this.layout = layout; - this.featureSwitches = new FeatureSwitchState(layout); + Utils.initDomPurify() + this.layout = layout + this.featureSwitches = new FeatureSwitchState(layout) this.guistate = new MenuState( this.featureSwitches.featureSwitchWelcomeMessage.data, layout.id - ); - this.map = new UIEventSource(undefined); - const initial = new InitialMapPositioning(layout); - this.mapProperties = new MapLibreAdaptor(this.map, initial); - const geolocationState = new GeoLocationState(); + ) + this.map = new UIEventSource(undefined) + const initial = new InitialMapPositioning(layout) + this.mapProperties = new MapLibreAdaptor(this.map, initial) + const geolocationState = new GeoLocationState() - this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting; - this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin; + this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting + this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin this.osmConnection = new OsmConnection({ dryRun: this.featureSwitches.featureSwitchIsTesting, @@ -137,68 +141,66 @@ export default class ThemeViewState implements SpecialVisualizationState { undefined, "Used to complete the login" ), - osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data - }); + osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, + }) this.userRelatedState = new UserRelatedState( this.osmConnection, layout?.language, layout, this.featureSwitches, this.mapProperties - ); + ) this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { - this.mapProperties.allowRotating.setData(fixated !== "yes"); - }); - this.selectedElement = new UIEventSource(undefined, "Selected element"); - this.selectedLayer = new UIEventSource(undefined, "Selected layer"); + this.mapProperties.allowRotating.setData(fixated !== "yes") + }) + this.selectedElement = new UIEventSource(undefined, "Selected element") + this.selectedLayer = new UIEventSource(undefined, "Selected layer") this.selectedElementAndLayer = this.selectedElement.mapD( (feature) => { - const layer = this.selectedLayer.data; + const layer = this.selectedLayer.data if (!layer) { - return undefined; + return undefined } - return { layer, feature }; + return { layer, feature } }, [this.selectedLayer] - ); + ) this.geolocation = new GeoLocationHandler( geolocationState, this.selectedElement, this.mapProperties, this.userRelatedState.gpsLocationHistoryRetentionTime - ); + ) - this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location); + this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) - - const self = this; - this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id); + const self = this + this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) { - const overlayLayerStates = new Map }>(); + const overlayLayerStates = new Map }>() for (const rasterInfo of this.layout.tileLayerSources) { const isDisplayed = QueryParameters.GetBooleanQueryParameter( "overlay-" + rasterInfo.id, rasterInfo.defaultState ?? true, "Wether or not overlayer layer " + rasterInfo.id + " is shown" - ); - const state = { isDisplayed }; - overlayLayerStates.set(rasterInfo.id, state); - new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state); + ) + const state = { isDisplayed } + overlayLayerStates.set(rasterInfo.id, state) + new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) } - this.overlayLayerStates = overlayLayerStates; + this.overlayLayerStates = overlayLayerStates } - { /* Setup the layout source * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too */ if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) { - this.fullNodeDatabase = new FullNodeDatabaseSource(); + this.fullNodeDatabase = new FullNodeDatabaseSource() } const layoutSource = new LayoutSource( @@ -208,49 +210,49 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection.Backend(), (id) => self.layerState.filteredLayers.get(id).isDisplayed, this.fullNodeDatabase - ); + ) - this.indexedFeatures = layoutSource; + this.indexedFeatures = layoutSource - const empty = []; - let currentViewIndex = 0; + const empty = [] + let currentViewIndex = 0 this.currentView = new StaticFeatureSource( this.mapProperties.bounds.map((bbox) => { if (!bbox) { - return empty; + return empty } - currentViewIndex++; + currentViewIndex++ return [ bbox.asGeoJson({ zoom: this.mapProperties.zoom.data, ...this.mapProperties.location.data, - id: "current_view" - }) - ]; + id: "current_view", + }), + ] }) - ); - this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds); - this.dataIsLoading = layoutSource.isLoading; + ) + this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) + this.dataIsLoading = layoutSource.isLoading - const indexedElements = this.indexedFeatures; - this.featureProperties = new FeaturePropertiesStore(indexedElements); + const indexedElements = this.indexedFeatures + this.featureProperties = new FeaturePropertiesStore(indexedElements) this.changes = new Changes( { dryRun: this.featureSwitches.featureSwitchIsTesting, allElements: indexedElements, featurePropertiesStore: this.featureProperties, osmConnection: this.osmConnection, - historicalUserLocations: this.geolocation.historicalUserLocations + historicalUserLocations: this.geolocation.historicalUserLocations, }, layout?.isLeftRightSensitive() ?? false - ); - this.historicalUserLocations = this.geolocation.historicalUserLocations; + ) + this.historicalUserLocations = this.geolocation.historicalUserLocations this.newFeatures = new NewGeometryFromChangesFeatureSource( this.changes, indexedElements, this.featureProperties - ); - layoutSource.addSource(this.newFeatures); + ) + layoutSource.addSource(this.newFeatures) const perLayer = new PerLayerFeatureSourceSplitter( Array.from(this.layerState.filteredLayers.values()).filter( @@ -266,11 +268,11 @@ export default class ThemeViewState implements SpecialVisualizationState { features.length, "leftover features, such as", features[0].properties - ); - } + ) + }, } - ); - this.perLayer = perLayer.perLayer; + ) + this.perLayer = perLayer.perLayer } this.perLayer.forEach((fs) => { new SaveFeatureSourceToLocalStorage( @@ -280,74 +282,80 @@ export default class ThemeViewState implements SpecialVisualizationState { fs, this.featureProperties, fs.layer.layerDef.maxAgeOfCache - ); - }); + ) + }) this.floors = this.featuresInView.features.stabilized(500).map((features) => { if (!features) { - return []; + return [] } - const floors = new Set(); + const floors = new Set() for (const feature of features) { - const level = feature.properties["level"]; + let level = feature.properties["_level"] if (level) { - const levels = level.split(";"); + const levels = level.split(";") for (const l of levels) { - floors.add(l); + floors.add(l) } } else { - floors.add("0"); // '0' is the default and is thus _always_ present + floors.add("0") // '0' is the default and is thus _always_ present } } - const sorted = Array.from(floors); + const sorted = Array.from(floors) // Sort alphabetically first, to deal with floor "A", "B" and "C" - sorted.sort(); + sorted.sort() sorted.sort((a, b) => { // We use the laxer 'parseInt' to deal with floor '1A' - const na = parseInt(a); - const nb = parseInt(b); + const na = parseInt(a) + const nb = parseInt(b) if (isNaN(na) || isNaN(nb)) { - return 0; + return 0 } - return na - nb; - }); - sorted.reverse(/* new list, no side-effects */); - return sorted; - }); + return na - nb + }) + sorted.reverse(/* new list, no side-effects */) + return sorted + }) const lastClick = (this.lastClickObject = new LastClickFeatureSource( this.mapProperties.lastClickLocation, this.layout - )); + )) this.osmObjectDownloader = new OsmObjectDownloader( this.osmConnection.Backend(), this.changes - ); + ) - this.perLayerFiltered = this.showNormalDataOn(this.map); + this.perLayerFiltered = this.showNormalDataOn(this.map) - this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView; - this.imageUploadManager = new ImageUploadManager(layout, Imgur.singleton, this.featureProperties, this.osmConnection, this.changes) + this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView + this.imageUploadManager = new ImageUploadManager( + layout, + Imgur.singleton, + this.featureProperties, + this.osmConnection, + this.changes + ) - this.initActors(); - this.addLastClick(lastClick); - this.drawSpecialLayers(); - this.initHotkeys(); - this.miscSetup(); + this.initActors() + this.addLastClick(lastClick) + this.drawSpecialLayers() + this.initHotkeys() + this.miscSetup() if (!Utils.runningFromConsole) { - console.log("State setup completed", this); + console.log("State setup completed", this) } } public showNormalDataOn(map: Store): ReadonlyMap { - const filteringFeatureSource = new Map(); + const filteringFeatureSource = new Map() this.perLayer.forEach((fs, layerName) => { const doShowLayer = this.mapProperties.zoom.map( (z) => (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), [fs.layer.isDisplayed] - ); + ) if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) @@ -357,15 +365,15 @@ export default class ThemeViewState implements SpecialVisualizationState { * Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer! * */ - return; + return } const filtered = new FilteringFeatureSource( fs.layer, fs, (id) => this.featureProperties.getStore(id), this.layerState.globalFilters - ); - filteringFeatureSource.set(layerName, filtered); + ) + filteringFeatureSource.set(layerName, filtered) new ShowDataLayer(map, { layer: fs.layer.layerDef, @@ -373,30 +381,30 @@ export default class ThemeViewState implements SpecialVisualizationState { doShowLayer, selectedElement: this.selectedElement, selectedLayer: this.selectedLayer, - fetchStore: (id) => this.featureProperties.getStore(id) - }); - }); - return filteringFeatureSource; + fetchStore: (id) => this.featureProperties.getStore(id), + }) + }) + return filteringFeatureSource } /** * Various small methods that need to be called */ private miscSetup() { - this.userRelatedState.markLayoutAsVisited(this.layout); + this.userRelatedState.markLayoutAsVisited(this.layout) this.selectedElement.addCallbackAndRunD((feature) => { // As soon as we have a selected element, we clear the selected element // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear if (feature.properties.id === "last_click") { - return; + return } - this.lastClickObject.features.setData([]); - }); + this.lastClickObject.features.setData([]) + }) if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { - Utils.LoadCustomCss(this.layout.customCss); + Utils.LoadCustomCss(this.layout.customCss) } } @@ -405,74 +413,74 @@ export default class ThemeViewState implements SpecialVisualizationState { { nomod: "Escape", onUp: true }, Translations.t.hotkeyDocumentation.closeSidebar, () => { - this.selectedElement.setData(undefined); - this.guistate.closeAll(); + this.selectedElement.setData(undefined) + this.guistate.closeAll() } - ); + ) Hotkeys.RegisterHotkey( { - nomod: "b" + nomod: "b", }, Translations.t.hotkeyDocumentation.openLayersPanel, () => { if (this.featureSwitches.featureSwitchFilter.data) { - this.guistate.openFilterView(); + this.guistate.openFilterView() } } - ); + ) Hotkeys.RegisterHotkey( { shift: "O" }, Translations.t.hotkeyDocumentation.selectMapnik, () => { - this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto); + this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) } - ); + ) const setLayerCategory = (category: EliCategory) => { - const available = this.availableLayers.data; - const current = this.mapProperties.rasterLayer; + const available = this.availableLayers.data + const current = this.mapProperties.rasterLayer const best = RasterLayerUtils.SelectBestLayerAccordingTo( available, category, current.data - ); - console.log("Best layer for category", category, "is", best.properties.id); - current.setData(best); - }; + ) + console.log("Best layer for category", category, "is", best.properties.id) + current.setData(best) + } Hotkeys.RegisterHotkey( { nomod: "O" }, Translations.t.hotkeyDocumentation.selectOsmbasedmap, () => setLayerCategory("osmbasedmap") - ); + ) Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => setLayerCategory("map") - ); + ) Hotkeys.RegisterHotkey( { nomod: "P" }, Translations.t.hotkeyDocumentation.selectAerial, () => setLayerCategory("photo") - ); + ) } private addLastClick(last_click: LastClickFeatureSource) { // The last_click gets a _very_ special treatment as it interacts with various parts - const last_click_layer = this.layerState.filteredLayers.get("last_click"); - this.featureProperties.trackFeatureSource(last_click); - this.indexedFeatures.addSource(last_click); + const last_click_layer = this.layerState.filteredLayers.get("last_click") + this.featureProperties.trackFeatureSource(last_click) + this.indexedFeatures.addSource(last_click) last_click.features.addCallbackAndRunD((features) => { if (this.selectedLayer.data?.id === "last_click") { // The last-click location moved, but we have selected the last click of the previous location // So, we update _after_ clearing the selection to make sure no stray data is sticking around - this.selectedElement.setData(undefined); - this.selectedElement.setData(features[0]); + this.selectedElement.setData(undefined) + this.selectedElement.setData(features[0]) } - }); + }) new ShowDataLayer(this.map, { features: new FilteringFeatureSource(last_click_layer, last_click), @@ -484,18 +492,18 @@ export default class ThemeViewState implements SpecialVisualizationState { if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { this.map.data.flyTo({ zoom: Constants.minZoomLevelToAddNewPoint, - center: this.mapProperties.lastClickLocation.data - }); - return; + center: this.mapProperties.lastClickLocation.data, + }) + return } // We first clear the selection to make sure no weird state is around - this.selectedLayer.setData(undefined); - this.selectedElement.setData(undefined); + this.selectedLayer.setData(undefined) + this.selectedElement.setData(undefined) - this.selectedElement.setData(feature); - this.selectedLayer.setData(last_click_layer.layerDef); - } - }); + this.selectedElement.setData(feature) + this.selectedLayer.setData(last_click_layer.layerDef) + }, + }) } /** @@ -503,7 +511,7 @@ export default class ThemeViewState implements SpecialVisualizationState { */ private drawSpecialLayers() { type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] - const empty = []; + const empty = [] /** * A listing which maps the layerId onto the featureSource */ @@ -523,21 +531,21 @@ export default class ThemeViewState implements SpecialVisualizationState { bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })] ) ), - current_view: this.currentView - }; + current_view: this.currentView, + } if (this.layout?.lockLocation) { - const bbox = new BBox(this.layout.lockLocation); - this.mapProperties.maxbounds.setData(bbox); + const bbox = new BBox(this.layout.lockLocation) + this.mapProperties.maxbounds.setData(bbox) ShowDataLayer.showRange( this.map, new StaticFeatureSource([bbox.asGeoJson({})]), this.featureSwitches.featureSwitchIsTesting - ); + ) } - const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view"); + const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") if (currentViewLayer?.tagRenderings?.length > 0) { - const params = MetaTagging.createExtraFuncParams(this); - this.featureProperties.trackFeatureSource(specialLayers.current_view); + const params = MetaTagging.createExtraFuncParams(this) + this.featureProperties.trackFeatureSource(specialLayers.current_view) specialLayers.current_view.features.addCallbackAndRunD((features) => { MetaTagging.addMetatags( features, @@ -546,37 +554,37 @@ export default class ThemeViewState implements SpecialVisualizationState { this.layout, this.osmObjectDownloader, this.featureProperties - ); - }); + ) + }) } - const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range"); + const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") - const rangeIsDisplayed = rangeFLayer?.isDisplayed; + const rangeIsDisplayed = rangeFLayer?.isDisplayed if ( !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) ) { - rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true); + rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) } this.layerState.filteredLayers.forEach((flayer) => { - const id = flayer.layerDef.id; - const features: FeatureSource = specialLayers[id]; + const id = flayer.layerDef.id + const features: FeatureSource = specialLayers[id] if (features === undefined) { - return; + return } - this.featureProperties.trackFeatureSource(features); + this.featureProperties.trackFeatureSource(features) // this.indexedFeatures.addSource(features) new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, layer: flayer.layerDef, selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer - }); - }); + selectedLayer: this.selectedLayer, + }) + }) } /** @@ -585,30 +593,35 @@ export default class ThemeViewState implements SpecialVisualizationState { private initActors() { // Unselect the selected element if it is panned out of view this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { - const selected = this.selectedElement.data; + const selected = this.selectedElement.data if (selected === undefined) { - return; + return } - const bbox = BBox.get(selected); + const bbox = BBox.get(selected) if (!bbox.overlapsWith(bounds)) { - this.selectedElement.setData(undefined); + this.selectedElement.setData(undefined) } - }); + }) this.selectedElement.addCallback((selected) => { if (selected === undefined) { // We did _unselect_ an item - we always remove the lastclick-object - this.lastClickObject.features.setData([]); - this.selectedLayer.setData(undefined); + this.lastClickObject.features.setData([]) + this.selectedLayer.setData(undefined) } - }); - new ThemeViewStateHashActor(this); - new MetaTagging(this); - new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this); - new ChangeToElementsActor(this.changes, this.featureProperties); - new PendingChangesUploader(this.changes, this.selectedElement); - new SelectedElementTagsUpdater(this); - new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers); - new PreferredRasterLayerSelector(this.mapProperties.rasterLayer, this.availableLayers, this.featureSwitches.backgroundLayerId, this.userRelatedState.preferredBackgroundLayer) + }) + new ThemeViewStateHashActor(this) + new MetaTagging(this) + new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) + new ChangeToElementsActor(this.changes, this.featureProperties) + new PendingChangesUploader(this.changes, this.selectedElement) + new SelectedElementTagsUpdater(this) + new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) + new PreferredRasterLayerSelector( + this.mapProperties.rasterLayer, + this.availableLayers, + this.featureSwitches.backgroundLayerId, + this.userRelatedState.preferredBackgroundLayer + ) } } From 7852829f1b25043533b40625112f7b194b00c6c6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Sep 2023 01:24:26 +0200 Subject: [PATCH 088/133] UX: explicitly indicate that geolocation was refused, see #1583 --- langs/en.json | 1 + src/UI/BigComponents/ThemeIntroPanel.svelte | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/langs/en.json b/langs/en.json index 42432d12c..06e94de36 100644 --- a/langs/en.json +++ b/langs/en.json @@ -197,6 +197,7 @@ "example": "Example", "examples": "Examples", "fewChangesBefore": "Please, answer a few questions of existing features before adding a new feature.", + "geopermissionDenied": "Using the geolocation was denied", "getStartedLogin": "Log in with OpenStreetMap to get started", "getStartedNewAccount": " or create a new account", "goToInbox": "Open inbox", diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index 2e43de9b0..3bf9ce163 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -74,11 +74,17 @@ - {:else if $geopermission !== "denied"} + {:else if $geopermission === "denied"} + {:else } + + {/if}
From d2c668922b7ac5dc682461c6789fbbdb37fad8c3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Sep 2023 01:25:28 +0200 Subject: [PATCH 089/133] Fix: include japanese dot as sentence separator --- src/UI/i18n/Translation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/i18n/Translation.ts b/src/UI/i18n/Translation.ts index 26fe70806..45f725bcc 100644 --- a/src/UI/i18n/Translation.ts +++ b/src/UI/i18n/Translation.ts @@ -244,7 +244,7 @@ export class Translation extends BaseUIElement { continue } let txt = this.translations[lng] - txt = txt.replace(/(\.||
).*/, "") + txt = txt.replace(/(\.||
|。).*/, "") txt = Utils.EllipsesAfter(txt, 255) tr[lng] = txt.trim() } From 72572ce1a6ba7b256624a127d6b6ce1cd0bb56a3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Sep 2023 01:25:58 +0200 Subject: [PATCH 090/133] CI: restore generate steps --- scripts/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 737f929d4..3b1cf07b8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -12,8 +12,8 @@ mkdir dist/assets 2> /dev/null export NODE_OPTIONS="--max-old-space-size=8192" # This script ends every line with '&&' to chain everything. A failure will thus stop the build -# npm run generate:editor-layer-index && -# npm run generate && +npm run generate:editor-layer-index && +npm run generate && npm run generate:layouts if [ $? -ne 0 ]; then From 8866488bd63a961af23d41c4d4b4aaaff0ccc3c3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Sep 2023 01:27:32 +0200 Subject: [PATCH 091/133] Deployment: add clean step --- scripts/hetzner/deployHetzner.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/hetzner/deployHetzner.sh b/scripts/hetzner/deployHetzner.sh index 8d692a40c..6f1044d22 100755 --- a/scripts/hetzner/deployHetzner.sh +++ b/scripts/hetzner/deployHetzner.sh @@ -21,3 +21,4 @@ scp -r dist.zip hetzner:/root/ && scp ./scripts/hetzner/config/* hetzner:/root/ ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" rm dist.zip +npm run clean From ba909ea0ecefd0708fe6f37e25c7a023a9aaa85d Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Tue, 26 Sep 2023 14:01:46 +0200 Subject: [PATCH 092/133] Only override keys --- src/Logic/Osm/OsmConnection.ts | 1027 ++++++++++++++++---------------- 1 file changed, 512 insertions(+), 515 deletions(-) diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 07028c35a..663dd15eb 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -1,553 +1,550 @@ // @ts-ignore -import { osmAuth } from "osm-auth"; -import { Store, Stores, UIEventSource } from "../UIEventSource"; -import { OsmPreferences } from "./OsmPreferences"; -import { Utils } from "../../Utils"; -import { LocalStorageSource } from "../Web/LocalStorageSource"; -import * as config from "../../../package.json"; +import { osmAuth } from "osm-auth" +import { Store, Stores, UIEventSource } from "../UIEventSource" +import { OsmPreferences } from "./OsmPreferences" +import { Utils } from "../../Utils" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import * as config from "../../../package.json" export default class UserDetails { - public loggedIn = false; - public name = "Not logged in"; - public uid: number; - public csCount = 0; - public img?: string; - public unreadMessages = 0; - public totalMessages: number = 0; - public home: { lon: number; lat: number }; - public backend: string; - public account_created: string; - public tracesCount: number = 0; - public description: string; + public loggedIn = false + public name = "Not logged in" + public uid: number + public csCount = 0 + public img?: string + public unreadMessages = 0 + public totalMessages: number = 0 + public home: { lon: number; lat: number } + public backend: string + public account_created: string + public tracesCount: number = 0 + public description: string - constructor(backend: string) { - this.backend = backend; - } + constructor(backend: string) { + this.backend = backend + } } export interface AuthConfig { - "#"?: string; // optional comment - oauth_client_id: string; - oauth_secret: string; - url: string; + "#"?: string // optional comment + oauth_client_id: string + oauth_secret: string + url: string } export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export class OsmConnection { - public static readonly oauth_configs: Record = - config.config.oauth_credentials; - public auth; - public userDetails: UIEventSource; - public isLoggedIn: Store; - public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" - ); - public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" - ); + public static readonly oauth_configs: Record = + config.config.oauth_credentials + public auth + public userDetails: UIEventSource + public isLoggedIn: Store + public gpxServiceIsOnline: UIEventSource = new UIEventSource( + "unknown" + ) + public apiIsOnline: UIEventSource = new UIEventSource( + "unknown" + ) - public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" - ); - public preferencesHandler: OsmPreferences; - public readonly _oauth_config: AuthConfig; - private readonly _dryRun: Store; - private fakeUser: boolean; - private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; - private readonly _iframeMode: Boolean | boolean; - private readonly _singlePage: boolean; - private isChecking = false; + public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( + "not-attempted" + ) + public preferencesHandler: OsmPreferences + public readonly _oauth_config: AuthConfig + private readonly _dryRun: Store + private fakeUser: boolean + private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] + private readonly _iframeMode: Boolean | boolean + private readonly _singlePage: boolean + private isChecking = false - constructor(options?: { - dryRun?: Store - fakeUser?: false | boolean - oauth_token?: UIEventSource - // Used to keep multiple changesets open and to write to the correct changeset - singlePage?: boolean - osmConfiguration?: "osm" | "osm-test" - attemptLogin?: true | boolean - }) { - options = options ?? {}; - this.fakeUser = options.fakeUser ?? false; - this._singlePage = options.singlePage ?? true; - this._oauth_config = - OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? - OsmConnection.oauth_configs.osm; - console.debug("Using backend", this._oauth_config.url); - this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; + constructor(options?: { + dryRun?: Store + fakeUser?: false | boolean + oauth_token?: UIEventSource + // Used to keep multiple changesets open and to write to the correct changeset + singlePage?: boolean + osmConfiguration?: "osm" | "osm-test" + attemptLogin?: true | boolean + }) { + options = options ?? {} + this.fakeUser = options.fakeUser ?? false + this._singlePage = options.singlePage ?? true + this._oauth_config = + OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? + OsmConnection.oauth_configs.osm + console.debug("Using backend", this._oauth_config.url) + this._iframeMode = Utils.runningFromConsole ? false : window !== window.top - // Check if there are settings available in environment variables, and if so, use those - if ( - import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && - import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined - ) { - console.debug("Using environment variables for oauth config"); - this._oauth_config = { - oauth_client_id: import.meta.env.VITE_OSM_OAUTH_CLIENT_ID, - oauth_secret: import.meta.env.VITE_OSM_OAUTH_SECRET, - url: "https://api.openstreetmap.org" - }; - } - - this.userDetails = new UIEventSource( - new UserDetails(this._oauth_config.url), - "userDetails" - ); - if (options.fakeUser) { - const ud = this.userDetails.data; - ud.csCount = 5678; - ud.loggedIn = true; - ud.unreadMessages = 0; - ud.name = "Fake user"; - ud.totalMessages = 42; - } - const self = this; - this.UpdateCapabilities(); - this.isLoggedIn = this.userDetails.map( - (user) => - user.loggedIn && - (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), - [this.apiIsOnline] - ); - this.isLoggedIn.addCallback((isLoggedIn) => { - if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { - // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do - // This means someone attempted to toggle this; so we attempt to login! - self.AttemptLogin(); - } - }); - - this._dryRun = options.dryRun ?? new UIEventSource(false); - - this.updateAuthObject(); - - this.preferencesHandler = new OsmPreferences( - this.auth, - this - ); - - if (options.oauth_token?.data !== undefined) { - console.log(options.oauth_token.data); - const self = this; - this.auth.bootstrapToken( - options.oauth_token.data, - (x) => { - console.log("Called back: ", x); - self.AttemptLogin(); - }, - this.auth - ); - - options.oauth_token.setData(undefined); - } - if (this.auth.authenticated() && options.attemptLogin !== false) { - this.AttemptLogin(); // Also updates the user badge - } else { - console.log("Not authenticated"); - } - } - - public GetPreference( - key: string, - defaultValue: string = undefined, - options?: { - documentation?: string - prefix?: string - } - ): UIEventSource { - return this.preferencesHandler.GetPreference(key, defaultValue, options); - } - - public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { - return this.preferencesHandler.GetLongPreference(key, prefix); - } - - public OnLoggedIn(action: (userDetails: UserDetails) => void) { - this._onLoggedIn.push(action); - } - - public LogOut() { - this.auth.logout(); - this.userDetails.data.loggedIn = false; - this.userDetails.data.csCount = 0; - this.userDetails.data.name = ""; - this.userDetails.ping(); - console.log("Logged out"); - this.loadingStatus.setData("not-attempted"); - } - - /** - * The backend host, without path or trailing '/' - * - * new OsmConnection().Backend() // => "https://www.openstreetmap.org" - */ - public Backend(): string { - return this._oauth_config.url; - } - - public AttemptLogin() { - this.UpdateCapabilities(); - this.loadingStatus.setData("loading"); - if (this.fakeUser) { - this.loadingStatus.setData("logged-in"); - console.log("AttemptLogin called, but ignored as fakeUser is set"); - return; - } - const self = this; - console.log("Trying to log in..."); - this.updateAuthObject(); - LocalStorageSource.Get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href - ); - this.auth.xhr( - { - method: "GET", - path: "/api/0.6/user/details" - }, - function(err, details) { - if (err != null) { - console.log(err); - self.loadingStatus.setData("error"); - if (err.status == 401) { - console.log("Clearing tokens..."); - // Not authorized - our token probably got revoked - self.auth.logout(); - self.LogOut(); - } - return; + // Check if there are settings available in environment variables, and if so, use those + if ( + import.meta.env.VITE_OSM_OAUTH_CLIENT_ID !== undefined && + import.meta.env.VITE_OSM_OAUTH_SECRET !== undefined + ) { + console.debug("Using environment variables for oauth config") + this._oauth_config.oauth_client_id = import.meta.env.VITE_OSM_OAUTH_CLIENT_ID + this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET } - if (details == null) { - self.loadingStatus.setData("error"); - return; + this.userDetails = new UIEventSource( + new UserDetails(this._oauth_config.url), + "userDetails" + ) + if (options.fakeUser) { + const ud = this.userDetails.data + ud.csCount = 5678 + ud.loggedIn = true + ud.unreadMessages = 0 + ud.name = "Fake user" + ud.totalMessages = 42 + } + const self = this + this.UpdateCapabilities() + this.isLoggedIn = this.userDetails.map( + (user) => + user.loggedIn && + (self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"), + [this.apiIsOnline] + ) + this.isLoggedIn.addCallback((isLoggedIn) => { + if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { + // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do + // This means someone attempted to toggle this; so we attempt to login! + self.AttemptLogin() + } + }) + + this._dryRun = options.dryRun ?? new UIEventSource(false) + + this.updateAuthObject() + + this.preferencesHandler = new OsmPreferences( + this.auth, + this + ) + + if (options.oauth_token?.data !== undefined) { + console.log(options.oauth_token.data) + const self = this + this.auth.bootstrapToken( + options.oauth_token.data, + (x) => { + console.log("Called back: ", x) + self.AttemptLogin() + }, + this.auth + ) + + options.oauth_token.setData(undefined) + } + if (this.auth.authenticated() && options.attemptLogin !== false) { + this.AttemptLogin() // Also updates the user badge + } else { + console.log("Not authenticated") + } + } + + public GetPreference( + key: string, + defaultValue: string = undefined, + options?: { + documentation?: string + prefix?: string + } + ): UIEventSource { + return this.preferencesHandler.GetPreference(key, defaultValue, options) + } + + public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { + return this.preferencesHandler.GetLongPreference(key, prefix) + } + + public OnLoggedIn(action: (userDetails: UserDetails) => void) { + this._onLoggedIn.push(action) + } + + public LogOut() { + this.auth.logout() + this.userDetails.data.loggedIn = false + this.userDetails.data.csCount = 0 + this.userDetails.data.name = "" + this.userDetails.ping() + console.log("Logged out") + this.loadingStatus.setData("not-attempted") + } + + /** + * The backend host, without path or trailing '/' + * + * new OsmConnection().Backend() // => "https://www.openstreetmap.org" + */ + public Backend(): string { + return this._oauth_config.url + } + + public AttemptLogin() { + this.UpdateCapabilities() + this.loadingStatus.setData("loading") + if (this.fakeUser) { + this.loadingStatus.setData("logged-in") + console.log("AttemptLogin called, but ignored as fakeUser is set") + return + } + const self = this + console.log("Trying to log in...") + this.updateAuthObject() + LocalStorageSource.Get("location_before_login").setData( + Utils.runningFromConsole ? undefined : window.location.href + ) + this.auth.xhr( + { + method: "GET", + path: "/api/0.6/user/details", + }, + function (err, details) { + if (err != null) { + console.log(err) + self.loadingStatus.setData("error") + if (err.status == 401) { + console.log("Clearing tokens...") + // Not authorized - our token probably got revoked + self.auth.logout() + self.LogOut() + } + return + } + + if (details == null) { + self.loadingStatus.setData("error") + return + } + + self.CheckForMessagesContinuously() + + // details is an XML DOM of user details + let userInfo = details.getElementsByTagName("user")[0] + + let data = self.userDetails.data + data.loggedIn = true + console.log("Login completed, userinfo is ", userInfo) + data.name = userInfo.getAttribute("display_name") + data.account_created = userInfo.getAttribute("account_created") + data.uid = Number(userInfo.getAttribute("id")) + data.csCount = Number.parseInt( + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + ) + data.tracesCount = Number.parseInt( + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 + ) + + data.img = undefined + const imgEl = userInfo.getElementsByTagName("img") + if (imgEl !== undefined && imgEl[0] !== undefined) { + data.img = imgEl[0].getAttribute("href") + } + + const description = userInfo.getElementsByTagName("description") + if (description !== undefined && description[0] !== undefined) { + data.description = description[0]?.innerHTML + } + const homeEl = userInfo.getElementsByTagName("home") + if (homeEl !== undefined && homeEl[0] !== undefined) { + const lat = parseFloat(homeEl[0].getAttribute("lat")) + const lon = parseFloat(homeEl[0].getAttribute("lon")) + data.home = { lat: lat, lon: lon } + } + + self.loadingStatus.setData("logged-in") + const messages = userInfo + .getElementsByTagName("messages")[0] + .getElementsByTagName("received")[0] + data.unreadMessages = parseInt(messages.getAttribute("unread")) + data.totalMessages = parseInt(messages.getAttribute("count")) + + self.userDetails.ping() + for (const action of self._onLoggedIn) { + action(self.userDetails.data) + } + self._onLoggedIn = [] + } + ) + } + + /** + * Interact with the API. + * + * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' + */ + public async interact( + path: string, + method: "GET" | "POST" | "PUT" | "DELETE", + header?: Record, + content?: string + ): Promise { + return new Promise((ok, error) => { + this.auth.xhr( + { + method, + options: { + header, + }, + content, + path: `/api/0.6/${path}`, + }, + function (err, response) { + if (err !== null) { + error(err) + } else { + ok(response) + } + } + ) + }) + } + + public async post( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "POST", header, content) + } + + public async put( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "PUT", header, content) + } + + public async get(path: string, header?: Record): Promise { + return await this.interact(path, "GET", header) + } + + public closeNote(id: number | string, text?: string): Promise { + let textSuffix = "" + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text) + } + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) + return new Promise((ok) => { + ok() + }) + } + return this.post(`notes/${id}/close${textSuffix}`) + } + + public reopenNote(id: number | string, text?: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) + return new Promise((ok) => { + ok() + }) + } + let textSuffix = "" + if ((text ?? "") !== "") { + textSuffix = "?text=" + encodeURIComponent(text) + } + return this.post(`notes/${id}/reopen${textSuffix}`) + } + + public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually opening note with text ", text) + return new Promise<{ id: number }>((ok) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ) + }) + } + // 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", + }) + const parsed = JSON.parse(response) + const id = parsed.properties + console.log("OPENED NOTE", id) + return id + } + + public async uploadGpxTrack( + gpx: string, + options: { + description: string + visibility: "private" | "public" | "trackable" | "identifiable" + filename?: string + /** + * Some words to give some properties; + * + * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. + */ + labels: string[] + } + ): Promise<{ id: number }> { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually uploading GPX ", gpx) + return new Promise<{ id: number }>((ok, error) => { + window.setTimeout( + () => ok({ id: Math.floor(Math.random() * 1000) }), + Math.random() * 5000 + ) + }) } - self.CheckForMessagesContinuously(); - - // details is an XML DOM of user details - let userInfo = details.getElementsByTagName("user")[0]; - - let data = self.userDetails.data; - data.loggedIn = true; - console.log("Login completed, userinfo is ", userInfo); - data.name = userInfo.getAttribute("display_name"); - data.account_created = userInfo.getAttribute("account_created"); - data.uid = Number(userInfo.getAttribute("id")); - data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 - ); - data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 - ); - - data.img = undefined; - const imgEl = userInfo.getElementsByTagName("img"); - if (imgEl !== undefined && imgEl[0] !== undefined) { - data.img = imgEl[0].getAttribute("href"); + const contents = { + file: gpx, + description: options.description ?? "", + tags: options.labels?.join(",") ?? "", + visibility: options.visibility, } - const description = userInfo.getElementsByTagName("description"); - if (description !== undefined && description[0] !== undefined) { - data.description = description[0]?.innerHTML; - } - const homeEl = userInfo.getElementsByTagName("home"); - if (homeEl !== undefined && homeEl[0] !== undefined) { - const lat = parseFloat(homeEl[0].getAttribute("lat")); - const lon = parseFloat(homeEl[0].getAttribute("lon")); - data.home = { lat: lat, lon: lon }; + const extras = { + file: + '; filename="' + + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + + '"\r\nContent-Type: application/gpx+xml', } - self.loadingStatus.setData("logged-in"); - const messages = userInfo - .getElementsByTagName("messages")[0] - .getElementsByTagName("received")[0]; - data.unreadMessages = parseInt(messages.getAttribute("unread")); - data.totalMessages = parseInt(messages.getAttribute("count")); + const boundary = "987654" - self.userDetails.ping(); - for (const action of self._onLoggedIn) { - action(self.userDetails.data); + let body = "" + for (const key in contents) { + body += "--" + boundary + "\r\n" + body += 'Content-Disposition: form-data; name="' + key + '"' + if (extras[key] !== undefined) { + body += extras[key] + } + body += "\r\n\r\n" + body += contents[key] + "\r\n" } - self._onLoggedIn = []; - } - ); - } + body += "--" + boundary + "--\r\n" - /** - * Interact with the API. - * - * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' - */ - public async interact( - path: string, - method: "GET" | "POST" | "PUT" | "DELETE", - header?: Record, - content?: string - ): Promise { - return new Promise((ok, error) => { - this.auth.xhr( - { - method, - options: { - header - }, - content, - path: `/api/0.6/${path}` - }, - function(err, response) { - if (err !== null) { - error(err); - } else { - ok(response); - } + const response = await this.post("gpx/create", body, { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length, + }) + const parsed = JSON.parse(response) + console.log("Uploaded GPX track", parsed) + return { id: parsed } + } + + public addCommentToNote(id: number | string, text: string): Promise { + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id) + return new Promise((ok) => { + ok() + }) } - ); - }); - } - - public async post( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "POST", header, content); - } - - public async put( - path: string, - content?: string, - header?: Record - ): Promise { - return await this.interact(path, "PUT", header, content); - } - - public async get(path: string, header?: Record): Promise { - return await this.interact(path, "GET", header); - } - - public closeNote(id: number | string, text?: string): Promise { - let textSuffix = ""; - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text); - } - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text); - return new Promise((ok) => { - ok(); - }); - } - return this.post(`notes/${id}/close${textSuffix}`); - } - - public reopenNote(id: number | string, text?: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text); - return new Promise((ok) => { - ok(); - }); - } - let textSuffix = ""; - if ((text ?? "") !== "") { - textSuffix = "?text=" + encodeURIComponent(text); - } - return this.post(`notes/${id}/reopen${textSuffix}`); - } - - public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually opening note with text ", text); - return new Promise<{ id: number }>((ok) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ); - }); - } - // 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" - }); - const parsed = JSON.parse(response); - const id = parsed.properties; - console.log("OPENED NOTE", id); - return id; - } - - public async uploadGpxTrack( - gpx: string, - options: { - description: string - visibility: "private" | "public" | "trackable" | "identifiable" - filename?: string - /** - * Some words to give some properties; - * - * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. - */ - labels: string[] - } - ): Promise<{ id: number }> { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually uploading GPX ", gpx); - return new Promise<{ id: number }>((ok, error) => { - window.setTimeout( - () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 - ); - }); - } - - const contents = { - file: gpx, - description: options.description ?? "", - tags: options.labels?.join(",") ?? "", - visibility: options.visibility - }; - - const extras = { - file: - "; filename=\"" + - (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - "\"\r\nContent-Type: application/gpx+xml" - }; - - const boundary = "987654"; - - let body = ""; - for (const key in contents) { - body += "--" + boundary + "\r\n"; - body += "Content-Disposition: form-data; name=\"" + key + "\""; - if (extras[key] !== undefined) { - body += extras[key]; - } - body += "\r\n\r\n"; - body += contents[key] + "\r\n"; - } - body += "--" + boundary + "--\r\n"; - - const response = await this.post("gpx/create", body, { - "Content-Type": "multipart/form-data; boundary=" + boundary, - "Content-Length": body.length - }); - const parsed = JSON.parse(response); - console.log("Uploaded GPX track", parsed); - return { id: parsed }; - } - - public addCommentToNote(id: number | string, text: string): Promise { - if (this._dryRun.data) { - console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id); - return new Promise((ok) => { - ok(); - }); - } - if ((text ?? "") === "") { - throw "Invalid text!"; - } - - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", - - path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` - }, - function(err, _) { - if (err !== null) { - error(err); - } else { - ok(); - } + if ((text ?? "") === "") { + throw "Invalid text!" } - ); - }); - } - /** - * To be called by land.html - */ - public finishLogin(callback: (previousURL: string) => void) { - this.auth.authenticate(function() { - // Fully authed at this point - console.log("Authentication successful!"); - const previousLocation = LocalStorageSource.Get("location_before_login"); - callback(previousLocation.data); - }); - } + return new Promise((ok, error) => { + this.auth.xhr( + { + method: "POST", - private updateAuthObject() { - let pwaStandAloneMode = false; - try { - if (Utils.runningFromConsole) { - pwaStandAloneMode = true; - } else if ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: fullscreen)").matches - ) { - pwaStandAloneMode = true; - } - } catch (e) { - console.warn( - "Detecting standalone mode failed", - e, - ". Assuming in browser and not worrying furhter" - ); + path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, + }, + function (err, _) { + if (err !== null) { + error(err) + } else { + ok() + } + } + ) + }) } - const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage; - // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... - // Same for an iframe... - - this.auth = new osmAuth({ - client_id: this._oauth_config.oauth_client_id, - url: this._oauth_config.url, - scope: "read_prefs write_prefs write_api write_gpx write_notes", - redirect_uri: Utils.runningFromConsole - ? "https://mapcomplete.org/land.html" - : window.location.protocol + "//" + window.location.host + "/land.html", - singlepage: !standalone, - auto: true - }); - } - - private CheckForMessagesContinuously() { - const self = this; - if (this.isChecking) { - return; + /** + * To be called by land.html + */ + public finishLogin(callback: (previousURL: string) => void) { + this.auth.authenticate(function () { + // Fully authed at this point + console.log("Authentication successful!") + const previousLocation = LocalStorageSource.Get("location_before_login") + callback(previousLocation.data) + }) } - this.isChecking = true; - Stores.Chronic(5 * 60 * 1000).addCallback((_) => { - if (self.isLoggedIn.data) { - console.log("Checking for messages"); - self.AttemptLogin(); - } - }); - } - private UpdateCapabilities(): void { - const self = this; - this.FetchCapabilities().then(({ api, gpx }) => { - self.apiIsOnline.setData(api); - self.gpxServiceIsOnline.setData(gpx); - }); - } + private updateAuthObject() { + let pwaStandAloneMode = false + try { + if (Utils.runningFromConsole) { + pwaStandAloneMode = true + } else if ( + window.matchMedia("(display-mode: standalone)").matches || + window.matchMedia("(display-mode: fullscreen)").matches + ) { + pwaStandAloneMode = true + } + } catch (e) { + console.warn( + "Detecting standalone mode failed", + e, + ". Assuming in browser and not worrying furhter" + ) + } + const standalone = this._iframeMode || pwaStandAloneMode || !this._singlePage - private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { - if (Utils.runningFromConsole) { - return { api: "online", gpx: "online" }; + // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... + // Same for an iframe... + + this.auth = new osmAuth({ + client_id: this._oauth_config.oauth_client_id, + url: this._oauth_config.url, + scope: "read_prefs write_prefs write_api write_gpx write_notes", + redirect_uri: Utils.runningFromConsole + ? "https://mapcomplete.org/land.html" + : window.location.protocol + "//" + window.location.host + "/land.html", + singlepage: !standalone, + auto: true, + }) } - const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities"); - if (result["content"] === undefined) { - console.log("Something went wrong:", result); - return { api: "unreachable", gpx: "unreachable" }; + + private CheckForMessagesContinuously() { + const self = this + if (this.isChecking) { + return + } + this.isChecking = true + Stores.Chronic(5 * 60 * 1000).addCallback((_) => { + if (self.isLoggedIn.data) { + console.log("Checking for messages") + self.AttemptLogin() + } + }) + } + + private UpdateCapabilities(): void { + const self = this + this.FetchCapabilities().then(({ api, gpx }) => { + self.apiIsOnline.setData(api) + self.gpxServiceIsOnline.setData(gpx) + }) + } + + private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { + if (Utils.runningFromConsole) { + return { api: "online", gpx: "online" } + } + const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") + if (result["content"] === undefined) { + console.log("Something went wrong:", result) + return { api: "unreachable", gpx: "unreachable" } + } + const xmlRaw = result["content"] + const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") + const statusEl = parsed.getElementsByTagName("status")[0] + const api = statusEl.getAttribute("api") + const gpx = statusEl.getAttribute("gpx") + return { api, gpx } } - const xmlRaw = result["content"]; - const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml"); - const statusEl = parsed.getElementsByTagName("status")[0]; - const api = statusEl.getAttribute("api"); - const gpx = statusEl.getAttribute("gpx"); - return { api, gpx }; - } } From d9d8c0ad1f0af64f6d08364a6c64f59b748f001a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Sep 2023 22:22:19 +0200 Subject: [PATCH 093/133] Themes: fix icon for shops layer --- assets/layers/shops/shops.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 8d6096d8e..ee0cf2bc1 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -348,6 +348,11 @@ "override": { "render": "./assets/layers/id_presets/maki-shop.svg", "+mappings": [ + { + "#": "Layer icon rendering", + "if": "id=", + "then": "circle:white;./assets/layers/id_presets/maki-shop.svg" + }, { "if": { "or": [ From c527427f70990006b9c71ef472bbe45d2c9bff70 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Sep 2023 22:38:30 +0200 Subject: [PATCH 094/133] Fix: add new items works again, fix #1593 --- src/Logic/Osm/Actions/OsmChangeAction.ts | 3 --- src/UI/Popup/AddNewPoint/AddNewPoint.svelte | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Logic/Osm/Actions/OsmChangeAction.ts b/src/Logic/Osm/Actions/OsmChangeAction.ts index 2bf31b02c..4161dc967 100644 --- a/src/Logic/Osm/Actions/OsmChangeAction.ts +++ b/src/Logic/Osm/Actions/OsmChangeAction.ts @@ -19,9 +19,6 @@ export default abstract class OsmChangeAction { constructor(mainObjectId: string, trackStatistics: boolean = true) { this.trackStatistics = trackStatistics this.mainObjectId = mainObjectId - if(mainObjectId === undefined || mainObjectId === null){ - throw "OsmObject received '"+mainObjectId+"' as mainObjectId" - } } public async Perform(changes: Changes) { diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index d89e94402..d1bce458c 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -102,7 +102,7 @@ console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); let snapToWay: undefined | OsmWay = undefined; - if (snapTo !== undefined) { + if (snapTo !== undefined && snapTo !== null) { const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); if (downloaded !== "deleted") { snapToWay = downloaded; From 6e5eeca64fbdcc944dc84fc46d189aba9bccd7d5 Mon Sep 17 00:00:00 2001 From: paunofu Date: Mon, 25 Sep 2023 10:35:07 +0000 Subject: [PATCH 095/133] Translated using Weblate (Catalan) Currently translated at 82.2% (2576 of 3131 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 645 +++++++++++++++++++++++++++++-------------- 1 file changed, 440 insertions(+), 205 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 08e5831f0..819c2a916 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" + }, "2": { "title": "un mupi sobre la paret" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tòtem" - }, - "10": { - "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Açò és un tauló d'anunis" }, + "10": { + "then": "Açò és una paret pintada" + }, "2": { "then": "Açò és una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Açò és un tòtem" - }, - "10": { - "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,6 +205,9 @@ "1": { "then": "Tauló d'anuncis" }, + "10": { + "then": "Paret Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tòtem" - }, - "10": { - "then": "Paret Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relleu" - }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -616,7 +616,7 @@ "then": "Un ciclista pot passar-hi." }, "1": { - "then": "Un ciclista pot passar-hi." + "then": "Un ciclista no pot passar-hi." } }, "question": "Una bicicleta pot passar aquesta barrera?" @@ -1820,6 +1820,27 @@ "1": { "question": "Té un connector
Schuko sense pin de terra (CEE7/4 tipus F)
connector" }, + "10": { + "question": "Té un connector
Tipus 2 amb cable (mennekes)
" + }, + "11": { + "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" + }, + "12": { + "question": "Té un connector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" + }, + "14": { + "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" + }, + "15": { + "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" + }, + "16": { + "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" + }, "2": { "question": "Té un connector
endoll de paret Europeu amb un pin de terra (CEE7/4 tipus F)
" }, @@ -1843,27 +1864,6 @@ }, "9": { "question": "Té un connector
CCS Tipus 2 (mennekes)
" - }, - "10": { - "question": "Té un connector
Tipus 2 amb cable (mennekes)
" - }, - "11": { - "question": "Té un connector
CCS Tesla Supercharger (un tipus2_css de marca)
" - }, - "12": { - "question": "Té un connector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" - }, - "14": { - "question": "Té un connector
USB per a carregar telèfons i dispositius electrònics petits
" - }, - "15": { - "question": "Té un connector
Bosch Active Connect amb 3 pins i cable
" - }, - "16": { - "question": "Té un connector
Bosch Active Connect amb 5 pins i cable
" } } } @@ -1919,30 +1919,6 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, - "2": { - "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" - }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipus 1 amb cable (J1772)" - }, - "7": { - "then": "Tipus 1 amb cable (J1772)" - }, - "8": { - "then": "Tipus 1 sense cable (J1772)" - }, - "9": { - "then": "Tipus 1 sense cable (J1772)" - }, "10": { "then": "CSS 1Tipus 1 (també conegut com Tipus 1 combo)" }, @@ -1973,6 +1949,9 @@ "19": { "then": "Tipus 2 amb cable (mennekes)" }, + "2": { + "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" + }, "20": { "then": "CSS Supercarregador Tesla (un tipus2_css de la marca)" }, @@ -2003,11 +1982,32 @@ "29": { "then": "Bosch Active Connect amb 3 pins i cable" }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, "30": { "then": "Bosch Active Connect amb 5 pins i cable" }, "31": { "then": "Bosch Active Connect amb 5 pins i cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipus 1 amb cable (J1772)" + }, + "7": { + "then": "Tipus 1 amb cable (J1772)" + }, + "8": { + "then": "Tipus 1 sense cable (J1772)" + }, + "9": { + "then": "Tipus 1 sense cable (J1772)" } }, "question": "Quins tipus de connexions de càrrega estan disponibles aquí?" @@ -2865,6 +2865,9 @@ "1": { "then": "Aquest carril bici està pavimentat" }, + "10": { + "then": "Aquesta via ciclista està feta de gravilla" + }, "2": { "then": "Aquest carril bici està fet d'asfalt" }, @@ -2876,9 +2879,6 @@ }, "9": { "then": "Aquesta via ciclista està feta de grava" - }, - "10": { - "then": "Aquesta via ciclista està feta de gravilla" } }, "question": "De què està feta la superfície d'aquest carrer?", @@ -3383,6 +3383,20 @@ } } }, + "speech_output_available": { + "questionHint": "P. e. anuncia la planta actual" + }, + "tactile_writing_available": { + "mappings": { + "0": { + "then": "L'ascensor té escriptura tàctil en Braille" + }, + "1": { + "then": "Aquest ascensor no té escriptura tàctil" + } + }, + "question": "Aquest ascensor té escriptura tàctil?" + }, "tactile_writing_language": { "render": { "special": { @@ -3428,6 +3442,12 @@ }, "1": { "then": "Costa 2 euros premsar un cèntim." + }, + "2": { + "then": "Costa 2 francs suïssos premsar un cèntim." + }, + "3": { + "then": "Costa 1 franc suís premsar un cèntim." } }, "question": "Quant costa premsar un cèntim?", @@ -3452,6 +3472,12 @@ }, "4": { "then": "Esta premsa de cèntims utilitza una moneda de 50 cèntims per a premsar." + }, + "5": { + "then": "Esta premsa de cèntims utilitza una moneda de 10 cèntims per a premsar." + }, + "6": { + "then": "Esta premsa de cèntims utilitza una moneda de 20 cèntims per a premsar." } }, "question": "Quina moneda s'utilitza per a premsar?", @@ -3478,6 +3504,20 @@ "question": "Quants dissenys hi han disponibles?", "render": "Esta premsa té {coin:design_count} dissenys disponibles." }, + "fee": { + "mappings": { + "0": { + "then": "Costa diners premsar una moneda." + }, + "1": { + "then": "Costa diners premsar una moneda." + }, + "2": { + "then": "És gratuït premsar una moneda." + } + }, + "question": "Costa diners premsar una moneda?" + }, "indoor": { "mappings": { "0": { @@ -3758,6 +3798,13 @@ "question": "Té opcions orgàniques" } } + }, + "9": { + "options": { + "0": { + "question": "Ús gratuït" + } + } } } }, @@ -3940,6 +3987,9 @@ }, "2": { "options": { + "0": { + "question": "Restaurants i negocis de menjar ràpid" + }, "1": { "question": "Només negocis de menjar ràpid" }, @@ -3948,6 +3998,13 @@ } } }, + "3": { + "options": { + "0": { + "question": "Té menú vegetarià" + } + } + }, "4": { "options": { "0": { @@ -3987,6 +4044,21 @@ "1": { "then": "Això és una fregiduria" }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" + }, "2": { "then": "Principalment serveix pasta" }, @@ -4010,21 +4082,6 @@ }, "9": { "then": "Aquí es serveixen plats francesos" - }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin menjar es serveix aquí?", @@ -4778,13 +4835,34 @@ } }, "tagRenderings": { + "map-attribution": { + "mappings": { + "0": { + "then": "OpenStreetMap està clarament atribuït, incloent la llicència ODBL" + }, + "1": { + "then": "OpenStreetMap està clarament atribuït, però no es menciona la llicència" + }, + "2": { + "then": "OpenStreetMap no es mencionava, però algú hi ha posat una enganxina d'OpenStreetMap" + }, + "3": { + "then": "No hi ha atribució" + }, + "4": { + "then": "No hi ha atribució" + } + }, + "question": "Hi ha atribució a OpenStreetMap?" + }, "map-map_source": { "mappings": { "0": { "then": "Aquest mapa està basat en OpenStreetMap" } }, - "question": "En quines dades es basa aquest mapa?" + "question": "En quines dades es basa aquest mapa?", + "render": "Aquest mapa està basat en {map_source}" }, "map_size": { "mappings": { @@ -4811,10 +4889,17 @@ "0": { "then": "Mapa topogràfic

El mapa conté línies de contorn.

" }, + "1": { + "then": "Un mapa amb tots els carrers o camins d'una àrea.

Els carrers estan majoritàriament nomenats; els angles, distàncies etc. són acurades

" + }, "2": { "then": "Això és un mapa esquemàtic.

Un mapa esbossat amb només camins importants i PDI. Els angles, els trajectes etc. són merament il·lustratius, no acurat.

" + }, + "3": { + "then": "Això és un toposcope.

Un marcador erigit en llocs alts que indica la direcció cap als elements paisatgístics notables que es poden veure des d'aquest punt

" } - } + }, + "question": "Quin tipus de mapa es mostra?" } }, "title": { @@ -4900,6 +4985,9 @@ }, "1": { "then": "La tasca està arreglada" + }, + "2": { + "then": "La tasca és un fals positiu" } } } @@ -5047,29 +5135,47 @@ }, "1": { "then": "No s'admeten gossos" + }, + "2": { + "then": "Els gossos poden anar solts" } - } + }, + "question": "Els gossos estan permesos en aquesta reserva natural?" }, "Editable description": { - "question": "Hi ha alguna informació addicional?" + "question": "Hi ha alguna informació addicional?", + "render": "Informació adicional: {description:0}" }, "Email": { "question": "A quina adreça de correu electrònic es pot enviar amb preguntes i problemes amb aquest parc natural?", "questionHint": "Respecteu la privadesa: només ompliu una adreça de correu electrònic personal si es publica àmpliament" }, "Name tag": { + "mappings": { + "0": { + "then": "Aquesta àrea no té un nom" + } + }, + "question": "Quin és el nom d'aquesta àrea?", "render": "Aquesta àrea s'anomena {name}" }, + "Non-editable description": { + "render": "Informació adicional: {description}" + }, "Operator tag": { "mappings": { "0": { "then": "Gestionat per NatuurPunt" }, + "1": { + "then": "Gestionat per {operator}" + }, "2": { "then": "Gestionat per Agentschap Natuur en Bos" } }, - "question": "Qui gestiona aquesta àrea?" + "question": "Qui gestiona aquesta àrea?", + "render": "Gestionat per {operator}" }, "Surface area": { "render": "Superfície: {_surface:ha}Ha" @@ -5078,12 +5184,25 @@ "question": "A quin número de telèfon es pot trucar amb preguntes i problemes amb aquest parc natural?", "questionHint": "Respecteu la privadesa: només empleneu una adreça de número de telèfon personal si es publica àmpliament" } + }, + "title": { + "render": "Reserva Natural" } }, "note": { "filter": { + "0": { + "options": { + "0": { + "question": "Has de mencionar {search} en el primer comentari" + } + } + }, "10": { "options": { + "0": { + "question": "Totes les notes" + }, "1": { "question": "Oculta les notes d'importació" }, @@ -5091,6 +5210,62 @@ "question": "Mostrar només les notes d'importació" } } + }, + "2": { + "options": { + "0": { + "question": "Obert pel contribuïdor {search}" + } + } + }, + "3": { + "options": { + "0": { + "question": "No obert pel contribuïdor {search}" + } + } + }, + "4": { + "options": { + "0": { + "question": "Editat per última vega pel contribuïdor {search}" + } + } + }, + "5": { + "options": { + "0": { + "question": "Oberta després de {search}" + } + } + }, + "6": { + "options": { + "0": { + "question": "Creada abans de {search}" + } + } + }, + "7": { + "options": { + "0": { + "question": "Creada després de {search}" + } + } + }, + "8": { + "options": { + "0": { + "question": "Sols mostrar les notes obertes per contribuïdors anònims" + } + } + }, + "9": { + "options": { + "0": { + "question": "Sols mostra les notes obertes" + } + } } }, "name": "Notes d'OpenStreetMap", @@ -5130,7 +5305,7 @@ }, "Operator": { "question": "Qui manté aquesta torre?", - "render": "Mantés per {operator}" + "render": "Mantés per {operator}" }, "access": { "mappings": { @@ -5399,6 +5574,12 @@ "1": { "then": "Aquesta és una plaça d'aparcament normal." }, + "10": { + "then": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." + }, + "11": { + "then": "Es tracta d'una plaça d'aparcament reservada al personal." + }, "2": { "then": "Aquesta és una plaça d'aparcament per a minusvàlids." }, @@ -5416,12 +5597,6 @@ }, "9": { "then": "Es tracta d'una plaça d'aparcament reservada per a motos." - }, - "10": { - "then": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." - }, - "11": { - "then": "Es tracta d'una plaça d'aparcament reservada al personal." } } } @@ -5452,9 +5627,27 @@ "name": "Camins per a vianants" }, "pharmacy": { + "filter": { + "1": { + "options": { + "0": { + "question": "Farmàcia que subministra medicaments amb recepta" + } + } + } + }, "name": "Farmàcies", + "presets": { + "0": { + "title": "una farmàcia" + } + }, "tagRenderings": { "name": { + "freeform": { + "placeholder": "Nom de la farmàcia" + }, + "question": "Quin és el nom de la farmàcia?", "render": "Aquesta farmàcia es diu {name}" }, "wheelchair": { @@ -5471,15 +5664,51 @@ }, "question": "És fàcil accedir a aquesta farmàcia amb una cadira de rodes?" } + }, + "title": { + "mappings": { + "0": { + "then": "Farmàcia" + } + }, + "render": "{name}" + } + }, + "physiotherapist": { + "description": "Aquesta capa mostra fisioterapeutes", + "name": "Fisioterapeuta", + "presets": { + "0": { + "title": "una consulta fisioterapèutica" + } + }, + "tagRenderings": { + "name": { + "question": "Quin és el nom d'aquesta consulta fisioterapèutica?", + "render": "Aquesta consulta fisioterapèutica es diu {name}" + } + }, + "title": { + "render": "Fisioterapeuta {name}" } }, "picnic_table": { + "description": "La capa mostra taules de pícnic", "name": "Taules de pícnic", "presets": { "0": { "title": "una taula de pícnic" } }, + "tagRenderings": { + "picnic_table-material": { + "mappings": { + "0": { + "then": "Aquesta és una taula de pícnic de fusta" + } + } + } + }, "title": { "render": "Taula de pícnic" } @@ -5895,6 +6124,9 @@ "1": { "then": "S'accepten monedes de 2 cèntims" }, + "10": { + "then": "S'accepten monedes de 20 cèntims" + }, "2": { "then": "S'accepten monedes de 5 cèntims" }, @@ -5918,9 +6150,6 @@ }, "9": { "then": "S'accepten monedes de 10 cèntims" - }, - "10": { - "then": "S'accepten monedes de 20 cèntims" } }, "question": "Quines monedes es poden utilitzar per a pagar aquí?" @@ -6306,30 +6535,6 @@ "1": { "question": "Reciclatge de piles" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" - }, "10": { "question": "Reciclatge de vidre" }, @@ -6360,11 +6565,35 @@ "19": { "question": "Reciclatge del rebuig" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, "20": { "question": "Reciclatge de cartutxos d'impressora" }, "21": { "question": "Reciclatge de bicicletes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" } } }, @@ -6432,30 +6661,6 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" - }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -6486,6 +6691,9 @@ "19": { "then": "Aquí es poden reciclar sabates" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -6500,6 +6708,27 @@ }, "24": { "then": "Aquí es poden reciclar bicicletes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -6663,6 +6892,11 @@ "name": "Refugi", "tagRenderings": { "shelter-type": { + "mappings": { + "4": { + "then": "Es tracta d'un cobert amb 3 parets, destinat principalment a l'acampada." + } + }, "question": "Quin tipus de refugi és aquest?" } }, @@ -7158,6 +7392,12 @@ "1": { "then": "Aquest fanal utilitza LED" }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" + }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -7181,12 +7421,6 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" - }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -7565,7 +7799,7 @@ } }, "toilet_at_amenity": { - "description": "Una capa que mostra banys (públics) ubicats en diferents llocs", + "description": "Una capa que mostra banys (públics) ubicats en diferents llocs.", "filter": { "0": { "options": { @@ -7575,6 +7809,7 @@ } } }, + "name": "Lavabos a altres instal·lacions", "tagRenderings": { "toilet-access": { "mappings": { @@ -8031,6 +8266,27 @@ "1": { "question": "Venda de begudes" }, + "10": { + "question": "Venda de llet" + }, + "11": { + "question": "Venda de pa" + }, + "12": { + "question": "Venda d'ous" + }, + "13": { + "question": "Venda de formatge" + }, + "14": { + "question": "Venda de mel" + }, + "15": { + "question": "Venda de patates" + }, + "16": { + "question": "Venda de flors" + }, "2": { "question": "Venda de llaminadures" }, @@ -8054,27 +8310,6 @@ }, "9": { "question": "Venda de càmeres interiors de bicicletes" - }, - "10": { - "question": "Venda de llet" - }, - "11": { - "question": "Venda de pa" - }, - "12": { - "question": "Venda d'ous" - }, - "13": { - "question": "Venda de formatge" - }, - "14": { - "question": "Venda de mel" - }, - "15": { - "question": "Venda de patates" - }, - "16": { - "question": "Venda de flors" } } } @@ -8115,30 +8350,6 @@ "1": { "then": "Es venen llaminadures" }, - "2": { - "then": "Es ven menjar" - }, - "3": { - "then": "Es ven tabaco" - }, - "4": { - "then": "Es venen preservatius" - }, - "5": { - "then": "Es ven cafè" - }, - "6": { - "then": "Es ven aigua" - }, - "7": { - "then": "Es venen diaris" - }, - "8": { - "then": "Es venen càmeres interiors de bicicletes" - }, - "9": { - "then": "Es ven llet" - }, "10": { "then": "Es ven pa" }, @@ -8165,6 +8376,30 @@ }, "18": { "then": "Es venen bitllets de transport públic" + }, + "2": { + "then": "Es ven menjar" + }, + "3": { + "then": "Es ven tabaco" + }, + "4": { + "then": "Es venen preservatius" + }, + "5": { + "then": "Es ven cafè" + }, + "6": { + "then": "Es ven aigua" + }, + "7": { + "then": "Es venen diaris" + }, + "8": { + "then": "Es venen càmeres interiors de bicicletes" + }, + "9": { + "then": "Es ven llet" } }, "question": "Que ven aquesta màquina expenedora?", @@ -8501,4 +8736,4 @@ } } } -} \ No newline at end of file +} From 6f5065be0f296ccf71b66a59b3b5fcee16ef387d Mon Sep 17 00:00:00 2001 From: paunofu Date: Tue, 26 Sep 2023 06:46:52 +0000 Subject: [PATCH 096/133] Translated using Weblate (Catalan) Currently translated at 86.3% (2719 of 3148 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 481 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 463 insertions(+), 18 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 819c2a916..e89503f01 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -672,6 +672,9 @@ }, "1": { "then": "Aquest banc no té una obra d'art integrada" + }, + "2": { + "then": "Aquest banc probablement no té cap obra d'art integrada" } }, "question": "Aquest banc té algun element artístic?", @@ -1653,6 +1656,9 @@ "tagRenderings": { "bird-hide-shelter-or-wall": { "mappings": { + "0": { + "then": "Observatori d'ocells" + }, "1": { "then": "Observatori d'ocells" }, @@ -1675,7 +1681,8 @@ "3": { "then": "No accessible per a persones amb cadira de rodes" } - } + }, + "question": "Aquest observatori d'ocells és accessible per als usuaris de cadira de rodes?" }, "birdhide-operator": { "mappings": { @@ -1691,6 +1698,9 @@ "mappings": { "1": { "then": "Observatori d'Ocells {name}" + }, + "2": { + "then": "Observatori d'ocells {name}" } }, "render": "Lloc d'observació d'ocells" @@ -3921,11 +3931,44 @@ "1": { "then": "Aquesta estació de fitness té un cartell amb instruccions per a un exercici concret." }, + "10": { + "then": "Aquesta estació de gimnàs té esglaons." + }, + "11": { + "then": "Aquesta estació de fitness disposa de cons per fer salts de granota." + }, + "12": { + "then": "Aquesta estació de fitness té bigues per saltar." + }, + "13": { + "then": "Aquesta estació de fitness té obstacles per a travesar." + }, + "14": { + "then": "Aquesta estació de fitness té una paret per enfilar-se." + }, "2": { "then": "Aquesta estació de fitness té una instal·lació per fer abdominals." }, "3": { "then": "Aquest estació de fitness té una instal·lació per a flexions. Normalment consta d'una o més barres horitzontals baixes." + }, + "4": { + "then": "Aquesta estació de fitness disposa de barres per fer estiraments." + }, + "5": { + "then": "Aquesta estació de fitness disposa d'una estació per fer hiperextensions." + }, + "6": { + "then": "Aquesta estació de fitness disposa d'anelles per fer exercicis de gimnàstica." + }, + "7": { + "then": "Aquest gimnàs té una escala horitzontal, també coneguda com a barres de mico." + }, + "8": { + "then": "Aquesta estació de fitness té barres per pujar-hi." + }, + "9": { + "then": "Aquesta estació de fitness té llocs per fer exercicis d'eslàlom." } } } @@ -4790,7 +4833,8 @@ "0": { "then": "Aquesta és una llar d'infants (també coneguda com a preescolar) on els nens petits reben educació primerenca." } - } + }, + "question": "Quin tipus d'instal·lació és aquesta?" }, "name": { "question": "Com s'anomena aquesta instal·lació?", @@ -5012,6 +5056,48 @@ }, "4": { "question": "Mostra les tasques que s'han omès" + }, + "5": { + "question": "Mostra tasques eliminades" + }, + "6": { + "question": "Mostra les tasques que ja estan arreglades" + }, + "7": { + "question": "Mostra les tasques marcades com a massa difícils" + }, + "8": { + "question": "Mostra tasques que s'han desactivat" + } + } + } + }, + "tagRenderings": { + "status": { + "mappings": { + "0": { + "then": "" + }, + "1": { + "then": "La tasca està arreglada" + }, + "2": { + "then": "La tasca és un fals positiu" + }, + "3": { + "then": "S'ha saltat la tasca" + }, + "4": { + "then": "S'ha suprimit la tasca" + }, + "5": { + "then": "La tasca ja està arreglada" + }, + "6": { + "then": "La tasca està marcada com a massa difícil" + }, + "7": { + "then": "La tasca està desactivada" } } } @@ -5198,6 +5284,13 @@ } } }, + "1": { + "options": { + "0": { + "question": "" + } + } + }, "10": { "options": { "0": { @@ -5618,8 +5711,12 @@ "then": "Aquesta màquina de tiquets d'aparcament no té número de referència" } }, + "question": "Quin és el número de referència d'aquesta màquina de bitllets d'aparcament?", "render": "Aquesta màquina de tiquets d'aparcament té el número de referència {ref}" } + }, + "title": { + "render": "Màquina de bitllets d'aparcament" } }, "pedestrian_path": { @@ -5627,7 +5724,15 @@ "name": "Camins per a vianants" }, "pharmacy": { + "description": "Una capa que mostra les farmàcies, que (probablement) distribueixen medicaments amb recepta", "filter": { + "0": { + "options": { + "0": { + "question": "Té autoservei" + } + } + }, "1": { "options": { "0": { @@ -5705,8 +5810,16 @@ "mappings": { "0": { "then": "Aquesta és una taula de pícnic de fusta" + }, + "1": { + "then": "Açò és una taula de pícnic de formigó" + }, + "2": { + "then": "Açò és una taula de pícnic feta de plàstic reciclat" } - } + }, + "question": "De quin material està feta aquesta taula de pícnic?", + "render": "Aquesta taula de pícnic està feta de {material}" } }, "title": { @@ -5754,6 +5867,9 @@ "2": { "then": "Només accessible per als clients del negoci que l'opera" }, + "3": { + "then": "Només accessible per als alumnes de l'escola" + }, "4": { "then": "No accessible" }, @@ -5779,7 +5895,8 @@ "question": "Aquest parc infantil està il·luminat per la nit?" }, "playground-max_age": { - "question": "Quina és l'edat màxima permesa per accedir al parc infantil?" + "question": "Quina és l'edat màxima permesa per accedir al parc infantil?", + "render": "Accessible per a nens de com a màxim {max_age}" }, "playground-min_age": { "question": "Quina és l'edat mínima requerida per a accedir al parc infantil?" @@ -5822,10 +5939,17 @@ }, "5": { "then": "La superfície és formigó" + }, + "6": { + "then": "La superfícies està sense pavimentar" + }, + "7": { + "then": "La superfície està pavimentada" } }, "question": "Quina és la superfície d'aquest parc infantil?", - "questionHint": "Si n'hi ha múltiples, selecciona la més predominant" + "questionHint": "Si n'hi ha múltiples, selecciona la més predominant", + "render": "La superfícies és {surface}" } }, "title": { @@ -5850,6 +5974,7 @@ } }, "postoffices": { + "description": "Una capa que mostra oficines postals.", "name": "Oficines de correus", "presets": { "0": { @@ -5880,7 +6005,8 @@ "then": "No pots enviar cartes des d'aquí" } }, - "question": "Pots enviar cartes des d'aquí?" + "question": "Pots enviar cartes des d'aquí?", + "render": "Podeu enviar cartes amb aquestes empreses: {post_office:letter_from}" }, "parcel-from": { "mappings": { @@ -5891,7 +6017,8 @@ "then": "No pots enviar paquets des d'aquí" } }, - "question": "Pots enviar un paquet des d'aquí?" + "question": "Pots enviar un paquet des d'aquí?", + "render": "Podeu enviar paquets amb aquestes empreses: {post_office:parcel_from}" }, "parcel-pickup": { "mappings": { @@ -5902,7 +6029,8 @@ "then": "No podeu recollir paquets perduts aquí" } }, - "question": "Es poden recollir els paquets perduts aquí?" + "question": "Es poden recollir els paquets perduts aquí?", + "render": "Podeu recollir paquets d'aquestes empreses: {post_office:parcel_pickup}" }, "parcel-to": { "mappings": { @@ -5913,7 +6041,8 @@ "then": "No pots enviar paquets ací per a arreplegar-los" } }, - "question": "Pots enviar paquets aquí per a arreplegar-los?" + "question": "Pots enviar paquets aquí per a arreplegar-los?", + "render": "Podeu enviar paquets aquí per recollir-los amb aquestes empreses: {post_office:parcel_to}" }, "partner-brand": { "mappings": { @@ -5970,7 +6099,7 @@ "then": "Col·laborador postal a {name}" } }, - "render": "Oficina de correus" + "render": "Oficina postal" } }, "public_bookcase": { @@ -6127,6 +6256,9 @@ "10": { "then": "S'accepten monedes de 20 cèntims" }, + "14": { + "then": "S'accepten monedes de 5 francs" + }, "2": { "then": "S'accepten monedes de 5 cèntims" }, @@ -6162,6 +6294,15 @@ "1": { "then": "S'accepten bitllets de 10 euros" }, + "10": { + "then": "S'accepten bitllets de 100 francs" + }, + "11": { + "then": "S'accepten bitllets de 200 francs" + }, + "12": { + "then": "S'accepten bitllets de 1000 francs" + }, "2": { "then": "S'accepten bitllets de 20 euros" }, @@ -6176,6 +6317,15 @@ }, "6": { "then": "S'accepten bitllets de 500 euros" + }, + "7": { + "then": "S'accepten bitllets de 10 francs" + }, + "8": { + "then": "S'accepten bitllets de 20 francs" + }, + "9": { + "then": "S'accepten bitllets de 50 francs" } }, "question": "Amb quins bitllets pot pagar aquí?" @@ -6263,6 +6413,13 @@ } } }, + "last_edit": { + "render": { + "special": { + "text": "Darrera edició el {_last_edit:timestamp} per {_last_edit:contributor}" + } + } + }, "level": { "mappings": { "0": { @@ -7280,6 +7437,12 @@ "tagRenderings": { "conveying": { "mappings": { + "0": { + "then": "Açò és una escala mecànica" + }, + "1": { + "then": "Açò no és una escala mecànica" + }, "2": { "then": "Això no és una escala mecànica" } @@ -7289,7 +7452,17 @@ "mappings": { "0": { "then": "Aquestes escales tenen barana" + }, + "1": { + "then": "Aquestes escales no tenen un passamà" } + }, + "question": "Aquestes escales tenen un passamà?" + }, + "multilevels": { + "override": { + "question": "Entre quines plantes estan aquestes escales?", + "render": "Aquestes escales estan entre les plantes {level}" } }, "ramp": { @@ -7300,6 +7473,9 @@ "1": { "then": "Aquí hi ha una rampa per a cadires de rodes" }, + "2": { + "then": "Aquí hi ha una rampa per a cadira de rodes, però es mostra separadament al mapa" + }, "3": { "then": "Aquí hi ha una rampa per als cotxets" }, @@ -7319,10 +7495,28 @@ } }, "question": "Aquestes escales tenen escriptura braille a la barana?" + }, + "tactile_writing_language": { + "render": { + "special": { + "question": "En quins idiomes hi ha escriptura tàctil (braille) per a la navegació? ", + "render_list_item": "Aquestes escales tenen escriptura tàctil en {language():font-bold}", + "render_single_language": "Aquestes escales tenen escriptura tàctil en {language():font-bold}" + } + } } + }, + "title": { + "mappings": { + "0": { + "then": "Escala mecànica" + } + }, + "render": "Escales" } }, "street_lamps": { + "description": "Una capa que mostra els llums del carrer", "name": "Fanals", "presets": { "0": { @@ -7342,7 +7536,8 @@ "then": "Aquest fanal emet llum taronja" } }, - "question": "Quin color de llum emet aquest fanal?" + "question": "Quin color de llum emet aquest fanal?", + "render": "Aquesta làmpada emet llum {light:color}" }, "count": { "mappings": { @@ -7353,7 +7548,8 @@ "then": "Aquest fanal té 2 aparells" } }, - "question": "Quants accessoris té aquest fanal?" + "question": "Quants accessoris té aquest fanal?", + "render": "Aquesta làmpada té {light:count} aparells" }, "direction": { "question": "Cap a on apunta aquest fanal?", @@ -7389,6 +7585,9 @@ }, "method": { "mappings": { + "0": { + "then": "Aquest fanal s'encén elèctricament" + }, "1": { "then": "Aquest fanal utilitza LED" }, @@ -7426,22 +7625,64 @@ "question": "Quin tipus d'il·luminació utilitza aquest fanal?" }, "ref": { - "question": "Quin és el número de referència d'aquest fanal?" + "question": "Quin és el número de referència d'aquest fanal?", + "render": "Aquest fanal té el número de referència {ref}" }, "support": { "mappings": { + "0": { + "then": "Aquesta làmpada està suspès mitjançant cables" + }, + "1": { + "then": "Aquesta làmpada està muntat al sostre" + }, + "2": { + "then": "Aquesta làmpada està muntat a terra" + }, + "3": { + "then": "Aquesta làmpada està muntada en un pal curt (principalment <1,5 m)" + }, "4": { - "then": "Aquest fanal està muntat en un pal" + "then": "Aquesta làmpada està muntada en un pal" + }, + "5": { + "then": "Aquesta làmpada està muntada directament a la paret" }, "6": { - "then": "Aquest fanal està muntat a la paret utilitzat una barra metàl·lica" + "then": "Aquesta làmpada està muntada a la paret utilitzat una barra metàl·lica" } - } + }, + "question": "Com està muntada aquesta làmpada?" } + }, + "title": { + "mappings": { + "0": { + "then": "Fanal {ref}" + } + }, + "render": "Fanal" } }, "surveillance_camera": { + "description": "Aquesta capa mostra les càmeres de vigilància i permet a qui col·labora, actualitzar la informació i afegir noves càmeres", "name": "Càmeres de videovigilància", + "presets": { + "0": { + "title": "una càmera de vigilància" + }, + "1": { + "title": "una càmera de vigilància muntada en una paret" + }, + "2": { + "description": "Un ALPR normalment té dues lents i una sèrie de llums infrarojes.", + "title": "una càmera ALPR (lector automàtic de matrícules, per les seves sigles en anglès)" + }, + "3": { + "description": "Un ALPR normalment té dues lents i una sèrie de llums infrarojes.", + "title": "una càmera ALPR (lector automàtic de matrícules) muntada a la paret" + } + }, "tagRenderings": { "Camera type: fixed; panning; dome": { "mappings": { @@ -7521,8 +7762,39 @@ "render": "Mètode de muntatge: {camera:mount}" }, "camera_direction": { + "mappings": { + "0": { + "then": "Grava en direcció {direction}" + } + }, "question": "En quina direcció geogràfica apunta aquesta càmera?", "render": "Grava en direcció {camera:direction}" + }, + "has_alpr": { + "mappings": { + "0": { + "then": "Es tracta d'una càmera sense reconeixement de matrícules." + }, + "1": { + "then": "Açò és un ALPR (lector automàtic de matrícules, per les seves sigles en anglès)" + } + }, + "question": "Aquesta càmera pot detectar matrícules automàticament?", + "questionHint": "Un ALPR (lector automàtic de matrícules, per les seves sigles en anglès) normalment té dues lents i una sèrie de LEDs infrarojos entremig." + }, + "is_indoor": { + "mappings": { + "0": { + "then": "Aquesta càmera es troba a l'interior" + }, + "1": { + "then": "Aquesta càmera es troba a l'exterior" + }, + "2": { + "then": "Aquesta càmera probablement es troba a l'exterior" + } + }, + "question": "L'espai públic vigilat per aquesta càmera és un espai interior o exterior?" } }, "title": { @@ -7530,13 +7802,19 @@ } }, "tertiary_education": { + "presets": { + "0": { + "title": "una universitat" + } + }, "tagRenderings": { "institution-kind": { "mappings": { "1": { "then": "Açò és una universitat, una institució d'educació terciaria on s'imparteixen carreres universitàries o superior." } - } + }, + "question": "Quin tipus d'institució és aquesta?" }, "isced": { "mappings": { @@ -7552,11 +7830,23 @@ }, "question": "Quin nivell d'educació és dona aquí?" } + }, + "title": { + "mappings": { + "2": { + "then": "Universitat" + } + } } }, "ticket_machine": { "description": "Troba màquines de bitllets per a bitllets de transport públic", "name": "Màquines de bitllets", + "presets": { + "0": { + "title": "una màquina de bitllets" + } + }, "tagRenderings": { "operator": { "freeform": { @@ -7860,16 +8150,60 @@ } }, "question": "Hi ha un lavabo específic per a usuaris amb cadira de rodes?" + }, + "wheelchair-door-width": { + "question": "Quina és l'amplada de la porta del lavabo accessible per a cadira de rodes?" + } + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": "metre" + }, + "1": { + "human": "centimetre" + } + } } } }, "trail": { "name": "Camins", + "tagRenderings": { + "Color": { + "mappings": { + "0": { + "then": "Ruta blava" + }, + "1": { + "then": "Ruta vermella" + }, + "2": { + "then": "Ruta verda" + }, + "3": { + "then": "Ruta groga" + } + } + }, + "trail-length": { + "render": "El sender té {_length:km} quilòmetres" + } + }, "title": { "render": "Camí" } }, "transit_routes": { + "description": "Capa que mostra les línies d'autobús", + "mapRendering": { + "0": { + "color": { + "render": "#ff0000" + } + } + }, "name": "Línies de bus", "tagRenderings": { "colour": { @@ -7891,7 +8225,18 @@ "to": { "question": "Quin és el punt final d'aquesta línea d'autobús?", "render": "Aquesta línia d'autobús acaba a {to}" + }, + "via": { + "render": "Aquesta línia d'autobús passa per {via}" } + }, + "title": { + "mappings": { + "0": { + "then": "{name}" + } + }, + "render": "Línia de bus" } }, "transit_stops": { @@ -7903,6 +8248,13 @@ } } }, + "1": { + "options": { + "0": { + "question": "Amb un banc" + } + } + }, "2": { "options": { "0": { @@ -7920,6 +8272,9 @@ }, "1": { "then": "Aquesta parada no té un banc" + }, + "2": { + "then": "Aquesta parada té un banc que està mapejat separadament" } }, "question": "Aquesta parada té un banc?" @@ -7931,6 +8286,9 @@ }, "1": { "then": "Aquesta parada no té paperera" + }, + "2": { + "then": "Aquesta parada té una paperera que està mapejada separadament" } }, "question": "Aquesta parada té una paperera?" @@ -7940,11 +8298,23 @@ }, "departures_board": { "mappings": { + "0": { + "then": "Aquesta parada té un tauler de sortides de tipus desconegut" + }, "1": { "then": "Aquesta parada té un tauló amb els horaris en temps real" }, "2": { "then": "Aquesta parada té un tauló amb els horaris en temps real" + }, + "3": { + "then": "Aquesta parada té un horari que mostra les sortides regulars" + }, + "4": { + "then": "Aquesta parada té un horari que conté només l'interval entre sortides" + }, + "5": { + "then": "Aquesta parada no té un tauler de sortides" } } }, @@ -7966,11 +8336,17 @@ }, "1": { "then": "Aquesta parada no té una coberta" + }, + "2": { + "then": "Aquesta parada té un refugi que està mapejat separadament" } }, "question": "Aquesta parada té una coberta?" }, "stop_name": { + "freeform": { + "placeholder": "Nom de la parada" + }, "mappings": { "0": { "then": "Aquesta parada no té nom" @@ -7990,9 +8366,17 @@ }, "question": "Aquesta parada té una superfície podotàctil?" } + }, + "title": { + "mappings": { + "0": { + "then": "Parada {name}" + } + } } }, "tree_node": { + "description": "Una capa que mostra arbres", "name": "Arbre", "presets": { "2": { @@ -8001,7 +8385,12 @@ }, "tagRenderings": { "circumference": { - "questionHint": "Es mesura a una alçada d'1,30 m" + "question": "Quina és la circumferència del tronc de l'arbre?", + "questionHint": "Es mesura a una alçada d'1,30 m", + "render": "El tronc de l'arbre té una circumferència de {circumference} metre" + }, + "height": { + "question": "Quina és l'alçada d'aquest arbre?" }, "tree-decidouous": { "mappings": { @@ -8012,9 +8401,18 @@ }, "tree-denotation": { "mappings": { + "2": { + "then": "L'arbre s'utilitza amb finalitats agrícoles, p. en un hort." + }, + "3": { + "then": "L'arbre està en un parc o semblant (cementiri, recinte escolar, …)." + }, "4": { "then": "L'arbre està en un jardí residencial." }, + "5": { + "then": "Aquest és un arbre al llarg d'una avinguda." + }, "6": { "then": "L'arbre està en una àrea urbana." } @@ -8090,6 +8488,32 @@ }, "question": "Les preguntes amb camps de dades desconeguts haurien d'aparèixer una per una o juntes?" }, + "background-layer": { + "mappings": { + "0": { + "then": "Utilitzeu la capa de fons predeterminada" + }, + "1": { + "then": "Utilitzeu OpenStreetMap-carto com a capa predeterminada" + }, + "2": { + "then": "Utilitzeu imatges aèries com a fons predeterminat" + }, + "3": { + "then": "Utilitzeu un mapa que no sigui openstreetmap com a fons predeterminat" + }, + "4": { + "then": "Utilitzeu la capa de fons actual ({__current_background}) com a fons predeterminat" + }, + "5": { + "then": "Utilitza la capa de fons {mapcomplete-preferred-background-layer} com a fons predeterminat" + } + }, + "question": "Quina capa de fons s'ha de mostrar per defecte?" + }, + "background-layer-readonly": { + "render": "Aquest mapa temàtic té un conjunt de capes de fons predefinides. La configuració predeterminada del tema no s'aplica" + }, "contributor-thanks": { "mappings": { "0": { @@ -8287,9 +8711,21 @@ "16": { "question": "Venda de flors" }, + "17": { + "question": "Venda d'aparcament" + }, + "18": { + "question": "Venda de monedes premsades" + }, + "19": { + "question": "Venda de bitllets de transport públic" + }, "2": { "question": "Venda de llaminadures" }, + "20": { + "question": "Venda de productes carnis" + }, "3": { "question": "Venda de menjar" }, @@ -8342,6 +8778,12 @@ "question": "Qui opera aquesta màquina expenedora?", "render": "{operator} gestiona aquesta màquina expenedora" }, + "phone": { + "override": { + "question": "Quin és el número de telèfon de l'operador d'aquesta màquina expenedora?", + "questionHint": "Aquest és el número al qual podeu trucar en cas de problemes amb la màquina expenedora" + } + }, "vending": { "mappings": { "0": { @@ -8377,6 +8819,9 @@ "18": { "then": "Es venen bitllets de transport públic" }, + "19": { + "then": "Es venen productes carnis" + }, "2": { "then": "Es ven menjar" }, From 02b38fec41cb3d2c24230bc02d438083b5631fbe Mon Sep 17 00:00:00 2001 From: kjon Date: Mon, 25 Sep 2023 16:51:29 +0000 Subject: [PATCH 097/133] Translated using Weblate (German) Currently translated at 98.6% (3106 of 3148 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/de/ --- langs/layers/de.json | 587 +++++++++++++++++++++++-------------------- 1 file changed, 308 insertions(+), 279 deletions(-) diff --git a/langs/layers/de.json b/langs/layers/de.json index d4e1387ef..e06449990 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -35,6 +35,16 @@ "1": { "title": "eine freistehende Posterbox" }, + "10": { + "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", + "title": "ein Schild" + }, + "11": { + "title": "eine Skulptur" + }, + "12": { + "title": "eine Wandmalerei" + }, "2": { "title": "eine wandmontierte Posterbox" }, @@ -61,16 +71,6 @@ }, "9": { "title": "ein Totem" - }, - "10": { - "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", - "title": "ein Schild" - }, - "11": { - "title": "eine Skulptur" - }, - "12": { - "title": "eine Wandmalerei" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Dies ist ein Brett" }, + "10": { + "then": "Dies ist eine Wandmalerei" + }, "2": { "then": "Dies ist eine Litfaßsäule" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Dies ist ein Totem" - }, - "10": { - "then": "Dies ist eine Wandmalerei" } }, "question": "Welche Art von Werbung ist das?", @@ -205,6 +205,9 @@ "1": { "then": "Brett" }, + "10": { + "then": "Wandmalerei" + }, "2": { "then": "Posterbox" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Totem" - }, - "10": { - "then": "Wandmalerei" } } } @@ -312,6 +312,15 @@ "1": { "then": "Wandbild" }, + "10": { + "then": "Azulejo (spanische dekorative Fliesenarbeit)" + }, + "11": { + "then": "Fliesenarbeit" + }, + "12": { + "then": "Holzschnitzerei" + }, "2": { "then": "Malerei" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relief" - }, - "10": { - "then": "Azulejo (spanische dekorative Fliesenarbeit)" - }, - "11": { - "then": "Fliesenarbeit" - }, - "12": { - "then": "Holzschnitzerei" } }, "question": "Um welche Art Kunstwerk handelt es sich?", @@ -672,6 +672,9 @@ }, "1": { "then": "Diese Bank hat kein integriertes Kunstwerk" + }, + "2": { + "then": "Die Bank hat vermutlich kein integriertes Kunstwerk" } }, "question": "Hat diese Bank ein künstlerisches Element?", @@ -1834,6 +1837,27 @@ "1": { "question": "Verfügt über einen

Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)
" }, + "10": { + "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" + }, + "11": { + "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" + }, + "12": { + "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" + }, + "13": { + "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" + }, + "14": { + "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" + }, + "15": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" + }, + "16": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" + }, "2": { "question": "Verfügt über einen
europäischen Netzstecker mit Erdungsstift (CEE7/4 Typ E)
Anschluss" }, @@ -1857,27 +1881,6 @@ }, "9": { "question": "Hat einen
Typ 2 CCS (Mennekes)
Anschluss" - }, - "10": { - "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" - }, - "11": { - "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" - }, - "12": { - "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" - }, - "13": { - "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" - }, - "14": { - "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" - }, - "15": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" - }, - "16": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" } } } @@ -1933,30 +1936,6 @@ "1": { "then": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)" }, - "2": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, - "3": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, - "4": { - "then": "Chademo-Anschluss" - }, - "5": { - "then": "Chademo-Anschluss" - }, - "6": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "7": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "8": { - "then": "Typ 1 ohne Kabel (J1772)" - }, - "9": { - "then": " Typ 1 ohne Kabel (J1772)" - }, "10": { "then": "Typ 1 CCS (Typ 1 Combo)" }, @@ -1987,6 +1966,9 @@ "19": { "then": "Typ 2 mit Kabel (mennekes)" }, + "2": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, "20": { "then": "Tesla Supercharger CCS (Typ 2 CSS von Tesla)" }, @@ -2017,11 +1999,32 @@ "29": { "then": " Bosch Active Connect mit 3 Pins und Kabel" }, + "3": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, "30": { "then": "Bosch Active Connect mit 5 Pins und Kabel" }, "31": { "then": " Bosch Active Connect mit 5 Pins und Kabel" + }, + "4": { + "then": "Chademo-Anschluss" + }, + "5": { + "then": "Chademo-Anschluss" + }, + "6": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "7": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "8": { + "then": "Typ 1 ohne Kabel (J1772)" + }, + "9": { + "then": " Typ 1 ohne Kabel (J1772)" } }, "question": "Welche Ladeanschlüsse gibt es hier?" @@ -3559,6 +3562,15 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" + }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3582,15 +3594,6 @@ }, "9": { "then": "Der Radweg ist aus Schotter" - }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieses Radwegs?", @@ -3639,6 +3642,15 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" + }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3662,15 +3674,6 @@ }, "9": { "then": "Der Radweg ist aus Schotter" - }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieser Straße?", @@ -4241,28 +4244,34 @@ } }, "elongated_coin": { - "description": "Ebene mit Münzpressen.", - "name": "Münzpressen", + "description": "Ebene mit Münzprägeautomaten.", + "name": "Münzprägeautomaten", "presets": { "0": { - "title": "Eine Münzpresse" + "title": "Einen Münzprägeautomaten" } }, "tagRenderings": { "charge": { "freeform": { - "placeholder": "Einwurf (z.B. 0,5€)" + "placeholder": "Gebühr (z.B. 0,50 €)" }, "mappings": { "0": { - "then": "Eine Münze zu Pressen kostet 1 Euro." + "then": "Die Prägung einer Münze kostet 1,00 €." }, "1": { - "then": "Eine Münze zu Pressen kostet 2€." + "then": "Die Prägung einer Münze kostet 2,00 €." + }, + "2": { + "then": "Die Prägung einer Münze kostet 2 Schweizer Franken." + }, + "3": { + "then": "Die Prägung einer Münze kostet 1 Schweizer Franken." } }, - "question": "Wieviel kostet es eine Münze zu Pressen?", - "render": "Es kostet {charge}€ um eine Münze zu Pressen." + "question": "Wieviel kostet die Prägung einer Münze?", + "render": "Die Prägung einer Münze kostet {charge}." }, "coin": { "freeform": { @@ -4270,23 +4279,29 @@ }, "mappings": { "0": { - "then": "Die Münzpresse benötigt eine 2 Cent Münze um zu Pressen." + "then": "Der Automat prägt 2 Cent Münzen." }, "1": { - "then": "Die Münzpresse benötigt eine 5 Cent Münze um zu Pressen." + "then": "Der Automat prägt 5 Cent Münzen." }, "2": { - "then": "Die Münzpresse benötigt eine 10 Cent Münze um zu Pressen." + "then": "Der Automat prägt 10 Cent Münzen." }, "3": { - "then": "Die Münzpresse benötigt eine 25 Cent Münze um zu Pressen." + "then": "Der Automat prägt 25 Cent Münzen." }, "4": { - "then": "Die Münzpresse benötigt eine 50 Cent Münze um zu Pressen." + "then": "Der Automat prägt 50 Cent Münzen." + }, + "5": { + "then": "Der Automat prägt 10 Centime Münzen." + }, + "6": { + "then": "Der Automat prägt 5 Centime Münzen." } }, - "question": "Welche Münze wird zum Pressen verwendet?", - "render": "Die Münzpresse benötigt eine {coin:type} Münze um zu Pressen." + "question": "Welche Münzen können geprägt werden?", + "render": "Der Automat prägt {coin:type} Cent Münzen." }, "designs": { "freeform": { @@ -4294,35 +4309,49 @@ }, "mappings": { "0": { - "then": "Die Münzpresse hat ein Motiv zur Auswahl." + "then": "Der Prägeautomat hat ein Motiv zur Auswahl." }, "1": { - "then": "Die Münzpresse hat zwei Motive zur Auswahl." + "then": "Der Prägeautomat hat zwei Motive zur Auswahl." }, "2": { - "then": "Die Münzpresse hat drei Motive zur Auswahl." + "then": "Der Prägeautomat hat drei Motive zur Auswahl." }, "3": { - "then": "Die Münzpresse hat vier Motive zur Auswahl." + "then": "Der Prägeautomat hat vier Motive zur Auswahl." } }, "question": "Wieviele Motive sind verfügbar?", - "render": "Die Münzpresse hat {coin:design_count} Motive zur Auswahl." + "render": "Der Prägeautomat hat {coin:design_count} Motive zur Auswahl." + }, + "fee": { + "mappings": { + "0": { + "then": "Das Prägen ist kostenpflichtig." + }, + "1": { + "then": "Das Prägen ist kostenpflichtig." + }, + "2": { + "then": "Das Prägen ist kostenlos." + } + }, + "question": "Ist das Prägen kostenpflichtig?" }, "indoor": { "mappings": { "0": { - "then": "Die Münzpresse befindet sich im Inneren." + "then": "Der Prägeautomat befindet sich im Inneren." }, "1": { - "then": "Die Münzpresse befindet sich Draußen." + "then": "Der Prägeautomat befindet sich im Freien." } }, - "question": "Befindet sich die Münzpresse im Inneren?" + "question": "Befindet sich der Prägeautomat im Inneren?" } }, "title": { - "render": "Münzpresse" + "render": "Prägeautomat" } }, "entrance": { @@ -4721,30 +4750,6 @@ "1": { "then": "Die Fitness-Station hat ein Schild mit Anweisungen für eine bestimmte Übung." }, - "2": { - "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." - }, - "3": { - "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." - }, - "4": { - "then": "Die Fitness-Station hat Stangen zum Dehnen." - }, - "5": { - "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." - }, - "6": { - "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." - }, - "7": { - "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." - }, - "8": { - "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." - }, - "9": { - "then": "Die Fitness-Station hat Pfosten für Slalomübungen." - }, "10": { "then": "Die Fitness-Station hat Trittsteine." }, @@ -4775,6 +4780,9 @@ "19": { "then": "Die Fitness-Station hat Kampfseile (battle ropes)." }, + "2": { + "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." + }, "20": { "then": "Die Fitness-Station hat ein Fahrradergometer." }, @@ -4789,6 +4797,27 @@ }, "24": { "then": "Die Fitness-Station hat eine Slackline." + }, + "3": { + "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." + }, + "4": { + "then": "Die Fitness-Station hat Stangen zum Dehnen." + }, + "5": { + "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." + }, + "6": { + "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." + }, + "7": { + "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." + }, + "8": { + "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." + }, + "9": { + "then": "Die Fitness-Station hat Pfosten für Slalomübungen." } }, "question": "Welche Übungsgeräte gibt es an dieser Fitness-Station?" @@ -4898,6 +4927,21 @@ "1": { "then": "Dies ist eine Pommesbude" }, + "10": { + "then": "Hier werden chinesische Gerichte serviert" + }, + "11": { + "then": "Hier werden griechische Gerichte serviert" + }, + "12": { + "then": "Hier werden indische Gerichte serviert" + }, + "13": { + "then": "Hier werden türkische Gerichte serviert" + }, + "14": { + "then": "Hier werden thailändische Gerichte serviert" + }, "2": { "then": "Bietet vorwiegend Pastagerichte an" }, @@ -4921,21 +4965,6 @@ }, "9": { "then": "Hier werden französische Gerichte serviert" - }, - "10": { - "then": "Hier werden chinesische Gerichte serviert" - }, - "11": { - "then": "Hier werden griechische Gerichte serviert" - }, - "12": { - "then": "Hier werden indische Gerichte serviert" - }, - "13": { - "then": "Hier werden türkische Gerichte serviert" - }, - "14": { - "then": "Hier werden thailändische Gerichte serviert" } }, "question": "Was für Essen gibt es hier?", @@ -6154,6 +6183,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Alle Notizen" + }, + "1": { + "question": "Importnotizen ausblenden" + }, + "2": { + "question": "Nur Importnotizen anzeigen" + } + } + }, "2": { "options": { "0": { @@ -6209,19 +6251,6 @@ "question": "Nur offene Notizen anzeigen" } } - }, - "10": { - "options": { - "0": { - "question": "Alle Notizen" - }, - "1": { - "question": "Importnotizen ausblenden" - }, - "2": { - "question": "Nur Importnotizen anzeigen" - } - } } }, "name": "OpenStreetMap-Hinweise", @@ -6550,6 +6579,21 @@ "1": { "then": "Dies ist ein normaler Stellplatz." }, + "10": { + "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." + }, + "11": { + "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." + }, + "12": { + "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." + }, + "13": { + "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." + }, + "14": { + "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." + }, "2": { "then": "Dies ist ein Behindertenstellplatz." }, @@ -6573,21 +6617,6 @@ }, "9": { "then": "Dies ist ein Stellplatz, der für Motorräder reserviert ist." - }, - "10": { - "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." - }, - "11": { - "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." - }, - "12": { - "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." - }, - "13": { - "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." - }, - "14": { - "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." } }, "question": "Welche Art von Stellplatz ist dies?" @@ -7600,30 +7629,6 @@ "1": { "question": "Recycling von Batterien" }, - "2": { - "question": "Recycling von Getränkekartons" - }, - "3": { - "question": "Recycling von Dosen" - }, - "4": { - "question": "Recycling von Kleidung" - }, - "5": { - "question": "Recycling von Speiseöl" - }, - "6": { - "question": "Recycling von Motoröl" - }, - "7": { - "question": "Recycling von Leuchtstoffröhren" - }, - "8": { - "question": "Recycling von Grünabfällen" - }, - "9": { - "question": "Recycling von Glasflaschen" - }, "10": { "question": "Recycling von Glas" }, @@ -7654,11 +7659,35 @@ "19": { "question": "Recycling von Restabfällen" }, + "2": { + "question": "Recycling von Getränkekartons" + }, "20": { "question": "Recycling von Druckerpatronen" }, "21": { "question": "Recycling von Fahrrädern" + }, + "3": { + "question": "Recycling von Dosen" + }, + "4": { + "question": "Recycling von Kleidung" + }, + "5": { + "question": "Recycling von Speiseöl" + }, + "6": { + "question": "Recycling von Motoröl" + }, + "7": { + "question": "Recycling von Leuchtstoffröhren" + }, + "8": { + "question": "Recycling von Grünabfällen" + }, + "9": { + "question": "Recycling von Glasflaschen" } } }, @@ -7726,30 +7755,6 @@ "1": { "then": "Getränkekartons können hier recycelt werden" }, - "2": { - "then": "Dosen können hier recycelt werden" - }, - "3": { - "then": "Kleidung kann hier recycelt werden" - }, - "4": { - "then": "Speiseöl kann hier recycelt werden" - }, - "5": { - "then": "Motoröl kann hier recycelt werden" - }, - "6": { - "then": "Hier können Leuchtstoffröhren recycelt werden" - }, - "7": { - "then": "Grünabfälle können hier recycelt werden" - }, - "8": { - "then": "Bio-Abfall kann hier recycelt werden" - }, - "9": { - "then": "Glasflaschen können hier recycelt werden" - }, "10": { "then": "Glas kann hier recycelt werden" }, @@ -7780,6 +7785,9 @@ "19": { "then": "Schuhe können hier recycelt werden" }, + "2": { + "then": "Dosen können hier recycelt werden" + }, "20": { "then": "Elektrokleingeräte können hier recycelt werden" }, @@ -7794,6 +7802,27 @@ }, "24": { "then": "Fahrräder können hier recycelt werden" + }, + "3": { + "then": "Kleidung kann hier recycelt werden" + }, + "4": { + "then": "Speiseöl kann hier recycelt werden" + }, + "5": { + "then": "Motoröl kann hier recycelt werden" + }, + "6": { + "then": "Hier können Leuchtstoffröhren recycelt werden" + }, + "7": { + "then": "Grünabfälle können hier recycelt werden" + }, + "8": { + "then": "Bio-Abfall kann hier recycelt werden" + }, + "9": { + "then": "Glasflaschen können hier recycelt werden" } }, "question": "Was kann hier recycelt werden?" @@ -8597,6 +8626,12 @@ "1": { "then": "Diese Straßenlaterne verwendet LEDs" }, + "10": { + "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" + }, + "11": { + "then": "Diese Straßenlaterne wird mit Gas beleuchtet" + }, "2": { "then": "Diese Straßenlaterne verwendet Glühlampenlicht" }, @@ -8620,12 +8655,6 @@ }, "9": { "then": "Diese Straßenlaterne verwendet Niederdruck-Natriumdampflampen (einfarbig orange)" - }, - "10": { - "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" - }, - "11": { - "then": "Diese Straßenlaterne wird mit Gas beleuchtet" } }, "question": "Mit welcher Art von Beleuchtung arbeitet diese Straßenlaterne?" @@ -9719,6 +9748,27 @@ "1": { "question": "Verkauf von Getränken" }, + "10": { + "question": "Verkauf von Milch" + }, + "11": { + "question": "Verkauf von Brot" + }, + "12": { + "question": "Verkauf von Eiern" + }, + "13": { + "question": "Verkauf von Käse" + }, + "14": { + "question": "Verkauf von Honig" + }, + "15": { + "question": "Verkauf von Kartoffeln" + }, + "16": { + "question": "Verkauf von Blumen" + }, "2": { "question": "Verkauf von Süßigkeiten" }, @@ -9742,27 +9792,6 @@ }, "9": { "question": "Verkauf von Fahrradschläuchen" - }, - "10": { - "question": "Verkauf von Milch" - }, - "11": { - "question": "Verkauf von Brot" - }, - "12": { - "question": "Verkauf von Eiern" - }, - "13": { - "question": "Verkauf von Käse" - }, - "14": { - "question": "Verkauf von Honig" - }, - "15": { - "question": "Verkauf von Kartoffeln" - }, - "16": { - "question": "Verkauf von Blumen" } } } @@ -9803,30 +9832,6 @@ "1": { "then": "Süßigkeiten werden verkauft" }, - "2": { - "then": "Lebensmittel werden verkauft" - }, - "3": { - "then": "Zigaretten werden verkauft" - }, - "4": { - "then": "Kondome werden verkauft" - }, - "5": { - "then": "Kaffee wird verkauft" - }, - "6": { - "then": "Trinkwasser wird verkauft" - }, - "7": { - "then": "Zeitungen werden verkauft" - }, - "8": { - "then": "Fahrradschläuche werden verkauft" - }, - "9": { - "then": "Milch wird verkauft" - }, "10": { "then": "Brot wird verkauft" }, @@ -9850,6 +9855,30 @@ }, "18": { "then": "Fahrscheine werden verkauft" + }, + "2": { + "then": "Lebensmittel werden verkauft" + }, + "3": { + "then": "Zigaretten werden verkauft" + }, + "4": { + "then": "Kondome werden verkauft" + }, + "5": { + "then": "Kaffee wird verkauft" + }, + "6": { + "then": "Trinkwasser wird verkauft" + }, + "7": { + "then": "Zeitungen werden verkauft" + }, + "8": { + "then": "Fahrradschläuche werden verkauft" + }, + "9": { + "then": "Milch wird verkauft" } }, "question": "Was wird in diesem Automaten verkauft?", @@ -10186,4 +10215,4 @@ } } } -} \ No newline at end of file +} From 9df3e35f8c3f5a3689fd4100d47005d4ad07e775 Mon Sep 17 00:00:00 2001 From: paunofu Date: Wed, 27 Sep 2023 10:04:54 +0000 Subject: [PATCH 098/133] Translated using Weblate (Spanish) Currently translated at 44.8% (1412 of 3148 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/es/ --- langs/layers/es.json | 299 +++++++++++++++++++++---------------------- 1 file changed, 147 insertions(+), 152 deletions(-) diff --git a/langs/layers/es.json b/langs/layers/es.json index 96bab0796..6fd2c0855 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un lletrer" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" + }, "2": { "title": "un mupi sobre la pared" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tótem" - }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un lletrer" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Esto es un tablón de anuncios" }, + "10": { + "then": "Esto es una pared pintada" + }, "2": { "then": "Esto es una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Esto es un tótem" - }, - "10": { - "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,6 +205,9 @@ "1": { "then": "Tablon de anuncios" }, + "10": { + "then": "Pared Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tótem" - }, - "10": { - "then": "Pared Pintada" } } } @@ -312,6 +312,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" + }, + "12": { + "then": "Tallado en madera" + }, "2": { "then": "Pintura" }, @@ -335,15 +344,6 @@ }, "9": { "then": "Relieve" - }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, - "12": { - "then": "Tallado en madera" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1440,6 +1440,27 @@ "0": { "question": "Todos los conectores" }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" + }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E)
" }, @@ -1463,27 +1484,6 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" - }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1538,30 +1538,6 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" - }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1592,6 +1568,9 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1622,11 +1601,32 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -2021,6 +2021,12 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2035,12 +2041,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2086,6 +2086,9 @@ "1": { "then": "Este carril bici está pavimentado" }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2097,9 +2100,6 @@ }, "9": { "then": "Este carril bici está hecho de grava" - }, - "10": { - "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -2710,6 +2710,18 @@ "0": { "then": "Esto es una pizzería" }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" + }, "2": { "then": "Principalmente sirve pasta" }, @@ -2730,18 +2742,6 @@ }, "9": { "then": "Aquí se sirven platos Franceses" - }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué comida se sirve aquí?", @@ -3139,6 +3139,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las notas de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } + }, "2": { "options": { "0": { @@ -3194,19 +3207,6 @@ "question": "Solo mostrar las notas abiertas" } } - }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las notas de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } } }, "name": "Notas de OpenStreetMap", @@ -3440,7 +3440,7 @@ } }, "postoffices": { - "description": "Una capa que muestra oficinas de correo.", + "description": "Una capa que muestra oficinas postales.", "name": "Oficinas de correo", "presets": { "0": { @@ -3450,7 +3450,7 @@ "tagRenderings": { "opening_hours": { "override": { - "question": "¿Cuáles son las horas de apertura para esta oficina de correos?" + "question": "¿Cuáles son las horas de apertura para esta oficina postal?" } } }, @@ -3822,21 +3822,6 @@ "1": { "question": "Reciclaje de baterías" }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "9": { - "question": "Reciclaje de botellas de cristal" - }, "10": { "question": "Reciclaje de cristal" }, @@ -3860,6 +3845,21 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" + }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "9": { + "question": "Reciclaje de botellas de cristal" } } } @@ -3902,24 +3902,6 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" - }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -3943,6 +3925,24 @@ }, "19": { "then": "Aquí se pueden reciclar zapatos" + }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -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}" @@ -4291,6 +4286,12 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" + }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -4311,12 +4312,6 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" - }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -4708,7 +4703,7 @@ "then": "El árbol está en un jardín privado o residencial." }, "5": { - "then": "El árbol está en bandejón de una avenida." + "then": "Este es un árbol a lo largo de una avenida." }, "6": { "then": "El árbol está en un zona urbana." @@ -4891,4 +4886,4 @@ } } } -} \ No newline at end of file +} From 8eabe873b03cecde18edc39d44027e622cfbce56 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Sep 2023 14:41:23 +0000 Subject: [PATCH 099/133] Translated using Weblate (Dutch) Currently translated at 89.6% (2823 of 3148 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/nl/ --- langs/layers/nl.json | 496 ++++++++++++++++++++++--------------------- 1 file changed, 250 insertions(+), 246 deletions(-) diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 460b43f84..391c14452 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -27,6 +27,9 @@ "advertising": { "name": "Reclame", "presets": { + "12": { + "title": "een muurschildering" + }, "3": { "description": "Een klein uithangbord voor buurtadvertenties, meestal gericht op voetgangers", "title": "een uithangbord" @@ -47,9 +50,6 @@ "8": { "description": "Een stuk groot, weerbestendig textiel met opgedrukte reclameboodschap die permanent aan de muur hangt", "title": "een spandoek" - }, - "12": { - "title": "een muurschildering" } }, "tagRenderings": { @@ -107,6 +107,9 @@ }, "title": { "mappings": { + "10": { + "then": "Muurschildering" + }, "3": { "then": "Aanplakzuil" }, @@ -124,9 +127,6 @@ }, "9": { "then": "Aanplakzuil" - }, - "10": { - "then": "Muurschildering" } } } @@ -208,6 +208,15 @@ "1": { "then": "Muurschildering" }, + "10": { + "then": "Azulejo (Spaanse siertegels)" + }, + "11": { + "then": "Tegelwerk" + }, + "12": { + "then": "Houtsculptuur" + }, "2": { "then": "Schilderij" }, @@ -231,15 +240,6 @@ }, "9": { "then": "Reliëf" - }, - "10": { - "then": "Azulejo (Spaanse siertegels)" - }, - "11": { - "then": "Tegelwerk" - }, - "12": { - "then": "Houtsculptuur" } }, "question": "Wat voor soort kunstwerk is dit?", @@ -1736,6 +1736,27 @@ "1": { "question": "Heeft een
Schuko stekker zonder aardingspin (CEE7/4 type F)
" }, + "10": { + "question": "Heeft een
Type 2 met kabel (J1772)
" + }, + "11": { + "question": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "12": { + "question": "Heeft een
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Heeft een
Tesla supercharger (destination) (Een Type 2 met kabel en Tesla-logo)
" + }, + "14": { + "question": "Heeft een
USB om GSMs en kleine electronica op te laden
" + }, + "15": { + "question": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "16": { + "question": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" + }, "2": { "question": "Heeft een
Europese stekker met aardingspin (CEE7/4 type E)
" }, @@ -1759,27 +1780,6 @@ }, "9": { "question": "Heeft een
Type 2 CCS (mennekes)
" - }, - "10": { - "question": "Heeft een
Type 2 met kabel (J1772)
" - }, - "11": { - "question": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "12": { - "question": "Heeft een
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Heeft een
Tesla supercharger (destination) (Een Type 2 met kabel en Tesla-logo)
" - }, - "14": { - "question": "Heeft een
USB om GSMs en kleine electronica op te laden
" - }, - "15": { - "question": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "16": { - "question": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" } } } @@ -1835,30 +1835,6 @@ "1": { "then": "Schuko stekker zonder aardingspin (CEE7/4 type F)" }, - "2": { - "then": "Europese stekker met aardingspin (CEE7/4 type E)" - }, - "3": { - "then": "Europese stekker met aardingspin (CEE7/4 type E)" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Type 1 met kabel (J1772)" - }, - "7": { - "then": "Type 1 met kabel (J1772)" - }, - "8": { - "then": "Type 1 zonder kabel (J1772)" - }, - "9": { - "then": "Type 1 zonder kabel (J1772)" - }, "10": { "then": "Type 1 CCS (ook gekend als Type 1 Combo)" }, @@ -1889,6 +1865,9 @@ "19": { "then": "Type 2 met kabel (J1772)" }, + "2": { + "then": "Europese stekker met aardingspin (CEE7/4 type E)" + }, "20": { "then": "Tesla Supercharger CCS (een type2 CCS met Tesla-logo)" }, @@ -1919,11 +1898,32 @@ "29": { "then": "Bosch Active Connect met 3 pinnen aan een kabel" }, + "3": { + "then": "Europese stekker met aardingspin (CEE7/4 type E)" + }, "30": { "then": "Bosch Active Connect met 5 pinnen aan een kabel" }, "31": { "then": "Bosch Active Connect met 5 pinnen aan een kabel" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Type 1 met kabel (J1772)" + }, + "7": { + "then": "Type 1 met kabel (J1772)" + }, + "8": { + "then": "Type 1 zonder kabel (J1772)" + }, + "9": { + "then": "Type 1 zonder kabel (J1772)" } }, "question": "Welke laadaansluitingen zijn hier beschikbaar?" @@ -3456,6 +3456,15 @@ "1": { "then": "Dit fietspad is geplaveid" }, + "10": { + "then": "Dit fietspad is gemaakt van fijn grind" + }, + "11": { + "then": "Dit fietspad is gemaakt van kiezelsteentjes" + }, + "12": { + "then": "Dit fietspad is gemaakt van aarde" + }, "2": { "then": "Dit fietspad is gemaakt van asfalt" }, @@ -3479,15 +3488,6 @@ }, "9": { "then": "Dit fietspad is gemaakt van grind" - }, - "10": { - "then": "Dit fietspad is gemaakt van fijn grind" - }, - "11": { - "then": "Dit fietspad is gemaakt van kiezelsteentjes" - }, - "12": { - "then": "Dit fietspad is gemaakt van aarde" } }, "question": "Waaruit is het oppervlak van het fietspad van gemaakt?", @@ -3536,6 +3536,15 @@ "1": { "then": "Dit fietspad is geplaveid" }, + "10": { + "then": "Dit fietspad is gemaakt van fijn grind" + }, + "11": { + "then": "Dit fietspad is gemaakt van kiezelsteentjes" + }, + "12": { + "then": "Dit fietspad is gemaakt van aarde" + }, "2": { "then": "Dit fietspad is gemaakt van asfalt" }, @@ -3559,15 +3568,6 @@ }, "9": { "then": "Dit fietspad is gemaakt van grind" - }, - "10": { - "then": "Dit fietspad is gemaakt van fijn grind" - }, - "11": { - "then": "Dit fietspad is gemaakt van kiezelsteentjes" - }, - "12": { - "then": "Dit fietspad is gemaakt van aarde" } }, "question": "Waaruit is het oppervlak van de straat gemaakt?", @@ -4572,6 +4572,21 @@ "1": { "then": "Dit is een frituur" }, + "10": { + "then": "Dit is een Chinees restaurant" + }, + "11": { + "then": "Dit is een Grieks restaurant" + }, + "12": { + "then": "Dit is een Indisch restaurant" + }, + "13": { + "then": "Dit is een Turks restaurant (dat meer dan enkel kebab verkoopt)" + }, + "14": { + "then": "Dit is een Thaïs restaurant" + }, "2": { "then": "Dit is een pastazaak" }, @@ -4595,21 +4610,6 @@ }, "9": { "then": "Dit is een Frans restaurant" - }, - "10": { - "then": "Dit is een Chinees restaurant" - }, - "11": { - "then": "Dit is een Grieks restaurant" - }, - "12": { - "then": "Dit is een Indisch restaurant" - }, - "13": { - "then": "Dit is een Turks restaurant (dat meer dan enkel kebab verkoopt)" - }, - "14": { - "then": "Dit is een Thaïs restaurant" } }, "question": "Welk soort gerechten worden hier geserveerd?", @@ -5744,6 +5744,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Alle Notes" + }, + "1": { + "question": "Verberg import Notes" + }, + "2": { + "question": "Toon enkel import Notes" + } + } + }, "2": { "options": { "0": { @@ -5799,19 +5812,6 @@ "question": "Toon enkel open Notes" } } - }, - "10": { - "options": { - "0": { - "question": "Alle Notes" - }, - "1": { - "question": "Verberg import Notes" - }, - "2": { - "question": "Toon enkel import Notes" - } - } } }, "name": "OpenStreetMap Notes", @@ -6107,6 +6107,21 @@ "1": { "then": "Dit is een normale parkeerplek." }, + "10": { + "then": "Deze parkeerplek is gereserveerd voor ouders met kinderen." + }, + "11": { + "then": "Deze parkeerplek is gereserveerd voor personeel." + }, + "12": { + "then": "Deze parkeerplek is gereserveerd voor taxis." + }, + "13": { + "then": "Deze parkeerplek is gereserveerd voor voertuigen met een aanhanger." + }, + "14": { + "then": "Deze parkeerplek is gereserveerd voor autodelen." + }, "2": { "then": "Dit is een gehandicaptenparkeerplaats." }, @@ -6130,21 +6145,6 @@ }, "9": { "then": "Deze parkeerplek is gereserveerd voor motoren." - }, - "10": { - "then": "Deze parkeerplek is gereserveerd voor ouders met kinderen." - }, - "11": { - "then": "Deze parkeerplek is gereserveerd voor personeel." - }, - "12": { - "then": "Deze parkeerplek is gereserveerd voor taxis." - }, - "13": { - "then": "Deze parkeerplek is gereserveerd voor voertuigen met een aanhanger." - }, - "14": { - "then": "Deze parkeerplek is gereserveerd voor autodelen." } }, "question": "Wat voor parkeerplek is dit?" @@ -6705,6 +6705,21 @@ "1": { "then": "Munten van 2 cent worden geaccepteerd" }, + "10": { + "then": "Munten van 20 rappen worden geaccepteerd" + }, + "11": { + "then": "Munten van ½ frank worden geaccepteerd" + }, + "12": { + "then": "Munten van 1 frank worden geaccepteerd" + }, + "13": { + "then": "Munten van 2 frank worden geaccepteerd" + }, + "14": { + "then": "Munten van 5 frank worden geaccepteerd" + }, "2": { "then": "Munten van 5 cent worden geaccepteerd" }, @@ -6728,21 +6743,6 @@ }, "9": { "then": "Munten van 10 rappen worden geaccepteerd" - }, - "10": { - "then": "Munten van 20 rappen worden geaccepteerd" - }, - "11": { - "then": "Munten van ½ frank worden geaccepteerd" - }, - "12": { - "then": "Munten van 1 frank worden geaccepteerd" - }, - "13": { - "then": "Munten van 2 frank worden geaccepteerd" - }, - "14": { - "then": "Munten van 5 frank worden geaccepteerd" } }, "question": "Met welke munten kan je hier betalen?" @@ -6755,6 +6755,15 @@ "1": { "then": "Biljetten van 10 euro worden geaccepteerd" }, + "10": { + "then": "Biljetten van 100 frank worden geaccepteerd" + }, + "11": { + "then": "Biljetten van 200 frank worden geaccepteerd" + }, + "12": { + "then": "Biljetten van 1000 frank worden geaccepteerd" + }, "2": { "then": "Biljetten van 20 euro worden geaccepteerd" }, @@ -6778,15 +6787,6 @@ }, "9": { "then": "Biljetten van 50 frank worden geaccepteerd" - }, - "10": { - "then": "Biljetten van 100 frank worden geaccepteerd" - }, - "11": { - "then": "Biljetten van 200 frank worden geaccepteerd" - }, - "12": { - "then": "Biljetten van 1000 frank worden geaccepteerd" } }, "question": "Met welke bankbiljetten kan je hier betalen?" @@ -7101,30 +7101,6 @@ "1": { "question": "Recycling van batterijen" }, - "2": { - "question": "Recycling van drankpakken" - }, - "3": { - "question": "Recycling van blikken" - }, - "4": { - "question": "Recycling van kleding" - }, - "5": { - "question": "Recycling van frituurvet" - }, - "6": { - "question": "Recycling van motorolie" - }, - "7": { - "question": "Recycling van tl-buizen" - }, - "8": { - "question": "Recycling van groen afval" - }, - "9": { - "question": "Recycling van glazen flessen" - }, "10": { "question": "Recycling van glas" }, @@ -7155,11 +7131,35 @@ "19": { "question": "Recycling van restafval" }, + "2": { + "question": "Recycling van drankpakken" + }, "20": { "question": "Recycling van inktpatronen" }, "21": { "question": "Recycling van fietsen" + }, + "3": { + "question": "Recycling van blikken" + }, + "4": { + "question": "Recycling van kleding" + }, + "5": { + "question": "Recycling van frituurvet" + }, + "6": { + "question": "Recycling van motorolie" + }, + "7": { + "question": "Recycling van tl-buizen" + }, + "8": { + "question": "Recycling van groen afval" + }, + "9": { + "question": "Recycling van glazen flessen" } } }, @@ -7227,30 +7227,6 @@ "1": { "then": "Drankpakken kunnen hier gerecycled worden" }, - "2": { - "then": "Blikken kunnen hier gerecycled worden" - }, - "3": { - "then": "Kleren kunnen hier gerecycled worden" - }, - "4": { - "then": "Frituurvet kan hier gerecycled worden" - }, - "5": { - "then": "Motorolie kan hier gerecycled worden" - }, - "6": { - "then": "TL-buizen kunnen hier gerecycled worden" - }, - "7": { - "then": "Groen afval kan hier gerecycled worden" - }, - "8": { - "then": "Organisch afval kan hier gerecycled worden" - }, - "9": { - "then": "Glazen flessen kunnen hier gerecycled worden" - }, "10": { "then": "Glas kan hier gerecycled worden" }, @@ -7281,6 +7257,9 @@ "19": { "then": "Schoenen kunnen hier gerecycled worden" }, + "2": { + "then": "Blikken kunnen hier gerecycled worden" + }, "20": { "then": "Kleine elektrische apparaten kunnen hier gerecycled worden" }, @@ -7295,6 +7274,27 @@ }, "24": { "then": "Fietsen (en fietswrakken) kunnen hier gerecycled worden" + }, + "3": { + "then": "Kleren kunnen hier gerecycled worden" + }, + "4": { + "then": "Frituurvet kan hier gerecycled worden" + }, + "5": { + "then": "Motorolie kan hier gerecycled worden" + }, + "6": { + "then": "TL-buizen kunnen hier gerecycled worden" + }, + "7": { + "then": "Groen afval kan hier gerecycled worden" + }, + "8": { + "then": "Organisch afval kan hier gerecycled worden" + }, + "9": { + "then": "Glazen flessen kunnen hier gerecycled worden" } }, "question": "Wat kan hier gerecycled worden?" @@ -8021,6 +8021,12 @@ "1": { "then": "Deze lantaarn gebruikt LEDs" }, + "10": { + "then": "Deze lantaarn gebruikt hogedruknatriumlampen (oranje met wit)" + }, + "11": { + "then": "Deze lantaarn wordt verlicht met gas" + }, "2": { "then": "Deze lantaarn gebruikt gloeilampen" }, @@ -8044,12 +8050,6 @@ }, "9": { "then": "Deze lantaarn gebruikt lagedruknatriumlampen (monochroom oranje)" - }, - "10": { - "then": "Deze lantaarn gebruikt hogedruknatriumlampen (oranje met wit)" - }, - "11": { - "then": "Deze lantaarn wordt verlicht met gas" } }, "question": "Wat voor verlichting gebruikt deze lantaarn?" @@ -8914,10 +8914,14 @@ "tagRenderings": { "all-questions-at-once": { "mappings": { + "0": { + "then": "Toon alle onbeantwoorde vragen" + }, "1": { "then": "Toon de vragen één per één" } - } + }, + "question": "Moeten onbeantwoorde vragen om beurt of allemaal samen getoond worden?" }, "contributor-thanks": { "mappings": { @@ -9009,6 +9013,27 @@ "1": { "question": "Verkoop van dranken" }, + "10": { + "question": "Verkoop van melk" + }, + "11": { + "question": "Verkoop van brood" + }, + "12": { + "question": "Verkoop van eieren" + }, + "13": { + "question": "Verkoop van kaas" + }, + "14": { + "question": "Verkoop van honing" + }, + "15": { + "question": "Verkoop van aardappelen" + }, + "16": { + "question": "Verkoop van bloemen" + }, "2": { "question": "Verkoop van snoep" }, @@ -9032,27 +9057,6 @@ }, "9": { "question": "Verkoop van fietsbinnenbanden" - }, - "10": { - "question": "Verkoop van melk" - }, - "11": { - "question": "Verkoop van brood" - }, - "12": { - "question": "Verkoop van eieren" - }, - "13": { - "question": "Verkoop van kaas" - }, - "14": { - "question": "Verkoop van honing" - }, - "15": { - "question": "Verkoop van aardappelen" - }, - "16": { - "question": "Verkoop van bloemen" } } } @@ -9093,30 +9097,6 @@ "1": { "then": "Snoep wordt verkocht" }, - "2": { - "then": "Eten wordt verkocht" - }, - "3": { - "then": "Sigaretten worden verkocht" - }, - "4": { - "then": "Condooms worden verkocht" - }, - "5": { - "then": "Koffie wordt verkocht" - }, - "6": { - "then": "Drinkwater wordt verkocht" - }, - "7": { - "then": "Kranten worden verkocht" - }, - "8": { - "then": "Binnenbanden voor fietsen worden verkocht" - }, - "9": { - "then": "Melk wordt verkocht" - }, "10": { "then": "Brood wordt verkocht" }, @@ -9143,6 +9123,30 @@ }, "19": { "then": "Vleesproducten worden hier verkocht" + }, + "2": { + "then": "Eten wordt verkocht" + }, + "3": { + "then": "Sigaretten worden verkocht" + }, + "4": { + "then": "Condooms worden verkocht" + }, + "5": { + "then": "Koffie wordt verkocht" + }, + "6": { + "then": "Drinkwater wordt verkocht" + }, + "7": { + "then": "Kranten worden verkocht" + }, + "8": { + "then": "Binnenbanden voor fietsen worden verkocht" + }, + "9": { + "then": "Melk wordt verkocht" } }, "question": "Wat verkoopt deze verkoopautomaat?", @@ -9480,4 +9484,4 @@ } } } -} \ No newline at end of file +} From afaec5fbbc4087e25de339e3dcf9fd8086e4f77c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 27 Sep 2023 20:38:47 +0000 Subject: [PATCH 100/133] Update translation files Updated by "Remove blank strings" hook in Weblate. Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ --- langs/layers/ca.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index e89503f01..e3906df1d 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -5075,9 +5075,6 @@ "tagRenderings": { "status": { "mappings": { - "0": { - "then": "" - }, "1": { "then": "La tasca està arreglada" }, @@ -5284,13 +5281,6 @@ } } }, - "1": { - "options": { - "0": { - "question": "" - } - } - }, "10": { "options": { "0": { From 33bd8369a2d8b9f48aa7249d5e2d35d38eb01587 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 21:11:22 +0000 Subject: [PATCH 101/133] Chore(deps): bump get-func-name from 2.0.0 to 2.0.2 Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2. - [Release notes](https://github.com/chaijs/get-func-name/releases) - [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2) --- updated-dependencies: - dependency-name: get-func-name dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f6a1ce4f..7422c4260 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mapcomplete", - "version": "0.33.1", + "version": "0.33.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mapcomplete", - "version": "0.33.1", + "version": "0.33.4", "license": "GPL-3.0-or-later", "dependencies": { "@rgossiaux/svelte-headlessui": "^1.0.2", @@ -7169,9 +7169,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "engines": { "node": "*" } @@ -18679,9 +18679,9 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" }, "get-intrinsic": { "version": "1.1.3", From 7e0120d0e8948510db1eb0c17090900127dc8ed8 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 00:01:55 +0200 Subject: [PATCH 102/133] Themes: add dog icon into title-icons, add dog filter, see #212 --- assets/layers/filters/filters.json | 29 ++++++- assets/layers/food/food.json | 3 +- assets/layers/icons/icons.json | 28 ++++++ assets/layers/questions/dogs_allowed.svg | 70 +++++++++++++++ .../layers/questions/dogs_allowed.svg.license | 2 + assets/layers/questions/dogs_leashed.svg | 54 ++++++++++++ .../layers/questions/dogs_leashed.svg.license | 2 + assets/layers/questions/license_info.json | 30 +++++++ assets/layers/questions/no_dogs.svg | 87 +++++++++++++++++++ assets/layers/questions/no_dogs.svg.license | 2 + assets/layers/questions/questions.json | 5 ++ 11 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 assets/layers/questions/dogs_allowed.svg create mode 100644 assets/layers/questions/dogs_allowed.svg.license create mode 100644 assets/layers/questions/dogs_leashed.svg create mode 100644 assets/layers/questions/dogs_leashed.svg.license create mode 100644 assets/layers/questions/no_dogs.svg create mode 100644 assets/layers/questions/no_dogs.svg.license diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 9a390b950..551ef6897 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -244,6 +244,33 @@ } } ] + }, + { + "id": "dogs", + "options": [ + { + "question": { + "en": "No preference towards dogs" + } + }, + { + "question": { + "en": "Dogs allowed" + }, + "osmTags": { + "or": [ + "dog=unleashed", + "dog=yes" + ] + } + }, + { + "question": { + "en": "No dogs allowed" + }, + "osmTags": "dog=no" + } + ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 5d2c36717..443cd8e36 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -1102,7 +1102,8 @@ }, "has_organic", "accepts_cash", - "accepts_cards" + "accepts_cards", + "dogs" ], "deletion": { "nonDeleteMappings": [ diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 2a3304728..9f15ac5dd 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -154,6 +154,34 @@ } ], "condition": "id~(node|way|relation)/[0-9]*" + }, + { + "id": "dogicon", + "labels": [ + "defaults" + ], + "mappings": [ + { + "if": "dog=no", + "#": "ignore-image-in-then", + "then": "no_dogs" + }, + { + "if": "dog=leashed", + "#": "ignore-image-in-then", + "then": "dogs are allowed but leashed" + }, + { + "if": { + "or": [ + "dog=yes", + "dog=unleashed" + ] + }, + "#": "ignore-image-in-then", + "then": "dogs are allowed" + } + ] } ], "mapRendering": null diff --git a/assets/layers/questions/dogs_allowed.svg b/assets/layers/questions/dogs_allowed.svg new file mode 100644 index 000000000..0f47ee8bf --- /dev/null +++ b/assets/layers/questions/dogs_allowed.svg @@ -0,0 +1,70 @@ + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + diff --git a/assets/layers/questions/dogs_allowed.svg.license b/assets/layers/questions/dogs_allowed.svg.license new file mode 100644 index 000000000..e14c126f7 --- /dev/null +++ b/assets/layers/questions/dogs_allowed.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: OpenClipArt +SPDX-License-Identifier: PD \ No newline at end of file diff --git a/assets/layers/questions/dogs_leashed.svg b/assets/layers/questions/dogs_leashed.svg new file mode 100644 index 000000000..d048b5d58 --- /dev/null +++ b/assets/layers/questions/dogs_leashed.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/assets/layers/questions/dogs_leashed.svg.license b/assets/layers/questions/dogs_leashed.svg.license new file mode 100644 index 000000000..e32d67ee6 --- /dev/null +++ b/assets/layers/questions/dogs_leashed.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: NPS Graphics, converted by User:ZyMOS +SPDX-License-Identifier: PD \ No newline at end of file diff --git a/assets/layers/questions/license_info.json b/assets/layers/questions/license_info.json index 65a360bb6..8985470b3 100644 --- a/assets/layers/questions/license_info.json +++ b/assets/layers/questions/license_info.json @@ -39,6 +39,26 @@ "https://www.onlinewebfonts.com/icon/464488" ] }, + { + "path": "dogs_allowed.svg", + "license": "PUBLIC-DOMAIN", + "authors": [ + "OpenClipArt" + ], + "sources": [ + "https://freesvg.org/no-dogs-round-sign-vector-graphics" + ] + }, + { + "path": "dogs_leashed.svg", + "license": "PUBLIC-DOMAIN", + "authors": [ + " \tNPS Graphics, converted by User:ZyMOS" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Pictograms-nps-pets_on_leash-2.svg" + ] + }, { "path": "nfc_card.svg", "license": "CC0-1.0", @@ -49,6 +69,16 @@ "https://wens.be/free-antwerpenize-bicycle-font" ] }, + { + "path": "no_dogs.svg", + "license": "Public Domain", + "authors": [ + "OpenClipArt" + ], + "sources": [ + "https://freesvg.org/no-dogs-round-sign-vector-graphics" + ] + }, { "path": "no_smoking.svg", "license": "CC0-1.0", diff --git a/assets/layers/questions/no_dogs.svg b/assets/layers/questions/no_dogs.svg new file mode 100644 index 000000000..888ae60de --- /dev/null +++ b/assets/layers/questions/no_dogs.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + diff --git a/assets/layers/questions/no_dogs.svg.license b/assets/layers/questions/no_dogs.svg.license new file mode 100644 index 000000000..8fc3e04c7 --- /dev/null +++ b/assets/layers/questions/no_dogs.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: OpenClipArt +SPDX-License-Identifier: Public Domain \ No newline at end of file diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index a3cf36052..473d93f64 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -487,6 +487,7 @@ "mappings": [ { "if": "dog=yes", + "icon": "./assets/layers/questions/dogs_allowed.svg", "then": { "en": "Dogs are allowed", "nl": "honden zijn toegelaten", @@ -515,6 +516,7 @@ }, { "if": "dog=no", + "icon": "./assets/layers/questions/no_dogs.svg", "then": { "en": "Dogs are not allowed", "nl": "honden zijn niet toegelaten", @@ -542,6 +544,7 @@ }, { "if": "dog=leashed", + "icon": "./assets/layers/questions/dogs_leashed.svg", "then": { "en": "Dogs are allowed, but they have to be leashed", "nl": "honden zijn enkel aan de leiband welkom", @@ -568,6 +571,8 @@ }, { "if": "dog=unleashed", + "icon": "./assets/layers/questions/dogs_allowed.svg", + "then": { "en": "Dogs are allowed and can run around freely", "nl": "honden zijn welkom en mogen vrij rondlopen", From b5fca68a82d529bd10f56ba0dd63d27ae11ea2a5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 03:04:25 +0200 Subject: [PATCH 103/133] Themes: fix: don't show irrelevant 'multilevel'-question anymore --- assets/layers/questions/questions.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 473d93f64..748c2221e 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -1626,6 +1626,7 @@ "id": "multilevels", "builtin": "single_level", "override": { + "=labels": [], "question": { "en": "What levels does this elevator go to?", "de": "Auf welchen Geschossen hält dieser Aufzug?", @@ -1663,7 +1664,9 @@ }, { "id": "repeated", - "labels": ["level"], + "labels": [ + "level" + ], "condition": "repeat_on~*", "render": { "en": "Multiple, identical objects can be found on floors {repeat_on}.", @@ -1672,7 +1675,9 @@ }, { "id": "single_level", - "labels": ["level"], + "labels": [ + "level" + ], "condition": "repeat_on=", "question": { "nl": "Op welke verdieping bevindt dit punt zich?", From 47bf1195bcc198446c35b31ebd7d446efde7dd9d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 03:05:00 +0200 Subject: [PATCH 104/133] Themes: improve information about indoor rooms --- assets/layers/etymology/etymology.json | 1 + assets/layers/indoors/indoors.json | 217 ++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 2 deletions(-) diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json index 17bc04049..aee734eda 100644 --- a/assets/layers/etymology/etymology.json +++ b/assets/layers/etymology/etymology.json @@ -46,6 +46,7 @@ }, { "id": "wikipedia-etymology", + "condition": "name~*", "question": { "en": "What is the Wikidata-item that this object is named after?", "nl": "Wat is het Wikidata-item van hetgeen dit object is naar vernoemd?", diff --git a/assets/layers/indoors/indoors.json b/assets/layers/indoors/indoors.json index d9aabbd81..f683eac31 100644 --- a/assets/layers/indoors/indoors.json +++ b/assets/layers/indoors/indoors.json @@ -89,9 +89,25 @@ } ] }, + "titleIcons": [ + "icons.defaults", + { + "render": "{ref}", + "condition": "ref~*" + }, + { + "mappings": [ + { + "if": "capacity~*", + "then": "
{capacity}
" + } + ] + } + ], "minzoom": 13, "tagRenderings": [ "images", + "level", { "id": "ref", "question": { @@ -162,7 +178,204 @@ "indoor=corridor" ] } - } + }, + { + "id": "room-type", + "question": { + "en": "What type of room is this?" + }, + "mappings": [ + { + "if": "room=administration", + "then": { + "en": "This is a administrative room" + }, + "icon": "./assets/layers/indoors/room_administration.svg" + }, + { + "if": "room=auditorium", + "then": { + "en": "This is a auditorium" + }, + "icon": "./assets/layers/indoors/room_auditorium.svg" + }, + { + "if": "room=bedroom", + "then": { + "en": "This is a bedroom" + }, + "icon": "./assets/layers/indoors/room_bedroom.svg" + }, + { + "if": "room=chapel", + "then": { + "en": "This is a chapel" + }, + "icon": "./assets/layers/indoors/room_chapel.svg" + }, + { + "if": "room=class", + "then": { + "en": "This is a classroom" + }, + "icon": "./assets/layers/indoors/room_class.svg" + }, + { + "if": "room=classroom", + "then": { + "en": "This is a classroom" + }, + "icon": "./assets/layers/indoors/room_class.svg", + "hideInAnswer": true + }, + { + "if": "room=computer", + "then": { + "en": "This is a computer room" + }, + "icon": "./assets/layers/indoors/room_computer.svg" + }, + { + "if": "room=conference", + "then": { + "en": "This is a conference room" + }, + "icon": "./assets/layers/indoors/room_conference.svg" + }, + { + "if": "room=crypt", + "then": { + "en": "This is a crypt" + }, + "icon": "./assets/layers/indoors/room_crypt.svg" + }, + { + "if": "room=kitchen", + "then": { + "en": "This is a kitchen" + }, + "icon": "./assets/layers/indoors/room_kitchen.svg" + }, + { + "if": "room=laboratory", + "then": { + "en": "This is a laboratory" + }, + "icon": "./assets/layers/indoors/room_laboratory.svg" + }, + { + "if": "room=library", + "then": { + "en": "This is a library" + }, + "icon": "./assets/layers/indoors/room_library.svg" + }, + { + "if": "room=locker", + "then": { + "en": "This is a locker room" + }, + "icon": "./assets/layers/indoors/room_locker.svg" + }, + { + "if": "room=nursery", + "then": { + "en": "This is a nursery" + }, + "icon": "./assets/layers/indoors/room_nursery.svg" + }, + { + "if": "room=office", + "then": { + "en": "This is an office" + }, + "icon": "./assets/layers/indoors/room_office.svg" + }, + { + "if": "room=prison_cell", + "then": { + "en": "This is a prison_cell" + }, + "icon": "./assets/layers/indoors/room_prison_cell.svg" + }, + { + "if": "room=restaurant", + "then": { + "en": "This is a restaurant" + }, + "icon": "./assets/layers/indoors/room_restaurant.svg" + }, + { + "if": "room=security_check", + "then": { + "en": "This is a room to perform security checks" + }, + "icon": "./assets/layers/indoors/room_security_check.svg" + }, + { + "if": "room=sport", + "then": { + "en": "This is a sport room" + }, + "icon": "./assets/layers/indoors/room_sport.svg" + }, + { + "if": "room=storage", + "then": { + "en": "This is a storage room" + }, + "icon": "./assets/layers/indoors/room_storage.svg" + }, + { + "if": "room=technical", + "then": { + "en": "This is a technical room" + }, + "icon": "./assets/layers/indoors/room_technical.svg" + }, + { + "if": "room=toilets", + "then": { + "en": "These are toilets" + }, + "icon": "./assets/layers/indoors/room_toilets.svg" + }, + { + "if": "room=waiting", + "then": { + "en": "This is a waiting room" + }, + "icon": "./assets/layers/indoors/room_waiting.svg" + } + ] + }, + { + "id": "room-capacity", + "question": { + "en": "How much people can at most fit in this room?" + }, + "condition": { + "or": [ + "room=waiting", + "room=restaurant", + "room=office", + "room=nursery", + "room=conference", + "room=auditorium", + "room=chapel", + "room=bedroom", + "room=classroom" + ] + }, + "render": { + "en": "At most {capacity} people fit this room" + }, + "freeform": { + "key": "capacity", + "type": "pnat" + } + }, + "etymology.wikipedia-etymology" ], "mapRendering": [ { @@ -222,7 +435,7 @@ { "if": { "or": [ - "room=adminstration", + "room=administration", "room=auditorium", "room=bedroom", "room=chapel", From 08e408e19ddc1f39913c90b2e16c99ab98e5ada2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 03:06:09 +0200 Subject: [PATCH 105/133] UX: slow down spin speed of search geolocation button --- src/UI/BigComponents/ThemeIntroPanel.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index 3bf9ce163..305f9e0d2 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -71,7 +71,7 @@ {:else if $geopermission === "requested"} {:else if $geopermission === "denied"} @@ -81,7 +81,7 @@ {:else } From 4067912ce09205f271e4b324ca4645463049bf9f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 03:07:10 +0200 Subject: [PATCH 106/133] Refactoring: include 'label' section into Checkbox --- src/UI/Base/Checkbox.svelte | 14 +++++----- src/UI/BigComponents/Filterview.svelte | 36 ++++++++++++-------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/UI/Base/Checkbox.svelte b/src/UI/Base/Checkbox.svelte index 010992df9..92de427ac 100644 --- a/src/UI/Base/Checkbox.svelte +++ b/src/UI/Base/Checkbox.svelte @@ -1,12 +1,14 @@ - - + diff --git a/src/UI/BigComponents/Filterview.svelte b/src/UI/BigComponents/Filterview.svelte index e4f3e234d..7ed9c26c4 100644 --- a/src/UI/BigComponents/Filterview.svelte +++ b/src/UI/BigComponents/Filterview.svelte @@ -55,27 +55,26 @@ {#if filteredLayer.layerDef.name}
- + {/if} + {#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
@@ -83,10 +82,9 @@
{#if filter.options.length === 1 && filter.options[0].fields.length === 0} - + + {filter.options[0].question} + {/if} {#if filter.options.length === 1 && filter.options[0].fields.length > 0} From 6edcd7d73c126c7e39772c12335a8dd3f04d6a95 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 03:10:36 +0200 Subject: [PATCH 107/133] Chore: sync translations for changed 'level'-id --- langs/layers/ca.json | 42 ++++++++++++++++++------------------ langs/layers/cs.json | 42 ++++++++++++++++++------------------ langs/layers/da.json | 42 ++++++++++++++++++------------------ langs/layers/de.json | 42 ++++++++++++++++++------------------ langs/layers/en.json | 45 +++++++++++++++++++++------------------ langs/layers/eo.json | 14 ++++++------ langs/layers/es.json | 42 ++++++++++++++++++------------------ langs/layers/fil.json | 42 ++++++++++++++++++------------------ langs/layers/fr.json | 42 ++++++++++++++++++------------------ langs/layers/hu.json | 42 ++++++++++++++++++------------------ langs/layers/id.json | 42 ++++++++++++++++++------------------ langs/layers/it.json | 36 +++++++++++++++---------------- langs/layers/ja.json | 36 +++++++++++++++---------------- langs/layers/nb_NO.json | 42 ++++++++++++++++++------------------ langs/layers/nl.json | 45 +++++++++++++++++++++------------------ langs/layers/pl.json | 42 ++++++++++++++++++------------------ langs/layers/pt.json | 42 ++++++++++++++++++------------------ langs/layers/pt_BR.json | 36 +++++++++++++++---------------- langs/layers/ru.json | 36 +++++++++++++++---------------- langs/layers/sl.json | 40 +++++++++++++++++----------------- langs/layers/sv.json | 36 +++++++++++++++---------------- langs/layers/zh_Hant.json | 42 ++++++++++++++++++------------------ 22 files changed, 438 insertions(+), 432 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 08e5831f0..ab650ab69 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -6034,27 +6034,6 @@ } } }, - "level": { - "mappings": { - "0": { - "then": "Situat a planta subterrani" - }, - "1": { - "then": "Situat a planta zero" - }, - "2": { - "then": "Situat a la planta zero" - }, - "3": { - "then": "Situat a primera planta" - }, - "4": { - "then": "Localitzat a la planta base" - } - }, - "question": "A quina planta està situat aquest element?", - "render": "Situat a la planta {level}" - }, "luminous_or_lit": { "mappings": { "0": { @@ -6167,6 +6146,27 @@ }, "question": "Aquest servei té endolls elèctrics, disponibles pels clients quan hi són dins?" }, + "single_level": { + "mappings": { + "0": { + "then": "Situat a planta subterrani" + }, + "1": { + "then": "Situat a planta zero" + }, + "2": { + "then": "Situat a la planta zero" + }, + "3": { + "then": "Situat a primera planta" + }, + "4": { + "then": "Localitzat a la planta base" + } + }, + "question": "A quina planta està situat aquest element?", + "render": "Situat a la planta {level}" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 4c2da9751..7c241f168 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -1767,27 +1767,6 @@ "question": "Jaký je název sítě pro bezdrátový přístup k internetu?", "render": "Název sítě je {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Nachází se v podzemí" - }, - "1": { - "then": "Nachází se v přízemí" - }, - "2": { - "then": "Nachází se v přízemí" - }, - "3": { - "then": "Nachází se v prvním patře" - }, - "4": { - "then": "Nachází se v prvním suterénu" - } - }, - "question": "V jaké úrovni se tento prvek nachází?", - "render": "Nachází se v {level}. patře" - }, "luminous_or_lit": { "mappings": { "0": { @@ -1897,6 +1876,27 @@ }, "question": "Má toto zařízení elektrické zásuvky, které jsou zákazníkům k dispozici, když jsou uvnitř?" }, + "single_level": { + "mappings": { + "0": { + "then": "Nachází se v podzemí" + }, + "1": { + "then": "Nachází se v přízemí" + }, + "2": { + "then": "Nachází se v přízemí" + }, + "3": { + "then": "Nachází se v prvním patře" + }, + "4": { + "then": "Nachází se v prvním suterénu" + } + }, + "question": "V jaké úrovni se tento prvek nachází?", + "render": "Nachází se v {level}. patře" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/da.json b/langs/layers/da.json index b47321dca..2378cec34 100644 --- a/langs/layers/da.json +++ b/langs/layers/da.json @@ -2163,27 +2163,6 @@ "question": "Hvad er netværksnavnet for den trådløse internetadgang?", "render": "Netværksnavnet er {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Placeret under jorden" - }, - "1": { - "then": "Beliggende i stueetagen" - }, - "2": { - "then": "Beliggende i stueetagen" - }, - "3": { - "then": "Beliggende på første sal" - }, - "4": { - "then": "Beliggende på første kælderetage" - } - }, - "question": "På hvilket niveau er denne funktion placeret?", - "render": "Beliggende på {level}. etage" - }, "multilevels": { "override": { "question": "Hvilke niveauer går denne elevator til?", @@ -2237,6 +2216,27 @@ }, "question": "Har denne faciliteter stikkontakter tilgængelige for kunder, når de er inde?" }, + "single_level": { + "mappings": { + "0": { + "then": "Placeret under jorden" + }, + "1": { + "then": "Beliggende i stueetagen" + }, + "2": { + "then": "Beliggende i stueetagen" + }, + "3": { + "then": "Beliggende på første sal" + }, + "4": { + "then": "Beliggende på første kælderetage" + } + }, + "question": "På hvilket niveau er denne funktion placeret?", + "render": "Beliggende på {level}. etage" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/de.json b/langs/layers/de.json index d4e1387ef..0db8b2538 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -7316,27 +7316,6 @@ } } }, - "level": { - "mappings": { - "0": { - "then": "Das Objekt befindet sich unter der Erde" - }, - "1": { - "then": "Das Objekt befindet sich im Erdgeschoss" - }, - "2": { - "then": "Das Objekt befindet sich im Erdgeschoss" - }, - "3": { - "then": "Das Objekt befindet sich im 1. Obergeschoss" - }, - "4": { - "then": "Das Objekt befindet sich im 1. Untergeschoss" - } - }, - "question": "Auf welcher Ebene befindet sich das Objekt?", - "render": "Das Objekt befindet sich im {level}. Geschoss" - }, "luminous_or_lit": { "mappings": { "0": { @@ -7452,6 +7431,27 @@ }, "question": "Gibt es hier Steckdosen, an denen Kunden ihre Geräte laden können?" }, + "single_level": { + "mappings": { + "0": { + "then": "Das Objekt befindet sich unter der Erde" + }, + "1": { + "then": "Das Objekt befindet sich im Erdgeschoss" + }, + "2": { + "then": "Das Objekt befindet sich im Erdgeschoss" + }, + "3": { + "then": "Das Objekt befindet sich im 1. Obergeschoss" + }, + "4": { + "then": "Das Objekt befindet sich im 1. Untergeschoss" + } + }, + "question": "Auf welcher Ebene befindet sich das Objekt?", + "render": "Das Objekt befindet sich im {level}. Geschoss" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/en.json b/langs/layers/en.json index 54817046e..c0944b437 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -7405,27 +7405,6 @@ } } }, - "level": { - "mappings": { - "0": { - "then": "Located underground" - }, - "1": { - "then": "Located on the ground floor" - }, - "2": { - "then": "Located on the ground floor" - }, - "3": { - "then": "Located on the first floor" - }, - "4": { - "then": "Located on the first basement level" - } - }, - "question": "On what level is this feature located?", - "render": "Located on the {level}th floor" - }, "luminous_or_lit": { "mappings": { "0": { @@ -7524,6 +7503,9 @@ "phone": { "question": "What is the phone number of {title()}?" }, + "repeated": { + "render": "Multiple, identical objects can be found on floors {repeat_on}." + }, "service:electricity": { "mappings": { "0": { @@ -7541,6 +7523,27 @@ }, "question": "Does this amenity have electrical outlets, available to customers when they are inside?" }, + "single_level": { + "mappings": { + "0": { + "then": "Located underground" + }, + "1": { + "then": "Located on the ground floor" + }, + "2": { + "then": "Located on the ground floor" + }, + "3": { + "then": "Located on the first floor" + }, + "4": { + "then": "Located on the first basement level" + } + }, + "question": "On what level is this feature located?", + "render": "Located on the {level}th floor" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/eo.json b/langs/layers/eo.json index 978385bb9..2eb17199b 100644 --- a/langs/layers/eo.json +++ b/langs/layers/eo.json @@ -138,7 +138,13 @@ "email": { "question": "Kio estas la retpoŝta adreso de {title()}?" }, - "level": { + "opening_hours": { + "render": "

Malfermitaj horoj

{opening_hours_table(opening_hours)}" + }, + "phone": { + "question": "Kio estas la telefonnumero de {title()}?" + }, + "single_level": { "mappings": { "1": { "then": "En la teretaĝo" @@ -152,12 +158,6 @@ }, "render": "En la {level}a etaĝo" }, - "opening_hours": { - "render": "

Malfermitaj horoj

{opening_hours_table(opening_hours)}" - }, - "phone": { - "question": "Kio estas la telefonnumero de {title()}?" - }, "website": { "question": "Kie estas la retejo de {title()}?" } diff --git a/langs/layers/es.json b/langs/layers/es.json index 96bab0796..7a6ee531c 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -3631,27 +3631,6 @@ "question": "¿Cuál es el nombre de red para el acceso inalámbrico a internet?", "render": "El nombre de red es {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Localizado bajo tierra" - }, - "1": { - "then": "Localizado en la planta baja" - }, - "2": { - "then": "Localizado en la planta baja" - }, - "3": { - "then": "Localizado en la primera planta" - }, - "4": { - "then": "Localizada en el primer sótano" - } - }, - "question": "¿En qué nivel se encuentra esta característica?", - "render": "Localizada en la {level}° planta" - }, "luminous_or_lit": { "mappings": { "0": { @@ -3764,6 +3743,27 @@ }, "question": "¿Esta facilidad tiene enchufes eléctricos, disponibles para los clientes cuando están dentro?" }, + "single_level": { + "mappings": { + "0": { + "then": "Localizado bajo tierra" + }, + "1": { + "then": "Localizado en la planta baja" + }, + "2": { + "then": "Localizado en la planta baja" + }, + "3": { + "then": "Localizado en la primera planta" + }, + "4": { + "then": "Localizada en el primer sótano" + } + }, + "question": "¿En qué nivel se encuentra esta característica?", + "render": "Localizada en la {level}° planta" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/fil.json b/langs/layers/fil.json index f77b2859e..f8763de68 100644 --- a/langs/layers/fil.json +++ b/langs/layers/fil.json @@ -34,27 +34,6 @@ "email": { "question": "Ano ang email address ng {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Nasa ilalim ng lupa" - }, - "1": { - "then": "Nasa unang palapag" - }, - "2": { - "then": "Nasa unang palapag" - }, - "3": { - "then": "Nasa unang palapag" - }, - "4": { - "then": "Nasa silong" - } - }, - "question": "Anong palapag matatagpuan ang tampók?", - "render": "Natagpuan sa ika-{level} na palapag" - }, "opening_hours": { "question": "Anong oras nagbubukas ang {title()}?", "render": "

Mga oras na bukas

{opening_hours_table(opening_hours)}" @@ -102,6 +81,27 @@ }, "question": "Merong bang mga intsupe (outlet) sa loob, para sa mga suki?" }, + "single_level": { + "mappings": { + "0": { + "then": "Nasa ilalim ng lupa" + }, + "1": { + "then": "Nasa unang palapag" + }, + "2": { + "then": "Nasa unang palapag" + }, + "3": { + "then": "Nasa unang palapag" + }, + "4": { + "then": "Nasa silong" + } + }, + "question": "Anong palapag matatagpuan ang tampók?", + "render": "Natagpuan sa ika-{level} na palapag" + }, "website": { "question": "Ano ang website ng {title()}?" }, diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 15583d684..3f4a92964 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -4809,27 +4809,6 @@ } } }, - "level": { - "mappings": { - "0": { - "then": "En sous-sol" - }, - "1": { - "then": "Rez-de-chaussée" - }, - "2": { - "then": "Rez-de-chaussée" - }, - "3": { - "then": "Premier étage" - }, - "4": { - "then": "Sous-sol" - } - }, - "question": "À quel étage se situe l’élément ?", - "render": "Étage {level}" - }, "luminous_or_lit": { "mappings": { "0": { @@ -4939,6 +4918,27 @@ }, "question": "Des prises sont elles à disposition des client·e·s en intérieur ?" }, + "single_level": { + "mappings": { + "0": { + "then": "En sous-sol" + }, + "1": { + "then": "Rez-de-chaussée" + }, + "2": { + "then": "Rez-de-chaussée" + }, + "3": { + "then": "Premier étage" + }, + "4": { + "then": "Sous-sol" + } + }, + "question": "À quel étage se situe l’élément ?", + "render": "Étage {level}" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/hu.json b/langs/layers/hu.json index 44c9e796c..be2714d5d 100644 --- a/langs/layers/hu.json +++ b/langs/layers/hu.json @@ -798,27 +798,6 @@ "email": { "question": "Mi a(z) {title()} e-mail címe?" }, - "level": { - "mappings": { - "0": { - "then": "A föld alatt" - }, - "1": { - "then": "A földszinten" - }, - "2": { - "then": "A földszinten" - }, - "3": { - "then": "Az első emeleten" - }, - "4": { - "then": "Az első alagsori szinten" - } - }, - "question": "Melyik szinten található ez a létesítmény?", - "render": "A(z) {level}. emeleten" - }, "opening_hours": { "question": "Mikor van nyitva ez: {title()}?", "render": "

Nyitva tartás

{opening_hours_table(opening_hours)}" @@ -875,6 +854,27 @@ }, "question": "Van-e ebben a létesítményben olyan konnektor, amely a bent tartózkodó ügyfelek rendelkezésére áll?" }, + "single_level": { + "mappings": { + "0": { + "then": "A föld alatt" + }, + "1": { + "then": "A földszinten" + }, + "2": { + "then": "A földszinten" + }, + "3": { + "then": "Az első emeleten" + }, + "4": { + "then": "Az első alagsori szinten" + } + }, + "question": "Melyik szinten található ez a létesítmény?", + "render": "A(z) {level}. emeleten" + }, "website": { "question": "Mi a weboldala ennek: {title()}?" }, diff --git a/langs/layers/id.json b/langs/layers/id.json index ff82e7cdd..925438b30 100644 --- a/langs/layers/id.json +++ b/langs/layers/id.json @@ -503,27 +503,6 @@ "question": "Apa nama jaringan internet nirkabelnya?", "render": "Nama jaringan ini adalah {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Terletak di bawah tanah" - }, - "1": { - "then": "Terletak di lantai dasar" - }, - "2": { - "then": "Terletak di lantai dasar" - }, - "3": { - "then": "Berlokasi di lantai pertama" - }, - "4": { - "then": "Terletak di lantai basement pertama" - } - }, - "question": "Pada tingkat apa fitur ini diletakkan?", - "render": "Terletak di lantai {level}" - }, "multilevels": { "override": { "question": "Pada lantai berapa saja lift ini berjalan?", @@ -569,6 +548,27 @@ "phone": { "question": "Berapa nomor telepon dari {title()}?" }, + "single_level": { + "mappings": { + "0": { + "then": "Terletak di bawah tanah" + }, + "1": { + "then": "Terletak di lantai dasar" + }, + "2": { + "then": "Terletak di lantai dasar" + }, + "3": { + "then": "Berlokasi di lantai pertama" + }, + "4": { + "then": "Terletak di lantai basement pertama" + } + }, + "question": "Pada tingkat apa fitur ini diletakkan?", + "render": "Terletak di lantai {level}" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 1b6502f5e..ea501f5a2 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -1809,24 +1809,6 @@ "email": { "question": "Qual è l'indirizzo email di {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Si trova sotto il livello stradale" - }, - "1": { - "then": "Si trova al pianoterra" - }, - "2": { - "then": "Si trova al pianoterra" - }, - "3": { - "then": "Si trova al primo piano" - } - }, - "question": "A quale piano si trova questo elemento?", - "render": "Si trova al piano numero {level}" - }, "opening_hours": { "question": "Quali sono gli orari di apertura di {title()}?", "render": "

Orari di apertura

{opening_hours_table(opening_hours)}" @@ -1854,6 +1836,24 @@ "phone": { "question": "Qual è il numero di telefono di {title()}?" }, + "single_level": { + "mappings": { + "0": { + "then": "Si trova sotto il livello stradale" + }, + "1": { + "then": "Si trova al pianoterra" + }, + "2": { + "then": "Si trova al pianoterra" + }, + "3": { + "then": "Si trova al primo piano" + } + }, + "question": "A quale piano si trova questo elemento?", + "render": "Si trova al piano numero {level}" + }, "website": { "question": "Qual è il sito web di {title()}?" }, diff --git a/langs/layers/ja.json b/langs/layers/ja.json index eb2c414f2..66ad27974 100644 --- a/langs/layers/ja.json +++ b/langs/layers/ja.json @@ -530,24 +530,6 @@ "email": { "question": "{title()}のEメールアドレスは何ですか?" }, - "level": { - "mappings": { - "0": { - "then": "地下にあります" - }, - "1": { - "then": "1階にあります" - }, - "2": { - "then": "1階にあります" - }, - "3": { - "then": "1階にあります" - } - }, - "question": "この機能は何階にあるのでしょうか?", - "render": "{level}階にあります" - }, "opening_hours": { "question": "{title()}の営業時間は?", "render": "

営業時間

{opening_hours_table(opening_hours)}" @@ -583,6 +565,24 @@ }, "question": "このアメニティにはコンセントがあり、お客様が店内にいるときにも利用できますか?" }, + "single_level": { + "mappings": { + "0": { + "then": "地下にあります" + }, + "1": { + "then": "1階にあります" + }, + "2": { + "then": "1階にあります" + }, + "3": { + "then": "1階にあります" + } + }, + "question": "この機能は何階にあるのでしょうか?", + "render": "{level}階にあります" + }, "website": { "question": "{title()}のウェブサイトは?" }, diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json index d073d3e7e..4be55ca72 100644 --- a/langs/layers/nb_NO.json +++ b/langs/layers/nb_NO.json @@ -642,27 +642,6 @@ "question": "Hva er nettverksnavnet for det trådløse nettverket?", "render": "Nettverksnavnet er {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Under bakken" - }, - "1": { - "then": "På gateplan" - }, - "2": { - "then": "På gateplan" - }, - "3": { - "then": "I andre etasje" - }, - "4": { - "then": "Er å finne på første kjellernivå" - } - }, - "question": "Hvilken etasje befinner funksjonen seg i?", - "render": "Ligger i {level} etasje" - }, "multilevels": { "override": { "question": "Hvilke etasjer går heisen til?", @@ -755,6 +734,27 @@ }, "question": "Har denne fasiliteten stikkontakter, tilgjengelig for kunder innendørs?" }, + "single_level": { + "mappings": { + "0": { + "then": "Under bakken" + }, + "1": { + "then": "På gateplan" + }, + "2": { + "then": "På gateplan" + }, + "3": { + "then": "I andre etasje" + }, + "4": { + "then": "Er å finne på første kjellernivå" + } + }, + "question": "Hvilken etasje befinner funksjonen seg i?", + "render": "Ligger i {level} etasje" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 460b43f84..a8ad5960e 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -6881,27 +6881,6 @@ } } }, - "level": { - "mappings": { - "0": { - "then": "Bevindt zich ondergronds" - }, - "1": { - "then": "Bevindt zich op de begane grond" - }, - "2": { - "then": "Bevindt zich gelijkvloers" - }, - "3": { - "then": "Bevindt zich op de eerste verdieping" - }, - "4": { - "then": "Bevindt zich in de eerste kelderverdieping" - } - }, - "question": "Op welke verdieping bevindt dit punt zich?", - "render": "Bevindt zich op de {level}de verdieping" - }, "multilevels": { "override": { "question": "Naar welke verdiepingen gaat deze lift?", @@ -6980,6 +6959,9 @@ "phone": { "question": "Wat is het telefoonnummer van {title()}?" }, + "repeated": { + "render": "Er zijn verschillende, identieke objecten op verdiepingen {repeat_on}." + }, "service:electricity": { "mappings": { "0": { @@ -6997,6 +6979,27 @@ }, "question": "Zijn er stekkers beschikbaar voor klanten die binnen zitten?" }, + "single_level": { + "mappings": { + "0": { + "then": "Bevindt zich ondergronds" + }, + "1": { + "then": "Bevindt zich op de begane grond" + }, + "2": { + "then": "Bevindt zich gelijkvloers" + }, + "3": { + "then": "Bevindt zich op de eerste verdieping" + }, + "4": { + "then": "Bevindt zich in de eerste kelderverdieping" + } + }, + "question": "Op welke verdieping bevindt dit punt zich?", + "render": "Bevindt zich op de {level}de verdieping" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/pl.json b/langs/layers/pl.json index 606dc16b0..9ca723481 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -2405,27 +2405,6 @@ "question": "Jaka jest nazwa sieci dla bezprzewodowego dostępu do Internetu?", "render": "Nazwa sieci to {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Znajduje się pod ziemią" - }, - "1": { - "then": "Znajduje się na parterze" - }, - "2": { - "then": "Znajduje się na parterze" - }, - "3": { - "then": "Znajduje się na pierwszym piętrze" - }, - "4": { - "then": "Położone na pierwszym poziomie piwnicy" - } - }, - "question": "Na jakim poziomie znajduje się ta funkcja?", - "render": "Znajduje się na {level} piętrze" - }, "luminous_or_lit": { "mappings": { "0": { @@ -2535,6 +2514,27 @@ }, "question": "Czy w tym przybytku znajdują się gniazdka elektryczne, gdzie klienci mogą naładować swoje urządzenia?" }, + "single_level": { + "mappings": { + "0": { + "then": "Znajduje się pod ziemią" + }, + "1": { + "then": "Znajduje się na parterze" + }, + "2": { + "then": "Znajduje się na parterze" + }, + "3": { + "then": "Znajduje się na pierwszym piętrze" + }, + "4": { + "then": "Położone na pierwszym poziomie piwnicy" + } + }, + "question": "Na jakim poziomie znajduje się ta funkcja?", + "render": "Znajduje się na {level} piętrze" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/pt.json b/langs/layers/pt.json index 0b555ee2d..835167ba8 100644 --- a/langs/layers/pt.json +++ b/langs/layers/pt.json @@ -882,27 +882,6 @@ "question": "Qual é o nome da rede para o acesso sem fios à Internet?", "render": "O nome da rede é {internet_access:ssid}" }, - "level": { - "mappings": { - "0": { - "then": "Está no subsolo" - }, - "1": { - "then": "Está ao nível do rés-do-chão" - }, - "2": { - "then": "Está ao nível do rés-do-chão" - }, - "3": { - "then": "Está no primeiro andar" - }, - "4": { - "then": "Localizado no primeiro nível da cave" - } - }, - "question": "Em que nível se encontra este elemento?", - "render": "Está no {level}º andar" - }, "multilevels": { "override": { "question": "Para que pisos vai este elevador?", @@ -956,6 +935,27 @@ }, "question": "Esta infraestrutura tem tomadas elétricas, disponíveis para os clientes quando estão no interior?" }, + "single_level": { + "mappings": { + "0": { + "then": "Está no subsolo" + }, + "1": { + "then": "Está ao nível do rés-do-chão" + }, + "2": { + "then": "Está ao nível do rés-do-chão" + }, + "3": { + "then": "Está no primeiro andar" + }, + "4": { + "then": "Localizado no primeiro nível da cave" + } + }, + "question": "Em que nível se encontra este elemento?", + "render": "Está no {level}º andar" + }, "smoking": { "mappings": { "0": { diff --git a/langs/layers/pt_BR.json b/langs/layers/pt_BR.json index a5a0a8ab2..c0e38375e 100644 --- a/langs/layers/pt_BR.json +++ b/langs/layers/pt_BR.json @@ -628,24 +628,6 @@ "email": { "question": "Qual o endereço de e-mail de {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Localizado no subsolo" - }, - "1": { - "then": "Localizado no térreo" - }, - "2": { - "then": "Localizado no térreo" - }, - "3": { - "then": "Localizado no primeiro andar" - } - }, - "question": "Em que nível esse recurso está localizado?", - "render": "Localizado no {level}o andar" - }, "opening_hours": { "question": "Qual o horário de funcionamento de {title()}?", "render": "

Horário de funcionamento

{opening_hours_table(opening_hours)}" @@ -664,6 +646,24 @@ "phone": { "question": "Qual o número de telefone de {title()}?" }, + "single_level": { + "mappings": { + "0": { + "then": "Localizado no subsolo" + }, + "1": { + "then": "Localizado no térreo" + }, + "2": { + "then": "Localizado no térreo" + }, + "3": { + "then": "Localizado no primeiro andar" + } + }, + "question": "Em que nível esse recurso está localizado?", + "render": "Localizado no {level}o andar" + }, "website": { "question": "Qual o site de {title()}?" }, diff --git a/langs/layers/ru.json b/langs/layers/ru.json index b32efc029..49cfa19c6 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -1558,24 +1558,6 @@ "email": { "question": "Какой адрес электронной почты у {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Расположено под землей" - }, - "1": { - "then": "Расположено на первом этаже" - }, - "2": { - "then": "Расположено на первом этаже" - }, - "3": { - "then": "Расположено на первом этаже" - } - }, - "question": "На каком этаже находится этот объект?", - "render": "Расположено на {level}ом этаже" - }, "opening_hours": { "question": "Какое время работы у {title()}?", "render": "

Часы работы

{opening_hours_table(opening_hours)}" @@ -1594,6 +1576,24 @@ "phone": { "question": "Какой номер телефона у {title()}?" }, + "single_level": { + "mappings": { + "0": { + "then": "Расположено под землей" + }, + "1": { + "then": "Расположено на первом этаже" + }, + "2": { + "then": "Расположено на первом этаже" + }, + "3": { + "then": "Расположено на первом этаже" + } + }, + "question": "На каком этаже находится этот объект?", + "render": "Расположено на {level}ом этаже" + }, "website": { "question": "Какой сайт у {title()}?" }, diff --git a/langs/layers/sl.json b/langs/layers/sl.json index 31e74dfef..279276a6b 100644 --- a/langs/layers/sl.json +++ b/langs/layers/sl.json @@ -149,26 +149,6 @@ "email": { "question": "Kakšen naslov elektronske pošte ima {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Nahaja se pod zemljo" - }, - "1": { - "then": "Nahaja se v pritličju" - }, - "2": { - "then": "Nahaja se v pritličju" - }, - "3": { - "then": "Nahaja se v prvem nadstropju" - }, - "4": { - "then": "Nahaja se v prvi kletni etaži" - } - }, - "render": "Nahaja se v {level}. nadstropju" - }, "opening_hours": { "question": "Kakšen odpiralni čas ima {title()}?", "render": "

Odpiralni čas

{opening_hours_table(opening_hours)}" @@ -198,6 +178,26 @@ }, "phone": { "question": "Kakšno telefonsko številko ima {title()}?" + }, + "single_level": { + "mappings": { + "0": { + "then": "Nahaja se pod zemljo" + }, + "1": { + "then": "Nahaja se v pritličju" + }, + "2": { + "then": "Nahaja se v pritličju" + }, + "3": { + "then": "Nahaja se v prvem nadstropju" + }, + "4": { + "then": "Nahaja se v prvi kletni etaži" + } + }, + "render": "Nahaja se v {level}. nadstropju" } } }, diff --git a/langs/layers/sv.json b/langs/layers/sv.json index 02d594aa4..fcace1943 100644 --- a/langs/layers/sv.json +++ b/langs/layers/sv.json @@ -51,24 +51,6 @@ "email": { "question": "Vad är e-postadressen till {title()}?" }, - "level": { - "mappings": { - "0": { - "then": "Ligger under jorden" - }, - "1": { - "then": "Ligger på bottenvåningen" - }, - "2": { - "then": "Ligger på bottenvåningen" - }, - "3": { - "then": "Ligger på första våningen" - } - }, - "question": "På vilken nivå finns den här funktionen?", - "render": "Ligger på {level}:e våningen" - }, "opening_hours": { "question": "Vilka är öppettiderna för {title()}?", "render": "

Öppettider

{opening_hours_table(opening_hours)}" @@ -104,6 +86,24 @@ }, "question": "Har den här bekvämligheten eluttag tillgängliga för kunder när de är inne?" }, + "single_level": { + "mappings": { + "0": { + "then": "Ligger under jorden" + }, + "1": { + "then": "Ligger på bottenvåningen" + }, + "2": { + "then": "Ligger på bottenvåningen" + }, + "3": { + "then": "Ligger på första våningen" + } + }, + "question": "På vilken nivå finns den här funktionen?", + "render": "Ligger på {level}:e våningen" + }, "website": { "question": "Vad är webbplatsen för {title()}?" }, diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json index 1b07031cf..dd9c456cb 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -628,27 +628,6 @@ "email": { "question": "{title()} 的電子郵件地址是什麼?" }, - "level": { - "mappings": { - "0": { - "then": "位於地下" - }, - "1": { - "then": "位於 1 樓" - }, - "2": { - "then": "位於 1 樓" - }, - "3": { - "then": "位於 2 樓" - }, - "4": { - "then": "位於地下一樓" - } - }, - "question": "此圖徽位於哪個樓層/層級?", - "render": "位於 {level} 樓" - }, "opening_hours": { "question": "{title()} 的開放時間是什麼?", "render": "

開放時間

{opening_hours_table(opening_hours)}" @@ -705,6 +684,27 @@ }, "question": "這個便利設施有電器設備,能給客戶使用嗎?" }, + "single_level": { + "mappings": { + "0": { + "then": "位於地下" + }, + "1": { + "then": "位於 1 樓" + }, + "2": { + "then": "位於 1 樓" + }, + "3": { + "then": "位於 2 樓" + }, + "4": { + "then": "位於地下一樓" + } + }, + "question": "此圖徽位於哪個樓層/層級?", + "render": "位於 {level} 樓" + }, "website": { "question": "{title()} 網址是什麼?" }, From 6d5f5d54f8d754b9148e5e7b9f5c32435e596d1a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Sep 2023 04:02:42 +0200 Subject: [PATCH 108/133] Refactoring: port reviews to svelte --- assets/layers/food/food.json | 2 +- assets/layers/icons/icons.json | 10 + assets/layers/questions/questions.json | 2 +- assets/svg/license_info.json | 10 + assets/svg/mangrove_logo.svg | 47 ++++ assets/svg/star.svg | 56 ++++- assets/svg/star_half.svg | 62 +++++- assets/svg/star_outline.svg | 58 ++++- langs/ca.json | 3 - langs/cs.json | 3 - langs/da.json | 3 - langs/de.json | 3 - langs/en.json | 12 +- langs/es.json | 3 - langs/fr.json | 3 - langs/gl.json | 3 - langs/hu.json | 3 - langs/id.json | 3 - langs/it.json | 3 - langs/ja.json | 3 - langs/nb_NO.json | 3 - langs/nl.json | 3 - langs/pl.json | 3 - langs/pt.json | 3 - langs/pt_BR.json | 3 - langs/ru.json | 3 - langs/zh_Hant.json | 3 - public/assets/mangrove_logo.png | Bin 14110 -> 0 bytes public/css/index-tailwind-output.css | 88 +++----- src/Logic/Web/MangroveReviews.ts | 201 ++++++++++-------- .../BigComponents/SelectedElementTitle.svelte | 2 +- src/UI/Reviews/AllReviews.svelte | 47 ++++ src/UI/Reviews/ReviewElement.ts | 56 ----- src/UI/Reviews/ReviewForm.svelte | 97 +++++++++ src/UI/Reviews/ReviewForm.ts | 101 --------- src/UI/Reviews/SingleReview.svelte | 38 ++++ src/UI/Reviews/SingleReview.ts | 64 ------ src/UI/Reviews/StarElement.svelte | 32 +++ src/UI/Reviews/StarsBar.svelte | 21 ++ src/UI/Reviews/StarsBarIcon.svelte | 11 + src/UI/SpecialVisualizations.ts | 74 ++++++- 41 files changed, 683 insertions(+), 462 deletions(-) create mode 100644 assets/svg/mangrove_logo.svg delete mode 100644 public/assets/mangrove_logo.png create mode 100644 src/UI/Reviews/AllReviews.svelte delete mode 100644 src/UI/Reviews/ReviewElement.ts create mode 100644 src/UI/Reviews/ReviewForm.svelte delete mode 100644 src/UI/Reviews/ReviewForm.ts create mode 100644 src/UI/Reviews/SingleReview.svelte delete mode 100644 src/UI/Reviews/SingleReview.ts create mode 100644 src/UI/Reviews/StarElement.svelte create mode 100644 src/UI/Reviews/StarsBar.svelte create mode 100644 src/UI/Reviews/StarsBarIcon.svelte diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 443cd8e36..bac347058 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -149,7 +149,6 @@ }, "tagRenderings": [ "images", - "level", { "question": { "nl": "Wat is de naam van deze eetgelegenheid?", @@ -213,6 +212,7 @@ "email", "phone", "payment-options", + "level", "wheelchair-access", { "question": { diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 9f15ac5dd..dd1efb4e3 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -182,6 +182,16 @@ "then": "dogs are allowed" } ] + }, + { + "id": "rating", + "labels": [ + "defaults" + ], + "icon": { + "class": "w-20 mx-1 flex items-center" + }, + "render": "{rating()}" } ], "mapRendering": null diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 748c2221e..af7f5742d 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -130,7 +130,7 @@ "id": "reviews", "description": "Shows the reviews module (including the possibility to leave a review)", "render": { - "*": "{reviews()}" + "*": "{create_review()}{list_reviews()}" } }, { diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index f84818f4c..d6643c1c3 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -723,6 +723,16 @@ "authors": [], "sources": [] }, + { + "path": "mangrove_logo.svg", + "license": "LOGO", + "authors": [ + "Mangrove.reviews" + ], + "sources": [ + "https://mangrove.reviews/" + ] + }, { "path": "mapcomplete_logo.svg", "license": "LOGO AND CC-BY-SA-4.0", diff --git a/assets/svg/mangrove_logo.svg b/assets/svg/mangrove_logo.svg new file mode 100644 index 000000000..f79997020 --- /dev/null +++ b/assets/svg/mangrove_logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/assets/svg/star.svg b/assets/svg/star.svg index 61301ada9..84cc67ff2 100644 --- a/assets/svg/star.svg +++ b/assets/svg/star.svg @@ -1,6 +1,52 @@ - - - - - + + + + + diff --git a/assets/svg/star_half.svg b/assets/svg/star_half.svg index 7765c2872..c52a80d87 100644 --- a/assets/svg/star_half.svg +++ b/assets/svg/star_half.svg @@ -1,6 +1,56 @@ - - - - - - \ No newline at end of file + + + + + + + diff --git a/assets/svg/star_outline.svg b/assets/svg/star_outline.svg index 859b7539c..fa23c3705 100644 --- a/assets/svg/star_outline.svg +++ b/assets/svg/star_outline.svg @@ -1,6 +1,52 @@ - - - - - - \ No newline at end of file + + + + + + diff --git a/langs/ca.json b/langs/ca.json index 21c6d7381..7e9cc090b 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -534,10 +534,7 @@ "attribution": "Les ressenyes funcionen gràcies a Mangrove Reviews i estan disponibles sota CC-BY 4.0.", "i_am_affiliated": "Tinc alguna filiació amb aquest objecte
Marca-ho si n'ets cap, creador, treballador, …", "name_required": "És requerit un nom per mostrar i crear revisions", - "no_rating": "Doneu una puntuació abans d'enviar…", "no_reviews_yet": "No hi ha revisions encara. Sigues el primer a escriure'n una i ajuda al negoci i a les dades lliures!", - "plz_login": "Entra per deixar una revisió", - "posting_as": "Enviat com", "save": "Desar", "saved": "Revisió compartida. Gràcies per compartir!", "saving_review": "Desant…", diff --git a/langs/cs.json b/langs/cs.json index e5229e6b9..fe833def6 100644 --- a/langs/cs.json +++ b/langs/cs.json @@ -448,10 +448,7 @@ "attribution": "Recenze jsou poskytovány službou Mangrove Reviews a jsou k dispozici pod licencí CC-BY 4.0.", "i_am_affiliated": "Jsem spojen/a s tímto objektem
Zaškrtněte, pokud jste vlastníkem, tvůrcem, zaměstnancem, …", "name_required": "Pro zobrazení a vytváření recenzí je vyžadováno jméno", - "no_rating": "Před odesláním udělte hodnocení…", "no_reviews_yet": "Zatím zde nejsou žádné recenze. Buďte první, kdo ji napíše, a pomozte otevřít data a podnikání!", - "plz_login": "Přihlaste se a zanechte recenzi", - "posting_as": "Přihlášeni jako", "save": "Uložit", "saved": "Recenze uložena. Díky za sdílení!", "saving_review": "Ukládání…", diff --git a/langs/da.json b/langs/da.json index 1350677b5..76bd60c63 100644 --- a/langs/da.json +++ b/langs/da.json @@ -383,10 +383,7 @@ "attribution": "Anmeldelserne er baseret på Mangrove Reviews og er tilgængelige under CC-BY 4.0.", "i_am_affiliated": "Jeg er tilknyttet dette objekt
Tjek, om du er ejer, skaber, ansat, ...", "name_required": "Der kræves et navn for at vise og oprette anmeldelser", - "no_rating": "Ingen vurdering givet", "no_reviews_yet": "Der er ingen anmeldelser endnu. Vær den første til at skrive en og hjælpe åbne data og forretningen!", - "plz_login": "Log ind for at give en anmeldelse", - "posting_as": "Anmelder som", "saved": "Anmeldelse gemt. Tak for at bidrage!", "saving_review": "Gemmer…", "title": "{count} Anmeldelser", diff --git a/langs/de.json b/langs/de.json index 671f97a02..2c18b8185 100644 --- a/langs/de.json +++ b/langs/de.json @@ -536,10 +536,7 @@ "attribution": "Rezensionen werden bereitgestellt von Mangrove Reviews und sind unter CC-BY 4.0 verfügbar.", "i_am_affiliated": "Ich bin an diesem Objekt beteiligt
Auswählen, wenn Sie Eigentümer, Ersteller, Angestellter … sind", "name_required": "Der Name des Objekts ist erforderlich, um Bewertungen zu erstellen und anzuzeigen", - "no_rating": "Vor dem Absenden eine Bewertung abgeben…", "no_reviews_yet": "Es gibt noch keine Bewertungen. Hilf mit der ersten Bewertung dem Geschäft und der Open Data Bewegung!", - "plz_login": "Anmelden, um eine Bewertung abzugeben", - "posting_as": "Veröffentlichen als", "save": "Speichern", "saved": "Bewertung gespeichert. Danke fürs Teilen!", "saving_review": "Speichern…", diff --git a/langs/en.json b/langs/en.json index 06e94de36..7167afb95 100644 --- a/langs/en.json +++ b/langs/en.json @@ -557,14 +557,16 @@ "reviews": { "affiliated_reviewer_warning": "(Affiliated review)", "attribution": "Reviews are powered by Mangrove Reviews and are available under CC-BY 4.0.", - "i_am_affiliated": "I am affiliated with this object
Check if you are an owner, creator, employee, …", + "i_am_affiliated": "I am affiliated with this object", + "i_am_affiliated_explanation": "Check if you are an owner, creator, employee, …", "name_required": "A name is required in order to display and create reviews", - "no_rating": "Give a rating before submitting…", "no_reviews_yet": "There are no reviews yet. Be the first to write one and help open data and the business!", - "plz_login": "Log in to leave a review", - "posting_as": "Posting as", + "question": "How would you rate {title()}?", + "question_opinion": "How was your experience?", + "reviewing_as": "Reviewing as {nickname}", + "reviewing_as_anonymous": "Reviewing as anonymous", "save": "Save", - "saved": "Review saved. Thanks for sharing!", + "saved": "Review saved. Thanks for sharing!", "saving_review": "Saving…", "title": "{count} reviews", "title_singular": "One review", diff --git a/langs/es.json b/langs/es.json index 815880080..9522e2660 100644 --- a/langs/es.json +++ b/langs/es.json @@ -414,10 +414,7 @@ "reviews": { "affiliated_reviewer_warning": "(Revisión afiliada)", "name_required": "Se requiere un nombre para mostrar y crear comentarios", - "no_rating": "Da una calificación antes de enviar…", "no_reviews_yet": "Aún no hay reseñas. ¡Sé el primero en escribir una y ayuda a los datos abiertos y a los negocios!", - "plz_login": "Inicia sesión para dejar una reseña", - "posting_as": "Publicación como", "saved": "Reseña guardada. ¡Gracias por compartir!", "saving_review": "Guardando…", "title": "{count} comentarios", diff --git a/langs/fr.json b/langs/fr.json index 7d9422991..7bf0c5f9c 100644 --- a/langs/fr.json +++ b/langs/fr.json @@ -428,10 +428,7 @@ "attribution": "Les avis sont fournis par Mangrove Reviews et sont disponibles sous licence CC-BY 4.0.", "i_am_affiliated": "Je suis affilié à cet objet
Cochez si vous en êtes le propriétaire, créateur, employé, …", "name_required": "Un nom est requis pour afficher et créer des avis", - "no_rating": "Aucun score donné", "no_reviews_yet": "Il n'y a pas encore d'avis. Soyez le premier à en écrire un et aidez le lieu et les données ouvertes !", - "plz_login": "Connectez vous pour laisser un avis", - "posting_as": "Envoi en tant que", "saved": "Avis enregistré. Merci du partage !", "saving_review": "Enregistrement…", "title": "{count} avis", diff --git a/langs/gl.json b/langs/gl.json index 23648730b..8d9d04d00 100644 --- a/langs/gl.json +++ b/langs/gl.json @@ -163,10 +163,7 @@ "reviews": { "affiliated_reviewer_warning": "(Recensión de afiliado)", "name_required": "Requírese un nome para amosar e crear recensións", - "no_rating": "Sen puntuacións", "no_reviews_yet": "Non hai recensións aínda. Se o primeiro en escribir unha e axuda ao negocio e aos datos libres!", - "plz_login": "Inicia sesión para deixar unha recensión", - "posting_as": "Publicar como", "saved": "Recensión compartida. Grazas por compartir!", "saving_review": "Gardando…", "title": "{count} recensións", diff --git a/langs/hu.json b/langs/hu.json index de4faf548..625a81515 100644 --- a/langs/hu.json +++ b/langs/hu.json @@ -303,10 +303,7 @@ "attribution": "A véleményeket Mangrove Reviews tárolja, és a CC-BY 4.0 licenc szerint érhetők el.", "i_am_affiliated": "Kapcsolatban állok ezzel a létesítménnyel
Ellenőrizd, hogy tulajdonos, alkotó, alkalmazott vagy hasonló vagy-e.", "name_required": "Vélemények megjelenítéséhez és létrehozásához névre van szükség", - "no_rating": "Még nem kapott értékelést", "no_reviews_yet": "Még nincs vélemény. Légy Te az első, aki ír, és ezzel támogasd a nyílt adatokat és az üzletet!", - "plz_login": "Értékelés írásához jelentkezz be", - "posting_as": "Közzétéve mint", "saved": "Vélemény elmentve. Köszönjük a megosztást!", "saving_review": "Mentés…", "title": "{count} vélemény", diff --git a/langs/id.json b/langs/id.json index 69e86090a..419d2c101 100644 --- a/langs/id.json +++ b/langs/id.json @@ -162,9 +162,6 @@ }, "reviews": { "attribution": "Ulasan didukung oleh Mangrove Reviews dan tersedia di bawah CC-BY 4.0.", - "no_rating": "Tidak ada peringkat yang diberikan", - "plz_login": "Masuk untuk meninggalkan ulasan", - "posting_as": "Posting sebagai", "saved": " Ulasan disimpan. Terima kasih sudah berbagi! ", "saving_review": "Menyimpan…", "title": "{count} ulasan", diff --git a/langs/it.json b/langs/it.json index 1506a86b5..95464ae7a 100644 --- a/langs/it.json +++ b/langs/it.json @@ -307,10 +307,7 @@ "attribution": "Le recensioni sono fornite da Mangrove Reviews e sono disponibili con licenza CC-BY 4.0.", "i_am_affiliated": "Sono associato con questo oggetto
Spunta se sei il proprietario, creatore, dipendente, etc.", "name_required": "È richiesto un nome per poter mostrare e creare recensioni", - "no_rating": "Nessun voto ricevuto", "no_reviews_yet": "Non ci sono ancora recensioni. Sii il primo a scriverne una aiutando così i dati liberi e l’attività!", - "plz_login": "Accedi per lasciare una recensione", - "posting_as": "Pubblica come", "saved": "Recensione salvata. Grazie per averla condivisa!", "saving_review": "Salvataggio…", "title": "{count} recensioni", diff --git a/langs/ja.json b/langs/ja.json index afcfdd6ad..e09d9f1f9 100644 --- a/langs/ja.json +++ b/langs/ja.json @@ -165,10 +165,7 @@ "attribution": "レビューは、Mangrove Reviews and are available under CC-BY 4.0で公開されます。", "i_am_affiliated": "わたしは、この対象物の関係者です
所有者、作成者、従業員などの有無を確認します", "name_required": "レビューを表示および作成するには名前が必要です", - "no_rating": "評価が与えられていません", "no_reviews_yet": "まだレビューはありません。最初に書き込みを行い、データとビジネスのオープン化を支援しましょう!", - "plz_login": "ログインしてレビューを終了する", - "posting_as": "としての投稿", "saved": "レビューが保存されました。共有ありがとう!", "saving_review": "保存中…", "title": "{count}個のレビュー", diff --git a/langs/nb_NO.json b/langs/nb_NO.json index 3c56e2c00..af9d92472 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -401,10 +401,7 @@ "attribution": "Vurderinger er muliggjort av Mangrove Reviews og er tilgjengelige som CC-BY 4.0.", "i_am_affiliated": "Jeg har en tilknytning til dette objektet
Sjekk om du er eier, skaper, ansatt, …", "name_required": "Et navn kreves for å vise og opprette vurderinger", - "no_rating": "Ingen vurdering gitt", "no_reviews_yet": "Ingen vurderinger enda. Vær først til å skrive en og hjelp åpen data og bevegelsen.", - "plz_login": "Logg inn for å legge igjen en vurdering", - "posting_as": "Anmelder som", "saved": "Vurdering lagret. Takk for at du deler din mening.", "saving_review": "Lagrer …", "title": "{count} vurderinger", diff --git a/langs/nl.json b/langs/nl.json index 1a7e651cb..70101f0b2 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -530,10 +530,7 @@ "attribution": "De beoordelingen worden voorzien door Mangrove Reviews en zijn beschikbaar onder deCC-BY 4.0-licentie. ", "i_am_affiliated": "Ik ben persoonlijk betrokken
Vink aan indien je de oprichter, maker, werknemer, ... of dergelijke bent", "name_required": "De naam van dit object moet gekend zijn om een review te kunnen maken", - "no_rating": "Geef een beoordeling voordat je verzendt…", "no_reviews_yet": "Er zijn nog geen beoordelingen. Wees de eerste om een beoordeling te schrijven en help open data en het bedrijf!", - "plz_login": "Meld je aan om een beoordeling te geven", - "posting_as": "Ingelogd als", "save": "Opslaan", "saved": "Bedankt om je beoordeling te delen!", "saving_review": "Opslaan...", diff --git a/langs/pl.json b/langs/pl.json index d4d949a35..ca72241b4 100644 --- a/langs/pl.json +++ b/langs/pl.json @@ -331,10 +331,7 @@ "attribution": "Recenzje są obsługiwane przez Recenzje Mangrove i są dostępne na licencji CC-BY 4.0.", "i_am_affiliated": "Jestem powiązany z tym obiektem
Sprawdź czy jesteś właścicielem, twórcą, pracownikiem, ...", "name_required": "Nazwa jest wymagana do wyświetlania i tworzenia opinii", - "no_rating": "Nie podano oceny", "no_reviews_yet": "Nie ma jeszcze recenzji. Bądź pierwszym, który je napisze i pomóż otworzyć dane i biznes!", - "plz_login": "Zaloguj się, aby zostawić opinię", - "posting_as": "Publikowanie jako", "save": "Zapisz", "saved": "Opinia została zapisana. Dzięki za udostępnienie!", "saving_review": "Zapisywanie…", diff --git a/langs/pt.json b/langs/pt.json index 1b2767540..37f0f8c73 100644 --- a/langs/pt.json +++ b/langs/pt.json @@ -352,10 +352,7 @@ "attribution": "As avaliações são fornecidas por Mangrove Reviews e estão disponíveis sob a licença CC-BY 4.0.", "i_am_affiliated": "Eu sou afiliado a este objeto

Marque isto se for proprietário, criador, funcionário…
", "name_required": "É necessário um nome para mostrar e criar avaliações", - "no_rating": "Nenhuma classificação dada", "no_reviews_yet": "Ainda não existem avaliações. Seja o primeiro a escrever uma e ajude a abrir os dados e os negócios!", - "plz_login": "Inicie a sessão para deixar uma avaliação", - "posting_as": "Publicar como", "saved": "Avaliação guardada. Obrigado por partilhar!", "saving_review": "A guardar…", "title": "{count} avaliações", diff --git a/langs/pt_BR.json b/langs/pt_BR.json index d6af7cb4d..e84552603 100644 --- a/langs/pt_BR.json +++ b/langs/pt_BR.json @@ -162,10 +162,7 @@ "attribution": "As resenhas são fornecidas por Mangrove Reviews e estão disponíveis em CC-BY 4.0.", "i_am_affiliated": "Eu sou afiliado a este objeto

Verifique se você é proprietário, criador, funcionário, …
", "name_required": "É necessário um nome para exibir e criar comentários", - "no_rating": "Nenhuma classificação dada", "no_reviews_yet": "Não há comentários ainda. Seja o primeiro a escrever um e ajude a abrir os dados e os negócios!", - "plz_login": "Entrar para deixar um comentário", - "posting_as": "Postando como", "saved": "Comentário salvo. Obrigado por compartilhar!", "saving_review": "Salvando…", "title": "{count} comentários", diff --git a/langs/ru.json b/langs/ru.json index aaa9683b7..ceb9b1378 100644 --- a/langs/ru.json +++ b/langs/ru.json @@ -176,10 +176,7 @@ "attribution": "Отзывы созданы на основе Mangrove Reviews и доступны под лицензией CC-BY 4.0.", "i_am_affiliated": "Я связан с этим объектом
Отметьте если вы создатель, владелец, работник, …", "name_required": "Необходимо название, чтобы просматривать и создавать отзывы", - "no_rating": "Нет рейтинга", "no_reviews_yet": "Пока нет отзывов. Оставьте первый отзыв и помогите открытым данным и бизнесу!", - "plz_login": "Войдите, чтобы оставить отзыв", - "posting_as": "Публикация от имени", "saved": " Отзыв сохранен. Спасибо, что поделились! ", "saving_review": "Сохранение…", "title": "{count} отзыв(-ов)", diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json index 8772a0778..128b248a3 100644 --- a/langs/zh_Hant.json +++ b/langs/zh_Hant.json @@ -383,10 +383,7 @@ "attribution": "評審系統由Mangrove Reviews提供技術支援,採用CC-BY 4.0授權條款。", "i_am_affiliated": "我是這物件的相關關係者
確認你是否是擁有者、創造者、員工等等", "name_required": "需要有名稱才能顯示和創造審核", - "no_rating": "還沒有評分", "no_reviews_yet": "還沒有審核,當第一個撰寫者來幫助開放資料與商家吧!", - "plz_login": "登入來留下審核", - "posting_as": "以貼文", "saved": "已儲存審核,謝謝你的分享!", "saving_review": "儲存中…", "title": "{count} 審核次數", diff --git a/public/assets/mangrove_logo.png b/public/assets/mangrove_logo.png deleted file mode 100644 index 38f39f8eda52574e21720c1925792308a68669cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14110 zcmY+LRa9I}u!V7Vch}(V?!hI&Ex1D$V+R0-=qH?2yozMl}>dY1Ox?yg0zH|r_q%mVy4Ox zu~<>0^dm3MyCjzP>eA9QQI!J(7X+3B!<^}Lu^hvq&2u^8as|%@NwzlC+d{$B4wK{DGi>6)v&l^Z2I}Vfrt*bCh?U^?!_(nlnxG$YlC)+3A}oT4PG{)$MdBa(Wl*z zVuQBN#fBa3tbWS#d>tsRe6#y1jgDK{)y6$}a~#yvA=g_S8IxID^yc@6V{WIj-z1cG zynYi|lz~j`x>6BnB`Nf(c-L%5+QmBys$$AJ0e}5?e>KdAD(?8) zh&=vf`dC@e;eJJ>n9hudU_{j_G)J$^@-ZP$`73Wpz@KM<>BCT2C0d-b~|ww>0~; z`k|P-I3k|@Z=6a8**tdh;1dYR%34I{cEA-BZrV=B?$@mHy_`=MzhiGT+F-YN-hJx5 z#Twd~_HVPT^A6SAhVXuSzG>O<7xMeLq^*#}X)zj#h~0?>zE_2t4G9^U%1zfVqvX`o z!QXJ%5?I*SXZ02nqqET|!=;Px9L@FiZ5=A0|G;#6?AYqkW%BQ6w8T zUVq)#$nDP1>S=2J!?Ek<$47Q@j!!U%$jCo>^F(}j{jZ#pI*LemW`e*lzkZ?+Ht(Y{I%(dUZq)v76e80?p^eB7FPrg zn^8`VSb)*bgXH97$#`-xGPRr`ZTfAl5th-Fd8_Fh1x-!OEF|||*IS2mBV^QU-A`1{ z2MJ=M#7>M+tY3~)UxI|v)$#hfXnnTamC076$$Qs5L-178Xm2DpXbvc!tbk= zo#dqoZFn53gNjA+qyxM|yD@6H_~Ls$sNcMfo0e?*?bAIVYhGF_om{~()o!DwiHUK z@I;kd!6ad?dkl3QQX%)^tgNiV;W%R5<6)-~#SA*lQYG~vyUkV?wX$T7ZLbI%R)b7& z(#;Y@d`_GB;zY}->^TrL_4;FYP+2ga!E&6}cWevG5%Edin`?H^|Glj5IZqr7>O;ERJ-4vPm2;blc4lyt)29mY}1Zp zzw5WKzU_vfuV#?fKa2f+m7<^n%OEzDMIY~mSXMidM!Uv{NBY2YAY6U)ZKc|fI_=#D zs-XIaR0j3nKaU$uMoNOF{V+&Grd?oZeQL=yG(NvXqL*W?)v5dR&>x0ey3FbQV4aapI?hrP3>GDb{UL~0h~b_6m@D8K9)(M*m`($} zFwN6To!Lm|?>#1=&xi7lP9Wo2@S*-%n*U1NGoV9=i77$xD6!e``3Py7!;?B|n`t$G z0*?_FQSYg@2!iUO(PlyT{MK=+eGXi&HemqF$Zns^xg8N7W;RT{>MR?IUO7mC8_QpC zSoBhtYmD=E=>ngcO!}ZSy1c(cs^@vV)2>#!>3(q(uJ@Z}@%s{XTk)X+kJ~n{`^U*l znJ4rvW@RbDtPnpk9&-U0Xe5y0LNK)u;Cnld()Ag(CGQ1(9F9qSYd30l`<`K}8-|3- zuz2u9ki+j1%xuuA=1C^<+Awgl=Fc%(Cso;(w$tW%k~kEDFO?JA7p?1M>X8^QcS&LE z?JelDAWjYKB}7RH`A65NF<9c}>F(gLEZ_fLVc_MaC*S|qmWS@|T~krBVU*U->{sZG zx}FanXY)f9PS(~8hHal|UCs8^Y36wLFZ>@))q{WGHoEQY?X|z;CMJr1y9OiFbaGr_ z+#?zW^$oH_}ye`s}BZ~W%e{Y)oKk^kc}EJVO8Rl#{tf+!V6SMM%Jx^%TV zQf`pc;)a5Lzunj}!7LXW`#;m#%oT<)>O(sZ+8SXJC0Z2+yZo44?+W z{{pEUkp$~5{5zO8E+xs_N;k&7DNgXo_mf;RCd$rSRzE^a@5q8C_xzZ2YAIg( zBLiQMu}RxDP`8bGyUa%7b(xPa5Q(|116!g^eJAsv0}>`Mk(sp0F??1*hFhxssYbG^ zn89k(Pt0kKFm$k%p(vj(>X$kGc$8smc|+*txK>Zw<^LwwA@XlSxCb*73hN52$4-xU}o?v+6?6jWkfnyn5Gt84z+BOC^`z}K6Azdwi~ zzOu8hu*NmK~sgz;mAcua67j!1QlW`UIMH%r5Dus6eoUcrVzL)kjZb@Y$Lh>7G9B zIk>+4{*v!p?5y-Lz}`w6ym>#7JT_a~7N zW(*G8r}Mu*X2eQY26<#fydMIs_ps6UN*SFnBGn>0FE-m6PTqFyib^suK?yT}l9Ypa zYg`qALJQ07Wo=?fz$e2kk8s(K3K7-|b|i}Jx)#jEme>U+(Ls^Zele6AvL^leEUUb4 zwU6Zd&b{1S4baf1dx=GO+TFfhdR2Nn^`--GnirD0DL4c)f_bpIcNC0_gLpS^oKD-q z>I4{B41+|zB}b@*D83WD{I@f~7Q|T^Ai)piz#J*7qWX@IdZ>+I_lDsAiHp&6UQCq} zTdp?z`u_TAvdX+K7*=;VWY)kJ!E!Q_vh!mT^je_|G7_!0wY4>ZwC1Kpx1&~bYy=RFN)&@2ti7)2(U|#K{}-a%S&87SMbYe1i8O_I4ZH_s2t)A z#;TWzT;Ms;AMHT;V2`%XM>-_oo!`F)e;e$ra@ik5L^r6$9at6h`)%%GDwhmL;?Hfp z&1&3T8I2tq{koj4)i2q~v=YIG!TrfOn45|>dUotRxn@iq=eHEIy&%DWKfW5p@|cga zp}cVSuF}$>*MfI=f%XpUYEk_VEv_dj$Cc$HB%oF-+>Brp5XkBehjvkzSv`%hG#ieU zIHcm%&vT626Q{#vhjpZ#4Hi4kU^AZnBk=(slsx?vX6 zE?5MCkAQ?H>3;N*?0}A^$d_Fxf0TbA1{2b;8GRp(lZz|NnHaG1biNu zpFt>!EKrw1!U^~qw&kai@SW{aZGKq!B3(xpT_d^;eXFmbw{hfWnmZ!VPfh%Z!jYIn zt7HCDo|Vt&wC_4NB*xjK7&ACV)SmKnio^X49^)FbRs z@j^-50Y!E1^6|E%k)Z*Ff)g#l(D#VKgDE8bc1ijb&VYONy63OIrJb6n)*SlXek4IE z9}TJXdLv_pA;khdC`0?d4DDV)YU3cu4pt2{1VmO=DP?g^(wTawVM;4)THlX|kZn6> zL7Mg6j^Pnv^&7m%NDC;0?oViluzA)}@2i<#>G|yzN1;bhJq{wt>ksScrLqu|Lj?K2 zp>Yf*Nrb@l5hjh5Vh~e|7f8^f^ZZwii|=^x6x{P`+lP5Mm@kr=eUq?>d=(_Dk#!0s= zbN%(PN-s@-sc_FQTqj;3g)Yk_*Q(t(^1qLZr+ST{%82j0S!Q4(6V&oxs8DTZ+2uhg zMBoaF{k3RU&?4!~!*DaEYT=7+iWfXf9Oy9@p`6$J8E;WAFvy7%OUL)#)W!OXa08jQ zI~~un>i}08X4P1Kmq~nhBDt&c@_6;-*DzYnA^WT7w=CF)o1J_MGLDo*WpmLn>a;Jo z0^*ybGQp%M+z&c`O^^k?oTpOen1+(_J5%Vh1`kc=3Rziq)`;vnksQAB`SX0z)h?L@ zF(KxdARhK>y;&_O#JV}5*wGC|TOr)?Gzx~^g&2w~?f$jNa^>WKA11KRmenrY2GPkx zXJ>9#ByN?Y!t)1u%4U`(s$~Qk=_%;@m}jiub^?{&MKfSYQKLaQ?x<{$k>E!*q2>PTXnYx^KkB?jl2zUj;sql6<0h`n+I{Vy1(IfRX5q|f1#O$yN6C*nsg}zCehwo|7K;g8-gJ#e$ z{c1a&FM+VV4W&?$$MK+7)!F27rG~r+yPh3v+&lzB70jQ~IZx8o_mXxp*x*2~QPfZv+d_%fktt;A@so0_C9vJ#VHsC65 zx-A)o)K?QTceWQnURrxf-OftD#L`!xeO+`s(IguqkVvd(1TE9XH;RHd69JlaFWr0f zeaLI!Q9YZ%7>MT}Ots+&sizm_R+K`CyO-;Zgdy4j9=qjO?FNheG>Ht)fSd2fzMx4f zBP1lu+u1|s1syH36~rCkt!WD1CW-gtw;WHz#DA0^(@#-VjTwF^iPQ{zyyE3dyA6PU--#!)K zD*Dcqsgk2pf3146wiF}jk^mW$amIy74(<9VF^E!gpVE>pE-q_`h?F|b({g|Yy3p*8 zysQh?hOy&qiiziRpy4Ek?~^kUALfx2B(Ff1(20%3bKPy$vh=}`P>yzg zAo)~&u8xC{@I@Wn`p{5Tkbdxv!iRd_SB(_qM7E+b6v0vUH~)B;Ja)8P6{oJQxj9YB z4WGmM;pwX^!nY-lxk4En1rkr}yQO+FqcW39qps|sfFI$UZjr)TKCYP-XuY!b0$Pou z(KBC*w=Du>J~?(t%G7R&(?}VJZmPHPVjjGuac|T?M{hPcZ;2bofs7NmnE^M3i?NE0 z@XLY=9(|bQ@KomM9q%P*`M<rj&T~0XdQmtgNZbq@X(SjBYDrv;>!l5$6dKE64zCaV&8s62U zN9@m&*-}FoF$$p;hGG$ON$;Hm6nv=KN|ipoKzyQlOjUybubEhZE^IBmThmCt7)-ZeV}496x$6W z=xik}Pt;FP!=_$(v`c5VXG`!XcEaR=HZAR+o2~bJNen{weX9uh?+ZFxxOv z1xdlDWJRV07;&lB0)EOeN|qF?>CCAwhZl>i>EtSL5|iza|u<1!OrL z6|OPXegpMe-a4V1K-^6Sk>dHZGi&b?$0;uo;k!g7? z_rbX;Gc$YCB^Wq-!XlVTP|j%)2=SGfb#-;kd8%9&^|+8pFrR?LP$=(V!>#Z&YiLa% zT?GMZ!8N%Zo|m$+C6WI=b?y(S_1|QkXlRr`x?sOX^13`_OgE zlRD1nW#!?kz(dFGaa3wP=F0NEVTylk|B|DZ=0~5VdPGQquVE5Ub>ySy;e%VF3UE@) z2J(sPj4Uiu%dR$I-OW_>=3`hN@eL^Ii0icqK37%!(5DRD)+sNCM-Yi^|&{rJX}41h{-1F(z-MiiVIr{C@vf>0tu> z&~ajNR`MSrhJX74t3hjG;X~v5hM&x*RFVj`vx0MZr&>jZ_A%fFC?v|t%0HpZ zxLsO4kfa_QS>WyNuJ!~3$W4c3{iZI(lz@W9QR1j47=#Jb1-izF#sf7TrO;XDPhKOV z^hp~RadiBKo{BUlDY)b=pI_Oc$d)d92ZxlfDhhZAb>e&K#y{2U(}ao&3(MDRCI5*F z;8k>=#YifoA4fcm*0W|{nVcy&9dvXrQdC71e%Qtn%K_I&)U*ENp{ z`C?lT4ja9wh^r4B9+5Oa*RWS>S|pc12V^RVAR%LV)lE8MzIT_U#lxmUrC%dzLpJ$ew@ zTI0FdKCT8Kg#WwVlF;I3az3xg_GSJ<>F{XMY`#D6S+2VZ@p?%NKLV9vJrAObKc)#s zPsQl`k)KEbVhH6-$U%)kTe1Usj^3EXAZPFC*#%Rvhy3fSPjhYI#02B$%HOAhgoeUO zdeuDh08eEs79z?lUJ^ESdw4#9J%M^`SAYxiwV-vCwcvzO@!4pbgBRN64sn7qh+bRHQ}r98CEimkl%7|5=*zX^zS; zv8?JXeUBxx?syTq?S;OiU?hYgLoNKWFtr;>qKhGSgv#gW=Ej}HW0!u^keZXDv}bq~ z3SDV>_#PM{Ne`>m4$icud!q5P`o$2zDtgfWBanzGt??}`syW*V$!h(vw#e)I}K#G+U2AC9H$!NmM3VD+bJ^$D|8{UTzSS?G2)50fBkY|L*Ise#;b z*-Imw7C&lQyAJP!Xr5<3UyS&2yGL1N-%Yk}c``DB_j4nud-R$wjS>z>=uso)=+tPf zcyNS=J7FE1S`M9*23Qd&@K2*j)JAp@K8VTUeAct#Pp08^IFt2OGh|CdUcOMfu(Bc0 zWQPtq=Q2)6zJ#Wve4no>a7ym<>?o2cA%a*9+tp}-H0>~S_n6PMQhYBvdy7snxcz|m z!tQn)yNlqH&B;@_gf*W%+OLf?XXI=Vu_O9EL84Lp``SrUcx)`n$96(^!ew=XA&hUS ze4$BU6+dMVm>mgpNhH!8p9=R%e=Ld59#4I}pwoDSl`0W0)&4a)D*sPW89@v8<=jk{ zpDrt>u}IqtAO4|%>6=K3?jixx8W%C=y;g65b3>Bk9jSNwg%6d7v%o{P^?N!(5e%Bk zJK*M6l=1sM)n1bX$E;iq54HTWJV;R#mZvz;_*P@sx~-!aQ^nIPuu^-d)ke;AF67Al zQkSL?+){Sb^qxIP`;*cZA*8~?!YE4^ec(FpuEC)rwyiV9`MLD?QnSMbCvMXm94?zE-e!>MjI9HX=nZ<(FJR)h zKQN0=L_6y-c-F3z(?ZP=%_Y|!nC2!1CbAF8cVSZ&)AHmx8k_$V^?hOwV(GQ|z z&tM}zYUl%tk&YS9AfOm?5zYl$x6w@e2JoN=@**p zko=T;bbo9c72<%lzC>c@qcye&6vF--$|la~mww|6aRTNjbkP?o3fP1P*OO_)J{-#b zvP3l~@QKe)F1A8gwf2B=w;)Y!Ea=sR!*x-qQG!&_^QrM$VMbO6l_2^!aRHei+2Qyx zO^Ucad>H1SZD&T>nxclLCZi)}QI=aMS_4j4>3T*F-+v#gqUU=gZ>6IgO7lD6^0C9I z)pHi-ea1(!CxaxB&S+kBdc|{*;rZQ>FDWS!W3{?BC4#^5OhGa zD`vwrvDByUPQ?DD!i473csO5Dq`^-o6eK31M*lN|mK1%jF>i~^f#nSAf&@>3<}FJ! z$VgM_et%UGmXJCrJTX(7()n{)U(!m|sfvSx z+&`1nl8=DPG8TiRXq52E7-(DLRre|aHH1UoL5M0DRxcGOFEB+JHrajvt(kg}rVv)Q zEe>K?R_KajUj#&9h2f1*x5`#X94ys`1U)@+9_m;v8V&qvb;*mLCx7@);XmvE1@bjM z1TVhRHhK9*?+7PYCTD(wm?Z<-Q%pzsZP1nOKq_v2pGrc*`)NF&#Vyjg>qCuEE)aXC zpygZoc=hW>C98V-bqz zyZ6Me`YEBBF!cx6g0abgiBq94Q%=^lM@@Gtm}g;XmtOMcC%FBH z5b8dTwa>@2VW|*+l*xYHuQI^%MT3C_+3w>XV$soZ{hfLX#d@0sx{>K@+Fqanmatzj zaWVH-e4fq`pyA}4$MQ{zOSJQxT*s3>uIo^3vR%@-x8y=C%oOw8oZO`ol+_i`!HScK z@G+3z&DuSb>raP9F*60yCOn>S5D+y`KGd?@ekrG$ncquz4&_s?p*C<$#F1*Gbb~mz z3m|-vzT@0T;EIxOCqE)VI1Ns~9rU5E=eU0+5mKwQrskUiemKcjDI>h8ViY@fCkMhZ zJg5pd{>&jp5y>%JX0;h=A`G-}6A13EU_J&WvFL!ov{xr#@)h-76%oSHw(aD2zGRkJuP4_B{H(R z+3w2XA#I7O;chmOW#EnteIb&SHQ$Shic(5I*L?<_f1bRQFRlqmu=+^k1V^Ss9yVf9 ztaI$(QtSvZnY%>l#)_0ACY(g*A@I^k*65SE9^}YVp(JWHfEfZ*`9% zJo-JXGRE@tP31m8_7cfdu8@c2bdEK`YRFgg%><0K=t|*k z&bEr^7I!kJLxHV&3*=Uqfc)M+{fx>>E9vbSLxJIy=Z>dpiR%mRrgkd zT_-d%r5AZHX+znUp)9ozvIEPr`!!A@anmp|fTQBEyB~n7d-;qZk9)VvokOvf8TBI>$#;_|)lAQXzy?a9wWLQ*8k$f^pP){8FkH>mBaFkl&W3Q$3H{E# z$_fgme@*0me~i2zwN9>b#OQ*?1vG?qwZT~X+L1|kXXR|_8g0jURp0c^zqe#N>)Y;h zi@dy7N#U6az935sp46k-&0$hG&`8e>sB(%u`HqS&SI@6chW zh7*hMk08g>m`2EU%lULV+#Qg_80jZye+V~PN;!b9#W2pKvxbo&F{#$;GZ_d8ok=Y0 zTURhThV~v`pTK;!K;ja)I$Z(tIogEwYOSG+#r3;G2oAi{Z&J=L=UPY;{%&rKX8ix{ zU2RkRRj1|RN-2Cqp?a6}pgMkYvGS(2L9LIGXaK8as&K~dkTtTxcMjl%5@)kMAFJcZ ztnlLyAJO??9pz)o2jX+i(Eyx@uY7D_a@W9um-0+TE=$>ry3F_%LIb6?Cif2G=1ExI z5&p(>P-$0@+hQE&o4{8dyJ4sdBkwcXU;@0u#B5=&`dJ;Whr|RI!b3V96Pb~zyPm&q zswy6(oaD0=w4EKjmC5T6X=?{4Y+ zpO+h-KcqKF78B_L@oX5AXDGp63CUKvIVTTi^7Bh7KeRu%ekI(dZv9Z{A3GWE;kGXm zgEz;hS;}5Fa(1h)o5QD%Z7bFL1v0={%9-tx!2y6J!jT0g1~am=&lymC9h6k8Ftm`T zM@LN(976>dL~Jm%B7TK`M6{~TmHt)&%ujgpe3INoqb_eZkf$g$3N7SLs6y3Rx&J;a zsAm6A$`j6#WQz~{3s3Hg0V|a|Ja!VnRK|sHX;!WkdyjOnQWHVVq{tcv-Q&0=_(;&> zau9w@Rtfq$$wL|aR_AX;(PUL|pC1C5jk|NG*lmHX5_$O9o>fQLMKS6a0jy`yjF)tkPKmUqI}2mqmL*{l5d16H+Do`RgkCutCa&Bn zp{*xvi2BgA^)~1;79vxvI`%D~T*7}eESIZ?kO_RIfoW^nP@Xwbdh#kNj~k*d4pc?bNxu@};!4zRNCiHJ z+OWW(k({RW>S86S<)aDI2Y*u5$)BM3s1g=13^!!@US8e=U696VcHWyJZZ?a{ig|?d zCE}9L%7t(+f036@Ta6tdeHpm8<_-^0Z08VE&d4`do@Zpjp6vQHGZq-G5zF~S^*&#Y;Szr_iUH<(e;hHHePrdXPIh&^qg{r_@QGEaEqaX`BmDydTYgBhqHUq*7<<&z)MuSuKisTt zfJc-ihv7j7UUn@eTSrQA|e1^efgyjs)P36f_MX=?iVEe@%Q4Q zmQ=$dFbF5Jc_y})q_i5!8Wi1pP8ncIz>e<*c792}qul$G8P#fow*Big({Ww$U92rZ z)G!KZ8JRf&*JJC)sT6wjX8_pn1SCX70SlS&+8Oo=l6hN&zlw+Fo=zQK{SD%P9v+FTms}A|S<}5pqg5IN94X zXYo2@cUYm_WRY|#+QUbQ*srpG%=%EVcf32<_mcYVLv)(6nq6&@LAUSP78m?4CB${w zW~o6u(|ABnCDzum*NA|)D0Ojh$=2Bm2gGP}hZEWrSU&y~_67zd${=Q*Dd1h*uGT;N zEXZKiP0kbYQ2F8uK!!2U+8Dsy4Tye1%9!QF_6BLzZGSLIG|TL6e@Jvo4v>`$WS4rV ze6eE4y6Z}X1yCoPk#QUUAjj;~)M7*qP~r4;6|3fpw!3*aVG{EVLosZ{)k6qrqk^!c z$z?YURy_`c72)L6h&%>$s~f0KvTvOSXlQ8>`^G;IoBsG@2(=+T3jo2n!9nSb%n;~z zA1evEocDs?Z3ELL#309BX+H?_hR+>JF5g=7gwaQt!XBO!fugGirqXxf>z>FLT@L zB-cdAgIxKtXcQx(mP#x6=Xb*nUJ>Wrz~gejeH5bboNct!I5fs3B$OSDB&_6M$H$BK zJT^LrhR}@7Z5b9KIo^O9W9&C(GwLko>&MLgZ;J{R35VinJdJuJp48P`r7P?>e8;dK z>GJdcf@LL+s`R;tX|khKGSL{U?jnG+J_O3wwbA)?;vE{R`WI`uxEKO-4@4O zGw!ad=8aR}%zWTrHR|LS2>8=!=1MH4Ctwz;s21n<@DpPsafHk#mPFKd)$Rc>$Mv2k z>tB=}_TRIk&%;nY4o?3{Fnoe%BFT`@a=-q^<^Ydgb4zpd%~{qLP{d;Z1{U^n=6Bj` z-6If58_y?QDTb zyV^6+Zm}G5t;sHf`oSK+7h83pv!e6_;0rF(JX_BzCvs9YpOZ?npu-eCn4jrdLG;U9}m)yQwqfQ zq#4ndNyZ$g0PnZJAVuM*QP9ei0;HJI=kfB6%ICXA$!JZ_pK}@dl8?wJ(u`k1)4@a( z?fAb`;j7#teeC9HBPs^7(dGV#7uNAS#03Q6oi#|0A`Go|ZOjzf`jnT~ZbY#^%fU;+Q)Lw0|(EEQK~U zGaX$x=#Ep`!bge8HUWG|3p5hFhdUyOmJ|?6Ybq1Lu*ma(d}B-qycYfkdJ04GlE>;h z0^qoMCtV_Zp0-)SQ(^&_QRkps6Z@jKT66 zJ71~@JAMa%dThe9D&UfvPi6B+vIkpokxFx~U?P1Qtu-A;#8{VF_POcGcm*{f5T#z}cM9>pyT82F-mzsD^6Jj*jf9pv`j&3a;C| z0I^OLl-3sBWL`>$zxFi#i-rZx?uBLcse~+JjoeQ#g>oQxNsrb={C4#~C6ph=3(%Fq zeL2X1Sj!HGe=043!L91r*>S*3TOqN37* z)!!Hv4`lk%*}Sn0K-H;NZNr5y_nBV6nh0^+>CUC7pxWOcy}h~L zOir$dh7SXAmHhA0BYE&#FF_mv$rSJLzlf;I(|#zAV2#0JDFn*$?QDzMH_39sNCZ*U zR{^qa;C{47Py8dq9X6W1j)S%eop!q2SLjn?vMK)^r2yuctfW0J+j6t@9Mycap^%~- zhYvx+K`V(2LR*X=*`B9e=iM-IF{qwyyO#_q-cO0E?)X06tLIfoPa`apDi<-x_3HmS z6agS_heH%3MgZeI<=gQc_=7YYn%v9s?4BZ6%DWgIS5GPp7VH@q z1OkIU2q$+3;6l{EJju3m0O`*Vq(2h+Wo^Ra=*DmNlacSu@(2@*N+fQsJ8tN}7Qi)7 zVz*Ll4mNG~>!f!fCe;9Yngax=r?py-zu!~Inn25y&oz3WKrjTjfkI%f^ZA5QztOLZ zkCK&$uISbniGaf2hz(Ai%%B)jSxIRUstkac4*>AYtGmr55^}G^Fudvfp%Sen(McH( z?F8A0KAZUdbhFJ>vK9tChAzZJ*wMuL_DQN6H0Ot)eRrQ`i?n<^!&{7rNdFJ$!Nt~d zg)o$5_lK~FHFsYlPY0?FFxX7Ud$nJIe13bW0Wz_ItZX=4)uE`SMwLXqn_;~%n0#utj%@;W*M zGO=9`)t}y3G0@T}XPe`*LY=~OE2^>JwSA|erp}6GF8NE(UhJ^lsp~+xnBm>>QN>9#KhXI$53$h@DVd zOYJvXG5H`vjLTF=?*Yp6ySS?}vrNOs(v(e!3*p#)t`JtH5Xlp-NG&nJlMtD~vgVsa z9|iv`hVpJHi`RBC6fBZjCweMo0BB9PSyqfYk|Xi@hp2_q@8I3h z$#ktb&6k9r6zkb~)iLhOviYUQt^YinW19VVrySSZz j8QE$g8066Z@>gtif!}DbK$QlZdxKDrQIW2eGztD623Ha@ diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index da51a2a7f..7c9f0be3e 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -938,10 +938,6 @@ video { margin-bottom: 2rem; } -.ml-3 { - margin-left: 0.75rem; -} - .-ml-6 { margin-left: -1.5rem; } @@ -1210,11 +1206,6 @@ video { width: 2.5rem; } -.w-max { - width: -webkit-max-content; - width: max-content; -} - .w-48 { width: 12rem; } @@ -1404,6 +1395,12 @@ video { margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); } +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-y-reverse > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 1; } @@ -1478,11 +1475,6 @@ video { text-overflow: clip; } -.break-normal { - overflow-wrap: normal; - word-break: normal; -} - .break-all { word-break: break-all; } @@ -1555,14 +1547,14 @@ video { border-width: 1px; } -.border-4 { - border-width: 4px; -} - .border-2 { border-width: 2px; } +.border-4 { + border-width: 4px; +} + .border-x { border-left-width: 1px; border-right-width: 1px; @@ -1669,10 +1661,6 @@ video { padding: 2rem; } -.p-1 { - padding: 0.25rem; -} - .p-2 { padding: 0.5rem; } @@ -1681,6 +1669,10 @@ video { padding: 1rem; } +.p-1 { + padding: 0.25rem; +} + .p-0\.5 { padding: 0.125rem; } @@ -1773,10 +1765,6 @@ video { text-align: justify; } -.align-middle { - vertical-align: middle; -} - .text-xl { font-size: 1.25rem; line-height: 1.75rem; @@ -1787,16 +1775,6 @@ video { line-height: 1.75rem; } -.text-4xl { - font-size: 2.25rem; - line-height: 2.5rem; -} - -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; -} - .text-3xl { font-size: 1.875rem; line-height: 2.25rem; @@ -1807,11 +1785,21 @@ video { line-height: 2rem; } +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + .text-base { font-size: 1rem; line-height: 1.5rem; } +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + .font-bold { font-weight: 700; } @@ -1891,10 +1879,6 @@ video { font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction); } -.leading-none { - line-height: 1; -} - .tracking-tight { letter-spacing: -0.025em; } @@ -2662,26 +2646,6 @@ a.link-underline { opacity: 1; } -@media (prefers-reduced-motion: no-preference) { - @-webkit-keyframes spin { - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - @keyframes spin { - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - .motion-safe\:animate-spin { - -webkit-animation: spin 1s linear infinite; - animation: spin 1s linear infinite; - } -} - @media (max-width: 480px) { .max-\[480px\]\:w-full { width: 100%; @@ -2816,10 +2780,6 @@ a.link-underline { height: 4rem; } - .md\:h-12 { - height: 3rem; - } - .md\:w-8 { width: 2rem; } diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index ec16f3445..15819a735 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -1,34 +1,35 @@ -import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" -import { MangroveReviews, Review } from "mangrove-reviews-typescript" -import { Utils } from "../../Utils" -import { Feature, Position } from "geojson" -import { GeoOperations } from "../GeoOperations" +import { ImmutableStore, Store, UIEventSource } from "../UIEventSource"; +import { MangroveReviews, Review } from "mangrove-reviews-typescript"; +import { Utils } from "../../Utils"; +import { Feature, Position } from "geojson"; +import { GeoOperations } from "../GeoOperations"; export class MangroveIdentity { - public readonly keypair: Store - public readonly key_id: Store + public readonly keypair: Store; + public readonly key_id: Store; constructor(mangroveIdentity: UIEventSource) { - const key_id = new UIEventSource(undefined) - this.key_id = key_id - const keypairEventSource = new UIEventSource(undefined) - this.keypair = keypairEventSource + const key_id = new UIEventSource(undefined); + this.key_id = key_id; + const keypairEventSource = new UIEventSource(undefined); + this.keypair = keypairEventSource; mangroveIdentity.addCallbackAndRunD(async (data) => { if (data === "") { - return + return; } - const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)) - keypairEventSource.setData(keypair) - const pem = await MangroveReviews.publicToPem(keypair.publicKey) - key_id.setData(pem) - }) + const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)); + keypairEventSource.setData(keypair); + const pem = await MangroveReviews.publicToPem(keypair.publicKey); + key_id.setData(pem); + }); try { if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") { - MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {}) + MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => { + }); } } catch (e) { - console.error("Could not create identity: ", e) + console.error("Could not create identity: ", e); } } @@ -38,13 +39,13 @@ export class MangroveIdentity { * @constructor */ private static async CreateIdentity(identity: UIEventSource): Promise { - const keypair = await MangroveReviews.generateKeypair() - const jwk = await MangroveReviews.keypairToJwk(keypair) + const keypair = await MangroveReviews.generateKeypair(); + const jwk = await MangroveReviews.keypairToJwk(keypair); if ((identity.data ?? "") !== "") { // Identity has been loaded via osmPreferences by now - we don't overwrite - return + return; } - identity.setData(JSON.stringify(jwk)) + identity.setData(JSON.stringify(jwk)); } } @@ -52,17 +53,18 @@ export class MangroveIdentity { * Tracks all reviews of a given feature, allows to create a new review */ export default class FeatureReviews { - private static readonly _featureReviewsCache: Record = {} - public readonly subjectUri: Store + private static readonly _featureReviewsCache: Record = {}; + public readonly subjectUri: Store; + public readonly average: Store; private readonly _reviews: UIEventSource<(Review & { madeByLoggedInUser: Store })[]> = - new UIEventSource([]) + new UIEventSource([]); public readonly reviews: Store<(Review & { madeByLoggedInUser: Store })[]> = - this._reviews - private readonly _lat: number - private readonly _lon: number - private readonly _uncertainty: number - private readonly _name: Store - private readonly _identity: MangroveIdentity + this._reviews; + private readonly _lat: number; + private readonly _lon: number; + private readonly _uncertainty: number; + private readonly _name: Store; + private readonly _identity: MangroveIdentity; private constructor( feature: Feature, @@ -75,55 +77,72 @@ export default class FeatureReviews { } ) { const centerLonLat = GeoOperations.centerpointCoordinates(feature) - ;[this._lon, this._lat] = centerLonLat + ;[this._lon, this._lat] = centerLonLat; this._identity = - mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)) - const nameKey = options?.nameKey ?? "name" + mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)); + const nameKey = options?.nameKey ?? "name"; if (feature.geometry.type === "Point") { - this._uncertainty = options?.uncertaintyRadius ?? 10 + this._uncertainty = options?.uncertaintyRadius ?? 10; } else { - let coordss: Position[][] + let coordss: Position[][]; if (feature.geometry.type === "LineString") { - coordss = [feature.geometry.coordinates] + coordss = [feature.geometry.coordinates]; } else if ( feature.geometry.type === "MultiLineString" || feature.geometry.type === "Polygon" ) { - coordss = feature.geometry.coordinates + coordss = feature.geometry.coordinates; } - let maxDistance = 0 + let maxDistance = 0; for (const coords of coordss) { for (const coord of coords) { maxDistance = Math.max( maxDistance, GeoOperations.distanceBetween(centerLonLat, coord) - ) + ); } } - this._uncertainty = options?.uncertaintyRadius ?? maxDistance + this._uncertainty = options?.uncertaintyRadius ?? maxDistance; } - this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName) + this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName); - this.subjectUri = this.ConstructSubjectUri() + this.subjectUri = this.ConstructSubjectUri(); - const self = this + const self = this; this.subjectUri.addCallbackAndRunD(async (sub) => { - const reviews = await MangroveReviews.getReviews({ sub }) - self.addReviews(reviews.reviews) - }) + const reviews = await MangroveReviews.getReviews({ sub }); + self.addReviews(reviews.reviews); + }); /* We also construct all subject queries _without_ encoding the name to work around a previous bug * See https://github.com/giggls/opencampsitemap/issues/30 */ this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => { try { - const reviews = await MangroveReviews.getReviews({ sub }) - self.addReviews(reviews.reviews) + const reviews = await MangroveReviews.getReviews({ sub }); + self.addReviews(reviews.reviews); } catch (e) { - console.log("Could not fetch reviews for partially incorrect query ", sub) + console.log("Could not fetch reviews for partially incorrect query ", sub); } - }) + }); + this.average = this._reviews.map(reviews => { + if (!reviews) { + return null; + } + if(reviews.length === 0){ + return null + } + let sum = 0; + let count = 0; + for (const review of reviews) { + if (review.rating !== undefined) { + count++; + sum += review.rating; + } + } + return Math.round(sum / count) + }); } /** @@ -139,14 +158,14 @@ export default class FeatureReviews { uncertaintyRadius?: number } ) { - const key = feature.properties.id - const cached = FeatureReviews._featureReviewsCache[key] + const key = feature.properties.id; + const cached = FeatureReviews._featureReviewsCache[key]; if (cached !== undefined) { - return cached + return cached; } - const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options) - FeatureReviews._featureReviewsCache[key] = featureReviews - return featureReviews + const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options); + FeatureReviews._featureReviewsCache[key] = featureReviews; + return featureReviews; } /** @@ -155,15 +174,15 @@ export default class FeatureReviews { public async createReview(review: Omit): Promise { const r: Review = { sub: this.subjectUri.data, - ...review, - } - const keypair: CryptoKeyPair = this._identity.keypair.data - console.log(r) - const jwt = await MangroveReviews.signReview(keypair, r) - console.log("Signed:", jwt) - await MangroveReviews.submitReview(jwt) - this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) }) - this._reviews.ping() + ...review + }; + const keypair: CryptoKeyPair = this._identity.keypair.data; + console.log(r); + const jwt = await MangroveReviews.signReview(keypair, r); + console.log("Signed:", jwt); + await MangroveReviews.submitReview(jwt); + this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) }); + this._reviews.ping(); } /** @@ -172,46 +191,48 @@ export default class FeatureReviews { * @private */ private addReviews(reviews: { payload: Review; kid: string }[]) { - const self = this - const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion)) + const self = this; + const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion)); - let hasNew = false + let hasNew = false; for (const reviewData of reviews) { - const review = reviewData.payload + const review = reviewData.payload; try { - const url = new URL(review.sub) - console.log("URL is", url) + const url = new URL(review.sub); + console.log("URL is", url); if (url.protocol === "geo:") { const coordinate = <[number, number]>( url.pathname.split(",").map((n) => Number(n)) - ) + ); const distance = GeoOperations.distanceBetween( [this._lat, this._lon], coordinate - ) + ); if (distance > this._uncertainty) { - continue + continue; } } } catch (e) { - console.warn(e) + console.warn(e); } - const key = review.rating + " " + review.opinion + const key = review.rating + " " + review.opinion; if (alreadyKnown.has(key)) { - continue + continue; } self._reviews.data.push({ ...review, madeByLoggedInUser: this._identity.key_id.map((user_key_id) => { - return reviewData.kid === user_key_id - }), - }) - hasNew = true + return reviewData.kid === user_key_id; + }) + }); + hasNew = true; } if (hasNew) { - self._reviews.ping() + self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first + + self._reviews.ping(); } } @@ -224,13 +245,13 @@ export default class FeatureReviews { private ConstructSubjectUri(dontEncodeName: boolean = false): Store { // https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2 // `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3 - const self = this - return this._name.map(function (name) { - let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}` + const self = this; + return this._name.map(function(name) { + let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`; if (name) { - uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)) + uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)); } - return uri - }) + return uri; + }); } } diff --git a/src/UI/BigComponents/SelectedElementTitle.svelte b/src/UI/BigComponents/SelectedElementTitle.svelte index 01c0bbaf9..600087dc7 100644 --- a/src/UI/BigComponents/SelectedElementTitle.svelte +++ b/src/UI/BigComponents/SelectedElementTitle.svelte @@ -46,7 +46,7 @@ > {#each layer.titleIcons as titleIconConfig} {#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ..._metatags, ..._tags } ) ?? true) && titleIconConfig.IsKnown(_tags)} -
+
+ import FeatureReviews from "../../Logic/Web/MangroveReviews"; + import SingleReview from "./SingleReview.svelte"; + import { Utils } from "../../Utils"; + import StarsBar from "./StarsBar.svelte"; + import ReviewForm from "./ReviewForm.svelte"; + import Translations from "../i18n/Translations"; + import Tr from "../Base/Tr.svelte"; + import type { SpecialVisualizationState } from "../SpecialVisualization"; + import { UIEventSource } from "../../Logic/UIEventSource"; + import type { Feature } from "geojson"; + import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; + import ToSvelte from "../Base/ToSvelte.svelte"; + import Svg from "../../Svg"; + + /** + * An element showing all reviews + */ + export let reviews: FeatureReviews; + export let state: SpecialVisualizationState; + export let tags: UIEventSource>; + export let feature: Feature; + export let layer: LayerConfig; + let average = reviews.average; + let _reviews = []; + reviews.reviews.addCallbackAndRunD(r => { + _reviews = Utils.NoNull(r); + }); + + + +
+ {#if _reviews.length > 1} + + {/if} + {#if _reviews.length > 0} + {#each _reviews as review} + + {/each} + {:else} + + {/if} +
+ + +
+
diff --git a/src/UI/Reviews/ReviewElement.ts b/src/UI/Reviews/ReviewElement.ts deleted file mode 100644 index efdf42fe8..000000000 --- a/src/UI/Reviews/ReviewElement.ts +++ /dev/null @@ -1,56 +0,0 @@ -import Combine from "../Base/Combine" -import Translations from "../i18n/Translations" -import SingleReview from "./SingleReview" -import BaseUIElement from "../BaseUIElement" -import Img from "../Base/Img" -import { VariableUiElement } from "../Base/VariableUIElement" -import Link from "../Base/Link" -import FeatureReviews from "../../Logic/Web/MangroveReviews" - -/** - * Shows the reviews and scoring base on mangrove.reviews - * The middle element is some other component shown in the middle, e.g. the review input element - */ -export default class ReviewElement extends VariableUiElement { - constructor(reviews: FeatureReviews, middleElement: BaseUIElement) { - super( - reviews.reviews.map( - (revs) => { - const elements = [] - revs.sort((a, b) => b.iat - a.iat) // Sort with most recent first - const avg = - revs.map((review) => review.rating).reduce((a, b) => a + b, 0) / revs.length - elements.push( - new Combine([ - SingleReview.GenStars(avg), - new Link( - revs.length === 1 - ? Translations.t.reviews.title_singular.Clone() - : Translations.t.reviews.title.Subs({ - count: "" + revs.length, - }), - `https://mangrove.reviews/search?sub=${encodeURIComponent( - reviews.subjectUri.data - )}`, - true - ), - ]).SetClass("font-2xl flex justify-between items-center pl-2 pr-2") - ) - - elements.push(middleElement) - - elements.push(...revs.map((review) => new SingleReview(review))) - elements.push( - new Combine([ - Translations.t.reviews.attribution.Clone(), - new Img("./assets/mangrove_logo.png"), - ]).SetClass("review-attribution") - ) - - return new Combine(elements).SetClass("block") - }, - [reviews.subjectUri] - ) - ) - } -} diff --git a/src/UI/Reviews/ReviewForm.svelte b/src/UI/Reviews/ReviewForm.svelte new file mode 100644 index 000000000..7949a457d --- /dev/null +++ b/src/UI/Reviews/ReviewForm.svelte @@ -0,0 +1,97 @@ + +{#if _state === "done"} + +{:else if _state === "saving"} + + + +{:else} +
+
+ +
+ {confirmedScore = e.detail.score}} on:hover={e => {score = e.detail.score}} + on:mouseout={e => {score = null}} score={score ?? confirmedScore ?? 0} + starSize="w-8 h-8"> + + {#if confirmedScore !== undefined} + +