From 8b5f5f7db184fad5abd0e1fc559d54e003566a04 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 7 Jul 2021 19:32:58 +0000 Subject: [PATCH 01/64] Translated using Weblate (Dutch) Currently translated at 48.8% (199 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/nl/ --- langs/themes/nl.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/langs/themes/nl.json b/langs/themes/nl.json index d6dd66dcc3..342ae1a183 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -240,7 +240,13 @@ }, "campersite": { "title": "Kampeersite", - "shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome" + "shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome", + "layers": { + "0": { + "name": "Camperplaatsen" + } + }, + "description": "Deze website verzamelt en toont alle officiële plaatsen waar een camper mag overnachten en afvalwater kan lozen. Ook jij kan extra gegevens toevoegen, zoals welke services er geboden worden en hoeveel dit kot, ook afbeeldingen en reviews kan je toevoegen. De data wordt op OpenStreetMap opgeslaan en is dus altijd gratis te hergebruiken, ook door andere applicaties." }, "climbing": { "title": "Open Klimkaart", @@ -987,4 +993,4 @@ } } } -} \ No newline at end of file +} From 2e40eb8881b9e533c3b56126c532a6a646062018 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 7 Jul 2021 19:34:47 +0000 Subject: [PATCH 02/64] Translated using Weblate (Dutch) Currently translated at 51.3% (209 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/nl/ --- langs/themes/nl.json | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 342ae1a183..6746eff92b 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -243,7 +243,37 @@ "shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome", "layers": { "0": { - "name": "Camperplaatsen" + "name": "Camperplaatsen", + "tagRenderings": { + "3": { + "question": "Hoeveel kost deze plaats?", + "render": "Deze plaats vraagt {charge}" + }, + "2": { + "mappings": { + "1": { + "then": "Kan gratis gebruikt worden" + }, + "0": { + "then": "Gebruik is betalend" + } + }, + "question": "Moet men betalen om deze camperplaats te gebruiken?" + }, + "1": { + "question": "Wat is de naam van deze plaats?", + "render": "Deze plaats heet {name}" + } + }, + "description": "camperplaatsen", + "title": { + "mappings": { + "0": { + "then": "Camper site" + } + }, + "render": "Camperplaats {name}" + } } }, "description": "Deze website verzamelt en toont alle officiële plaatsen waar een camper mag overnachten en afvalwater kan lozen. Ook jij kan extra gegevens toevoegen, zoals welke services er geboden worden en hoeveel dit kot, ook afbeeldingen en reviews kan je toevoegen. De data wordt op OpenStreetMap opgeslaan en is dus altijd gratis te hergebruiken, ook door andere applicaties." From 08e57a69b682a07669c7990de8f043b8089a79c5 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 8 Jul 2021 14:55:21 +0200 Subject: [PATCH 03/64] Fix input link --- Models/Constants.ts | 2 +- UI/BigComponents/UserBadge.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 312b5031f3..1bbe8f1cec 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.3b"; + public static vNumber = "0.8.3c"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/UI/BigComponents/UserBadge.ts b/UI/BigComponents/UserBadge.ts index cb84951dac..ab7292b4b6 100644 --- a/UI/BigComponents/UserBadge.ts +++ b/UI/BigComponents/UserBadge.ts @@ -68,7 +68,7 @@ export default class UserBadge extends Toggle { if (user.unreadMessages > 0) { messageSpan = new Link( new Combine([Svg.envelope, "" + user.unreadMessages]), - '${user.backend}/messages/inbox', + `${user.backend}/messages/inbox`, true ).SetClass("alert") } From a36168417e3478360a8c3e3c76b42fd49b8d3b18 Mon Sep 17 00:00:00 2001 From: Seppe Santens Date: Fri, 9 Jul 2021 16:16:35 +0200 Subject: [PATCH 04/64] Add OpenWindPowerMap theme --- .../themes/openwindpowermap/license_info.json | 12 ++ .../openwindpowermap/openwindpowermap.json | 165 ++++++++++++++++++ .../themes/openwindpowermap/wind_turbine.svg | 4 + 3 files changed, 181 insertions(+) create mode 100644 assets/themes/openwindpowermap/license_info.json create mode 100644 assets/themes/openwindpowermap/openwindpowermap.json create mode 100644 assets/themes/openwindpowermap/wind_turbine.svg diff --git a/assets/themes/openwindpowermap/license_info.json b/assets/themes/openwindpowermap/license_info.json new file mode 100644 index 0000000000..1ca28a0914 --- /dev/null +++ b/assets/themes/openwindpowermap/license_info.json @@ -0,0 +1,12 @@ +[ + { + "authors": [ + "Iconathon" + ], + "path": "wind_turbine.svg", + "license": "CC0", + "sources": [ + "https://commons.wikimedia.org/wiki/File:Wind_Turbine_(2076)_-_The_Noun_Project.svg" + ] + } +] \ No newline at end of file diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json new file mode 100644 index 0000000000..39c2baa412 --- /dev/null +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -0,0 +1,165 @@ +{ + "id": "openwindpowermap", + "title": { + "en": "OpenWindPowerMap" + }, + "maintainer": "Seppe Santens", + "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", + "description": { + "en": "A map for showing and editing wind turbines." + }, + "language": [ + "en" + ], + "version": "2021-06-18", + "startLat": 50.520, + "startLon": 4.643, + "startZoom": 8, + "clustering": { + "maxZoom": 8 + }, + "layers": [ + { + "id": "windturbine", + "name": { + "en": "wind turbine" + }, + "source": { + "osmTags": "generator:source=wind" + }, + "minzoom": 10, + "wayHandling": 1, + "title": { + "render": { + "en": "wind turbine" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "en": "{name}" + } + } + ] + }, + "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", + "iconSize": "40, 40, bottom", + "label": { + "mappings": [ + { + "if": "generator:output:electricity~^[0-9]+.*[W]$", + "then": "
{generator:output:electricity}
" + } + ] + }, + "tagRenderings": [ + { + "render": { + "en": "The power output of this wind turbine is {generator:output:electricity}." + }, + "question": { + "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" + }, + "freeform": { + "key": "generator:output:electricity" + } + }, + { + "render": { + "en": "This wind turbine is operated by {operator}." + }, + "question": { + "en": "Who operates this wind turbine?" + }, + "freeform": { + "key": "operator" + } + }, + { + "render": { + "en": "The total height (including rotor radius) of this wind turbine is {height} metres." + }, + "question": { + "en": "What is the total height of this wind turbine (including rotor radius), in metres?" + }, + "freeform": { + "key": "height", + "type": "float" + } + }, + { + "render": { + "en": "The rotor diameter of this wind turbine is {rotor:diameter} metres." + }, + "question": { + "en": "What is the rotor diameter of this wind turbine, in metres?" + }, + "freeform": { + "key": "rotor:diameter", + "type": "float" + } + }, + { + "render": { + "en": "This wind turbine went into operation on/in {start_date}." + }, + "question": { + "en": "When did this wind turbine go into operation?" + }, + "freeform": { + "key": "start_date", + "type": "date" + } + }, + "images" + ], + "presets": [ + { + "tags": [ + "power=generator", + "generator:source=wind" + ], + "title": { + "en": "wind turbine" + } + } + ] + } + ], + "units": [ + { + "appliesToKey": ["generator:output:electricity"], + "applicableUnits": [{ + "canonicalDenomination": "MW", + "alternativeDenomination": ["megawatts","megawatt"], + "human": { + "en": " megawatts", + "nl": " megawatt" + } + },{ + "canonicalDenomination": "kW", + "alternativeDenomination": ["kilowatts","kilowatt"], + "human": { + "en": " kilowatts", + "nl": " kilowatt" + } + },{ + "canonicalDenomination": "W", + "alternativeDenomination": ["watts","watt"], + "human": { + "en": " watts", + "nl": " watt" + } + },{ + "canonicalDenomination": "GW", + "alternativeDenomination": ["gigawatts","gigawatt"], + "human": { + "en": " gigawatts", + "nl": " gigawatt" + } + }], + "eraseInvalidValues": true + } + ], + "defaultBackgroundId": "CartoDB.Voyager" +} diff --git a/assets/themes/openwindpowermap/wind_turbine.svg b/assets/themes/openwindpowermap/wind_turbine.svg new file mode 100644 index 0000000000..a388b8faee --- /dev/null +++ b/assets/themes/openwindpowermap/wind_turbine.svg @@ -0,0 +1,4 @@ + \ No newline at end of file From 6d4af01c38130a4dcea732aa737f09ffc47a9b4e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 9 Jul 2021 17:46:10 +0200 Subject: [PATCH 05/64] Improve docs --- Docs/Making_Your_Own_Theme.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Docs/Making_Your_Own_Theme.md b/Docs/Making_Your_Own_Theme.md index 0a1cca965b..b669e986b7 100644 --- a/Docs/Making_Your_Own_Theme.md +++ b/Docs/Making_Your_Own_Theme.md @@ -55,12 +55,9 @@ The preferred way to add your theme is via a Pull Request. A Pull Request is les - If an SVG version is available, use the SVG version - Make sure all the links in `yourtheme.json` are updated. You can use `./assets/themes/yourtheme/yourimage.svg` instead of the HTML link - Create a file `license_info.json` in the theme directory, which contains metadata on every artwork source - 5) Add your theme to the code base: - - Open [AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/master/Customizations/AllKnownLayouts.ts) - - Add an import statement, e.g. `import * as yourtheme from "../assets/themes/yourtheme/yourthemes.json";` - - Add your theme to the `LayoutsList`, by adding a line `new LayoutConfig(yourtheme)` + 5) Add your theme to the code base: add it into "assets/themes" and make sure all the images are there too. Running 'ts-node scripts/fixTheme ' will help downloading the images and attempts to get the licenses if on wikimedia. 6) Add some finishing touches, such as a social image. See [this blog post](https://www.h3xed.com/web-and-internet/how-to-use-og-image-meta-tag-facebook-reddit) for some hints - 7) Test your theme: run the project as described [above](../README.md#Dev) + 7) Test your theme: run the project as described in [development_deployment](Development_deployment.md) 8) Happy with your theme? Time to open a Pull Request! 9) Thanks a lot for improving MapComplete! From 91df1d60f3c7970b6f0dd86d59d3b45cefdd0358 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 13:55:44 +0200 Subject: [PATCH 06/64] Added translation using Weblate (Finnish) --- langs/layers/fi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/layers/fi.json diff --git a/langs/layers/fi.json b/langs/layers/fi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/layers/fi.json @@ -0,0 +1 @@ +{} From 22288a1802795f87b19826caef28b38ef913a239 Mon Sep 17 00:00:00 2001 From: Irina Date: Fri, 9 Jul 2021 14:51:23 +0000 Subject: [PATCH 07/64] Translated using Weblate (Russian) Currently translated at 43.0% (240 of 557 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layer-translations/ru/ --- langs/layers/ru.json | 118 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 6dfc7c0d88..2c0480dcde 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -130,6 +130,9 @@ "mappings": { "0": { "then": "Прокат велосипедов бесплатен" + }, + "1": { + "then": "Прокат велосипеда стоит €20/год и €20 залог" } } }, @@ -199,7 +202,15 @@ "render": "Это велосипедное кафе называется {name}" }, "2": { - "question": "Есть ли в этом велосипедном кафе велосипедный насос для всеобщего использования?" + "question": "Есть ли в этом велосипедном кафе велосипедный насос для всеобщего использования?", + "mappings": { + "1": { + "then": "В этом велосипедном кафе нет велосипедного насоса для всеобщего использования" + }, + "0": { + "then": "В этом велосипедном кафе есть велосипедный насос для всеобщего использования" + } + } }, "5": { "question": "Какой сайт у {name}?" @@ -209,6 +220,31 @@ }, "7": { "question": "Какой адрес электронной почты у {name}?" + }, + "8": { + "question": "Каков режим работы этого велосипедного кафе?" + }, + "4": { + "mappings": { + "1": { + "then": "В этом велосипедном кафе нет услуг ремонта велосипедов" + }, + "0": { + "then": "В этом велосипедном кафе есть услуги ремонта велосипедов" + } + }, + "question": "Есть ли услуги ремонта велосипедов в этом велосипедном кафе?" + }, + "3": { + "mappings": { + "1": { + "then": "В этом велосипедном кафе нет инструментов для починки своего велосипеда" + }, + "0": { + "then": "В этом велосипедном кафе есть инструменты для починки своего велосипеда" + } + }, + "question": "Есть ли здесь инструменты для починки вашего велосипеда?" } }, "presets": { @@ -220,7 +256,8 @@ "bike_parking": { "tagRenderings": { "1": { - "question": "К какому типу относится эта велопарковка?" + "question": "К какому типу относится эта велопарковка?", + "render": "Это велопарковка типа {bicycle_parking}" }, "2": { "mappings": { @@ -236,7 +273,21 @@ } }, "5": { - "render": "{access}" + "render": "{access}", + "question": "Кто может пользоваться этой велопарковкой?" + }, + "4": { + "render": "Место для {capacity} велосипеда(ов)" + }, + "3": { + "mappings": { + "1": { + "then": "Это открытая парковка" + }, + "0": { + "then": "Это крытая парковка (есть крыша/навес)" + } + } } } }, @@ -297,6 +348,9 @@ "then": "Есть манометр, но он сломан" } } + }, + "3": { + "question": "Когда работает эта точка обслуживания велосипедов?" } }, "icon": { @@ -323,12 +377,14 @@ "4": { "then": "Магазин велосипедов {name}" } - } + }, + "render": "Обслуживание велосипедов/магазин" }, "description": "Магазин, специализирующийся на продаже велосипедов или сопутствующих товаров", "tagRenderings": { "2": { - "question": "Как называется магазин велосипедов?" + "question": "Как называется магазин велосипедов?", + "render": "Этот магазин велосипедов называется {name}" }, "3": { "question": "Какой сайт у {name}?" @@ -347,7 +403,8 @@ "1": { "then": "В этом магазине не продают велосипеды" } - } + }, + "question": "Продаются ли велосипеды в этом магазине?" }, "10": { "question": "В этом магазине ремонтируют велосипеды?", @@ -360,6 +417,9 @@ }, "2": { "then": "Этот магазин ремонтирует только велосипеды, купленные здесь" + }, + "3": { + "then": "В этом магазине обслуживают велосипеды определённого бренда" } } }, @@ -389,9 +449,37 @@ } }, "15": { - "question": "Здесь моют велосипеды?" + "question": "Здесь моют велосипеды?", + "mappings": { + "2": { + "then": "В этом магазине нет услуг мойки/чистки велосипедов" + }, + "0": { + "then": "В этом магазине оказываются услуги мойки/чистки велосипедов" + } + } + }, + "13": { + "question": "Предлагается ли в этом магазине велосипедный насос для всеобщего пользования?", + "mappings": { + "1": { + "then": "В этом магазине нет велосипедного насоса для всеобщего пользования" + }, + "0": { + "then": "В этом магазине есть велосипедный насос для всеобщего пользования" + } + } + }, + "14": { + "mappings": { + "2": { + "then": "Инструменты для починки доступны только при покупке/аренде велосипеда в магазине" + } + }, + "question": "Есть ли здесь инструменты для починки собственного велосипеда?" } - } + }, + "name": "Обслуживание велосипедов/магазин" }, "defibrillator": { "name": "Дефибрилляторы", @@ -424,6 +512,9 @@ "then": "Проверено сегодня!" } } + }, + "15": { + "render": "Дополнительная информация для экспертов OpenStreetMap: {fixme}" } } }, @@ -448,6 +539,12 @@ }, "4": { "render": "{inscription}" + }, + "5": { + "render": "Установлен {start_date}" + }, + "2": { + "render": "В знак памяти о {name}" } } }, @@ -783,5 +880,8 @@ "question": "Вы хотите добавить описание?" } } + }, + "bike_monitoring_station": { + "name": "Станции мониторинга" } -} \ No newline at end of file +} From 312d8eadd047ff2af3de1d4ae6afaceed5876e87 Mon Sep 17 00:00:00 2001 From: Artem Date: Fri, 9 Jul 2021 12:59:03 +0000 Subject: [PATCH 08/64] Translated using Weblate (Russian) Currently translated at 43.0% (240 of 557 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layer-translations/ru/ --- langs/layers/ru.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 2c0480dcde..d8905f92f7 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -350,7 +350,12 @@ } }, "3": { - "question": "Когда работает эта точка обслуживания велосипедов?" + "question": "Когда работает эта точка обслуживания велосипедов?", + "mappings": { + "0": { + "then": "Всегда открыто" + } + } } }, "icon": { From 0797b1370225d0b8935a857d92585ab04310c7d7 Mon Sep 17 00:00:00 2001 From: Nikolay Korotkiy Date: Fri, 9 Jul 2021 12:48:33 +0000 Subject: [PATCH 09/64] Translated using Weblate (Finnish) Currently translated at 4.8% (27 of 557 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layer-translations/fi/ --- langs/layers/fi.json | 104 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/langs/layers/fi.json b/langs/layers/fi.json index 0967ef424b..09fd6f9a85 100644 --- a/langs/layers/fi.json +++ b/langs/layers/fi.json @@ -1 +1,103 @@ -{} +{ + "bike_repair_station": { + "presets": { + "0": { + "title": "Pyöräpumppu" + } + }, + "icon": { + "render": "./assets/layers/bike_repair_station/repair_station.svg" + } + }, + "bike_parking": { + "tagRenderings": { + "5": { + "render": "{access}" + } + } + }, + "bench_at_pt": { + "tagRenderings": { + "1": { + "render": "{name}" + } + }, + "title": { + "render": "Penkki" + } + }, + "bench": { + "presets": { + "0": { + "description": "Lisää uusi penkki", + "title": "Penkki" + } + }, + "tagRenderings": { + "5": { + "mappings": { + "7": { + "then": "Väri: keltainen" + }, + "6": { + "then": "Väri: sininen" + }, + "5": { + "then": "Väri: musta" + }, + "4": { + "then": "Väri: punainen" + }, + "3": { + "then": "Väri: valkoinen" + }, + "2": { + "then": "Väri: harmaa" + }, + "1": { + "then": "Väri: vihreä" + }, + "0": { + "then": "Väri: ruskea" + } + }, + "render": "Väri: {colour}" + }, + "3": { + "mappings": { + "5": { + "then": "Materiaali: teräs" + }, + "4": { + "then": "Materiaali: muovi" + }, + "3": { + "then": "Materiaali: betoni" + }, + "2": { + "then": "Materiaali: kivi" + }, + "0": { + "then": "Materiaali: puu" + } + }, + "render": "Materiaali: {material}" + }, + "1": { + "mappings": { + "1": { + "then": "Selkänoja: ei" + }, + "0": { + "then": "Selkänoja: kyllä" + } + }, + "render": "Selkänoja" + } + }, + "title": { + "render": "Penkki" + }, + "name": "Penkit" + } +} From 296633bb4a3bbf467a31ba3cceb783e830ecf987 Mon Sep 17 00:00:00 2001 From: Nikolay Korotkiy Date: Fri, 9 Jul 2021 13:55:39 +0200 Subject: [PATCH 10/64] Added translation using Weblate (Finnish) --- langs/fi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/fi.json diff --git a/langs/fi.json b/langs/fi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/fi.json @@ -0,0 +1 @@ +{} From 22df75574165b38aaccf3b74693c3b1c38d36935 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 13:55:48 +0200 Subject: [PATCH 11/64] Added translation using Weblate (Finnish) --- langs/themes/fi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/themes/fi.json diff --git a/langs/themes/fi.json b/langs/themes/fi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/themes/fi.json @@ -0,0 +1 @@ +{} From eec689f24f05deaf26e59aee2bc3744331c7d9a7 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 13:55:53 +0200 Subject: [PATCH 12/64] Added translation using Weblate (Finnish) --- langs/shared-questions/fi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/shared-questions/fi.json diff --git a/langs/shared-questions/fi.json b/langs/shared-questions/fi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/shared-questions/fi.json @@ -0,0 +1 @@ +{} From 8a1e40937283dc3d494d12b75bc7e9f66558cf72 Mon Sep 17 00:00:00 2001 From: Irina Date: Fri, 9 Jul 2021 12:34:08 +0000 Subject: [PATCH 13/64] Translated using Weblate (Russian) Currently translated at 99.3% (165 of 166 strings) Translation: MapComplete/Core Translate-URL: https://hosted.weblate.org/projects/mapcomplete/core/ru/ --- langs/ru.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/langs/ru.json b/langs/ru.json index 6d24b6ec49..19feb61d8f 100644 --- a/langs/ru.json +++ b/langs/ru.json @@ -98,7 +98,7 @@ "fsGeolocation": "Включить кнопку \"найди меня\" (только в мобильной версии)", "fsSearch": "Включить строку поиска", "fsUserbadge": "Включить кнопку входа в систему", - "fsWelcomeMessage": "Показать всплывающее окно с приветствием и соответсвующие вкладки", + "fsWelcomeMessage": "Показать всплывающее окно с приветствием и соответствующие вкладки", "fsLayers": "Включить выбор слоя карты", "fsAddNew": "Включить кнопку \"добавить новую точку интереса\"", "fsLayerControlToggle": "Открыть панель выбора слоя", @@ -106,7 +106,7 @@ "editThisTheme": "Редактировать эту тему", "thanksForSharing": "Спасибо, что поделились!", "copiedToClipboard": "Ссылка скопирована в буфер обмена", - "embedIntro": "

Встроить на свой сайт

Пожалуйста, вставьте эту карту на свой сайт.
Мы призываем вас сделать это - вам даже не нужно спрашивать разрешения.
Она бесплатна и всегда будет бесплатной. Чем больше людей пользуются ею, тем более ценной она становится.", + "embedIntro": "

Встроить на свой сайт

Пожалуйста, вставьте эту карту на свой сайт.
Мы призываем вас сделать это - вам даже не нужно спрашивать разрешения.
Карта бесплатна и всегда будет бесплатной. Чем больше людей пользуются ею, тем более ценной она становится.", "addToHomeScreen": "

Добавить на домашний экран

Вы можете легко добавить этот сайт на домашний экран вашего смартфона. Для этого нажмите кнопку \"Добавить на главный экран\" в строке URL.", "intro": "

Поделиться этой картой

Поделитесь этой картой, скопировав ссылку ниже и отправив её друзьям и близким:" }, @@ -140,12 +140,12 @@ "doDelete": "Удалить изображение", "dontDelete": "Отмена", "uploadDone": "Ваше изображение добавлено. Спасибо за помощь!", - "respectPrivacy": "Не фотографируйте людей и номерные знаки. Не загружайте снимки Google Maps, Google Streetview и иные источники с закрытой лицензией.", + "respectPrivacy": "Не фотографируйте людей и номерные знаки. Не загружайте снимки Google Maps, Google Street View и иные источники с закрытой лицензией.", "uploadFailed": "Не удалось загрузить изображение. Проверьте, есть ли у вас доступ в Интернет и разрешены ли сторонние API? Браузеры Brave и UMatrix могут блокировать их.", "ccb": "под лицензией CC-BY", "ccbs": "под лицензией CC-BY-SA", "cco": "в открытом доступе", - "willBePublished": "Ваше изображение будет опубликоавано: ", + "willBePublished": "Ваше изображение будет опубликовано: ", "pleaseLogin": "Пожалуйста, войдите в систему, чтобы добавить изображение", "uploadingMultiple": "Загружаем {count} изображений…", "uploadingPicture": "Загружаем изображение…", From e83ddc0ff8e83e7a6480a3f799723a45457c98ea Mon Sep 17 00:00:00 2001 From: Irina Date: Fri, 9 Jul 2021 15:02:21 +0000 Subject: [PATCH 14/64] Translated using Weblate (Russian) Currently translated at 35.1% (143 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/ru/ --- langs/themes/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/langs/themes/ru.json b/langs/themes/ru.json index 8f9cd01a66..c2d70c10f3 100644 --- a/langs/themes/ru.json +++ b/langs/themes/ru.json @@ -276,6 +276,9 @@ "then": "Здесь нельзя утилизировать отходы химических туалетов" } } + }, + "6": { + "question": "Кто может использовать эту станцию утилизации?" } } } @@ -522,4 +525,4 @@ "trees": { "title": "Деревья" } -} \ No newline at end of file +} From a6af0c6fba67bdc62ef2dbc4d771134e628da288 Mon Sep 17 00:00:00 2001 From: Nikolay Korotkiy Date: Fri, 9 Jul 2021 12:08:44 +0000 Subject: [PATCH 15/64] Translated using Weblate (Finnish) Currently translated at 39.7% (66 of 166 strings) Translation: MapComplete/Core Translate-URL: https://hosted.weblate.org/projects/mapcomplete/core/fi/ --- langs/fi.json | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/langs/fi.json b/langs/fi.json index 0967ef424b..9b15cc0e69 100644 --- a/langs/fi.json +++ b/langs/fi.json @@ -1 +1,43 @@ -{} +{ + "general": { + "opening_hours": { + "ph_open": "avattu", + "ph_closed": "suljettu", + "ph_not_known": " " + }, + "weekdays": { + "sunday": "Sunnuntai", + "saturday": "Lauantai", + "friday": "Perjantai", + "thursday": "Torstai", + "wednesday": "Keskiviikko", + "tuesday": "Tiistai", + "monday": "Maanantai", + "abbreviations": { + "sunday": "Su", + "saturday": "La", + "friday": "Pe", + "thursday": "To", + "wednesday": "Ke", + "tuesday": "Ti", + "monday": "Ma" + } + }, + "backgroundMap": "Taustakartta", + "pickLanguage": "Valitse kieli: ", + "number": "numero", + "cancel": "Peruuta", + "save": "Tallenna", + "search": { + "searching": "Etsitään…" + } + }, + "centerMessage": { + "ready": "Valmis!" + }, + "image": { + "doDelete": "Poista kuva", + "dontDelete": "Peruuta", + "addPicture": "Lisää kuva" + } +} From e8bf46a759dd3a88222761194e8dd678c64dde84 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 9 Jul 2021 19:56:00 +0200 Subject: [PATCH 16/64] Fix bug: versions are reloaded now --- Logic/Osm/OsmObject.ts | 17 ++++++++++++----- Models/Constants.ts | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 6f9ec95b3e..d27eec5337 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -36,15 +36,22 @@ export abstract class OsmObject { this.backendURL = url; } - static DownloadObject(id): UIEventSource { + static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource { + let src : UIEventSource; if (OsmObject.objectCache.has(id)) { - return OsmObject.objectCache.get(id) + src = OsmObject.objectCache.get(id) + if(forceRefresh){ + src.setData(undefined) + }else{ + return src; + } + }else{ + src = new UIEventSource(undefined) } const splitted = id.split("/"); const type = splitted[0]; const idN = splitted[1]; - const src = new UIEventSource(undefined) OsmObject.objectCache.set(id, src); const newContinuation = (element: OsmObject) => { src.setData(element) @@ -158,11 +165,11 @@ export abstract class OsmObject { }) } - public static DownloadAll(neededIds): UIEventSource { + public static DownloadAll(neededIds, forceRefresh = true): UIEventSource { // local function which downloads all the objects one by one // this is one big loop, running one download, then rerunning the entire function - const allSources: UIEventSource [] = neededIds.map(id => OsmObject.DownloadObject(id)) + const allSources: UIEventSource [] = neededIds.map(id => OsmObject.DownloadObject(id, forceRefresh)) const allCompleted = new UIEventSource(undefined).map(_ => { return !allSources.some(uiEventSource => uiEventSource.data === undefined) }, allSources) diff --git a/Models/Constants.ts b/Models/Constants.ts index 1bbe8f1cec..79bcb5ca0a 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.3c"; + public static vNumber = "0.8.3d"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { From e11a5ca17babd47163a83ff7b269fc8bb928c83b Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 13:47:53 +0200 Subject: [PATCH 17/64] Attempting to add in backend to the element --- Logic/Osm/Changes.ts | 50 ++++++++++++++++++++------------------ Logic/Osm/OsmConnection.ts | 4 +-- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 4a4b00d356..7ebf4df9b8 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -6,31 +6,31 @@ import Constants from "../../Models/Constants"; import FeatureSource from "../FeatureSource/FeatureSource"; import {TagsFilter} from "../Tags/TagsFilter"; import {Tag} from "../Tags/Tag"; +import {OsmConnection} from "./OsmConnection"; /** * Handles all changes made to OSM. * Needs an authenticator via OsmConnection */ -export class Changes implements FeatureSource{ +export class Changes implements FeatureSource { - + + private static _nextId = -1; // Newly assigned ID's are negative public readonly name = "Newly added features" /** * The newly created points, as a FeatureSource */ - public features = new UIEventSource<{feature: any, freshness: Date}[]>([]); - - private static _nextId = -1; // Newly assigned ID's are negative + public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]); /** * All the pending changes */ - public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = - new UIEventSource<{elementId: string; key: string; value: string}[]>([]); + public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = + new UIEventSource<{ elementId: string; key: string; value: string }[]>([]); /** * Adds a change to the pending changes */ - private static checkChange(kv: {k: string, v: string}): { k: string, v: string } { + private static checkChange(kv: { k: string, v: string }): { k: string, v: string } { const key = kv.k; const value = kv.v; if (key === undefined || key === null) { @@ -49,8 +49,7 @@ export class Changes implements FeatureSource{ return {k: key.trim(), v: value.trim()}; } - - + addTag(elementId: string, tagsFilter: TagsFilter, tags?: UIEventSource) { const eventSource = tags ?? State.state?.allElements.getEventSourceById(elementId); @@ -59,7 +58,7 @@ export class Changes implements FeatureSource{ if (changes.length == 0) { return; } - + for (const change of changes) { if (elementTags[change.k] !== change.v) { elementTags[change.k] = change.v; @@ -76,16 +75,17 @@ export class Changes implements FeatureSource{ * Uploads all the pending changes in one go. * Triggered by the 'PendingChangeUploader'-actor in Actors */ - public flushChanges(flushreason: string = undefined){ - if(this.pending.data.length === 0){ + public flushChanges(flushreason: string = undefined) { + if (this.pending.data.length === 0) { return; } - if(flushreason !== undefined){ + if (flushreason !== undefined) { console.log(flushreason) } this.uploadAll([], this.pending.data); this.pending.setData([]); } + /** * Create a new node element at the given lat/long. * An internal OsmObject is created to upload later on, a geojson represention is returned. @@ -118,33 +118,37 @@ export class Changes implements FeatureSource{ // The tags are not yet written into the OsmObject, but this is applied onto a const changes = []; for (const kv of basicTags) { - properties[kv.key] = kv.value; if (typeof kv.value !== "string") { throw "Invalid value: don't use a regex in a preset" } + properties[kv.key] = kv.value; changes.push({elementId: id, key: kv.key, value: kv.value}) } - + console.log("New feature added and pinged") - this.features.data.push({feature:geojson, freshness: new Date()}); + this.features.data.push({feature: geojson, freshness: new Date()}); this.features.ping(); - + State.state.allElements.addOrGetElement(geojson).ping(); - this.uploadAll([osmNode], changes); + if (State.state.osmConnection.userDetails.data.backend !== OsmConnection.oauth_configs.osm.url) { + properties["_backend"] = State.state.osmConnection.userDetails.data.backend + } + + // this.uploadAll([osmNode], changes); return geojson; } private uploadChangesWithLatestVersions( knownElements: OsmObject[], newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { const knownById = new Map(); - + knownElements.forEach(knownElement => { - console.log("Setting ",knownElement.type + knownElement.id, knownElement) + console.log("Setting ", knownElement.type + knownElement.id, knownElement) knownById.set(knownElement.type + "/" + knownElement.id, knownElement) }) - - + + // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements // We apply the changes on them for (const change of pending) { diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index a3df9be9fd..decc8a09cb 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -30,7 +30,7 @@ export default class UserDetails { export class OsmConnection { - public static readonly _oauth_configs = { + public static readonly oauth_configs = { "osm": { oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', @@ -66,7 +66,7 @@ export class OsmConnection { osmConfiguration: "osm" | "osm-test" = 'osm' ) { this._singlePage = singlePage; - this._oauth_config = OsmConnection._oauth_configs[osmConfiguration] ?? OsmConnection._oauth_configs.osm; + this._oauth_config = OsmConnection.oauth_configs[osmConfiguration] ?? OsmConnection.oauth_configs.osm; console.debug("Using backend", this._oauth_config.url) OsmObject.SetBackendUrl(this._oauth_config.url + "/") this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; From 622b48f32e7207a33956037071ea6178eee2126e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 13:57:40 +0200 Subject: [PATCH 18/64] Fix bug --- Logic/Osm/Changes.ts | 3 --- Logic/Osm/OsmObject.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 4a4b00d356..c9efc979b2 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -140,7 +140,6 @@ export class Changes implements FeatureSource{ const knownById = new Map(); knownElements.forEach(knownElement => { - console.log("Setting ",knownElement.type + knownElement.id, knownElement) knownById.set(knownElement.type + "/" + knownElement.id, knownElement) }) @@ -156,8 +155,6 @@ export class Changes implements FeatureSource{ } } } else { - console.log(knownById, change.elementId) - knownById.get(change.elementId).addTag(change.key, change.value); } } diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index d27eec5337..e8f2047591 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -177,7 +177,7 @@ export abstract class OsmObject { if (completed) { return allSources.map(src => src.data) } - return [] + return undefined }); } From d8c6bff2558f150b889ea637301e69a810fc4e5a Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 14:01:33 +0200 Subject: [PATCH 19/64] Version bump --- Models/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 79bcb5ca0a..34859e6142 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.3d"; + public static vNumber = "0.8.3e"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { From 74579d8170542d851e189be18fbfb7c7637e15ae Mon Sep 17 00:00:00 2001 From: Raphael Das Gupta Date: Fri, 9 Jul 2021 18:41:07 +0200 Subject: [PATCH 20/64] Added translation using Weblate (Esperanto) --- langs/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/eo.json diff --git a/langs/eo.json b/langs/eo.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/eo.json @@ -0,0 +1 @@ +{} From b6dea319885b5ac4ef9a7ec21275bf8a30594e77 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 18:41:17 +0200 Subject: [PATCH 21/64] Added translation using Weblate (Esperanto) --- langs/themes/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/themes/eo.json diff --git a/langs/themes/eo.json b/langs/themes/eo.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/themes/eo.json @@ -0,0 +1 @@ +{} From 0cb5e7b716e953132652780000f350ad6c2df6eb Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 18:41:19 +0200 Subject: [PATCH 22/64] Added translation using Weblate (Esperanto) --- langs/shared-questions/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/shared-questions/eo.json diff --git a/langs/shared-questions/eo.json b/langs/shared-questions/eo.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/shared-questions/eo.json @@ -0,0 +1 @@ +{} From 74e7f9ae525741c96e9e80e56048dc1fe1f0c635 Mon Sep 17 00:00:00 2001 From: seppesantens Date: Sat, 10 Jul 2021 07:41:08 +0000 Subject: [PATCH 23/64] Translated using Weblate (Dutch) Currently translated at 55.2% (225 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/nl/ --- langs/themes/nl.json | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 6746eff92b..b23136e56b 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -1022,5 +1022,61 @@ } } } + }, + "charging_stations": { + "layers": { + "0": { + "description": "Een oplaadpunt", + "title": { + "render": "Oplaadpunt" + }, + "name": "Oplaadpunten" + } + }, + "title": "Oplaadpunten" + }, + "shops": { + "layers": { + "0": { + "tagRenderings": { + "4": { + "question": "Wat is de website van deze winkel?" + }, + "3": { + "question": "Wat is het telefoonnummer?" + }, + "2": { + "mappings": { + "4": { + "then": "Bakkerij" + }, + "3": { + "then": "Kapper" + }, + "1": { + "then": "Supermarkt" + } + } + }, + "1": { + "question": "Wat is de naam van deze winkel?" + }, + "6": { + "question": "Wat zijn de openingsuren van deze winkel?" + } + }, + "description": "Een winkel", + "title": { + "render": "Winkel" + }, + "name": "Winkel", + "presets": { + "0": { + "title": "Winkel", + "description": "Voeg een nieuwe winkel toe" + } + } + } + } } } From 6179a26812b35a4d895ecdde75d5eb111e05e062 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 9 Jul 2021 18:41:14 +0200 Subject: [PATCH 24/64] Added translation using Weblate (Esperanto) --- langs/layers/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 langs/layers/eo.json diff --git a/langs/layers/eo.json b/langs/layers/eo.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/langs/layers/eo.json @@ -0,0 +1 @@ +{} From cddea6ced9a9eb89705168952d3e83e4116b5db1 Mon Sep 17 00:00:00 2001 From: Irina Date: Fri, 9 Jul 2021 19:45:47 +0000 Subject: [PATCH 25/64] Translated using Weblate (Russian) Currently translated at 54.3% (303 of 557 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layer-translations/ru/ --- langs/layers/ru.json | 209 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 193 insertions(+), 16 deletions(-) diff --git a/langs/layers/ru.json b/langs/layers/ru.json index d8905f92f7..b97d98281d 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -609,14 +609,17 @@ "1": { "then": "Это бетонный стол для пикника" } - } + }, + "render": "Этот стол для пикника сделан из {material}", + "question": "Из чего изготовлен этот стол для пикника?" } }, "presets": { "0": { "title": "Стол для пикника" } - } + }, + "description": "Слой, отображающий столы для пикника" }, "playground": { "name": "Детские площадки", @@ -647,11 +650,15 @@ }, "5": { "then": "Поверхность - бетон" + }, + "2": { + "then": "Покрытие из щепы" } } }, "3": { - "render": "Доступно для детей старше {min_age} лет" + "render": "Доступно для детей старше {min_age} лет", + "question": "С какого возраста доступна эта детская площадка?" }, "6": { "mappings": { @@ -673,8 +680,40 @@ }, "2": { "then": "Всегда доступен" + }, + "0": { + "then": "Открыто от рассвета до заката" } - } + }, + "question": "Когда открыта эта игровая площадка?" + }, + "9": { + "mappings": { + "2": { + "then": "Недоступна пользователям кресел-колясок" + }, + "1": { + "then": "Частично доступна пользователям кресел-колясок" + }, + "0": { + "then": "Полностью доступна пользователям кресел-колясок" + } + }, + "question": "Доступна ли детская площадка пользователям кресел-колясок?" + }, + "4": { + "render": "Доступно детям до {max_age}" + }, + "2": { + "mappings": { + "1": { + "then": "Эта детская площадка не освещается ночью" + }, + "0": { + "then": "Эта детская площадка освещается ночью" + } + }, + "question": "Эта игровая площадка освещается ночью?" } }, "presets": { @@ -701,7 +740,7 @@ "tagRenderings": { "2": { "render": "Название книжного шкафа — {name}", - "question": "Как называется общественный книжный шкаф?", + "question": "Как называется этот общественный книжный шкаф?", "mappings": { "0": { "then": "У этого книжного шкафа нет названия" @@ -709,7 +748,8 @@ } }, "3": { - "question": "Сколько книг помещается в этом общественном книжном шкафу?" + "question": "Сколько книг помещается в этом общественном книжном шкафу?", + "render": "{capacity} книг помещается в этот книжный шкаф" }, "4": { "mappings": { @@ -718,13 +758,31 @@ }, "1": { "then": "В основном книги для взрослых" + }, + "2": { + "then": "Книги и для детей, и для взрослых" } - } + }, + "question": "Какие книги можно найти в этом общественном книжном шкафу?" }, "11": { - "render": "Более подробная информация на сайте" + "render": "Более подробная информация на сайте", + "question": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?" + }, + "10": { + "render": "Установлен {start_date}", + "question": "Когда был установлен этот общественный книжный шкаф?" + }, + "6": { + "mappings": { + "0": { + "then": "Свободный доступ" + } + }, + "question": "Имеется ли свободный доступ к этому общественному книжному шкафу?" } - } + }, + "description": "Уличный шкаф с книгами, доступными для всех" }, "slow_roads": { "tagRenderings": { @@ -763,6 +821,21 @@ "mappings": { "2": { "then": "Это стол для пинг-понга" + }, + "5": { + "then": "Здесь можно играть в баскетбол" + }, + "4": { + "then": "Здесь можно играть в корфбол" + }, + "3": { + "then": "Здесь можно играть в теннис" + }, + "1": { + "then": "Здесь можно играть в футбол" + }, + "0": { + "then": "Здесь можно играть в баскетбол" } } }, @@ -784,21 +857,55 @@ "4": { "then": "Поверхность - бетон" } - } + }, + "question": "Какое покрытие на этой спортивной площадке?" }, "7": { "mappings": { "1": { "then": "Всегда доступен" } - } + }, + "question": "В какое время доступна эта площадка?" + }, + "4": { + "mappings": { + "1": { + "then": "Желательна предварительная запись для доступа на эту спортивную площадку" + }, + "3": { + "then": "Невозможна предварительная запись" + }, + "2": { + "then": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна" + } + }, + "question": "Нужна ли предварительная запись для доступа на эту спортивную площадку?" + }, + "3": { + "mappings": { + "2": { + "then": "Доступ только членам клуба" + }, + "1": { + "then": "Ограниченный доступ (напр., только по записи, в определённые часы, ...)" + }, + "0": { + "then": "Свободный доступ" + } + }, + "question": "Есть ли свободный доступ к этой спортивной площадке?" } }, "presets": { "1": { "title": "Спортивная площадка" + }, + "0": { + "title": "Стол для настольного тенниса" } - } + }, + "description": "Спортивная площадка" }, "surveillance_camera": { "name": "Камеры наблюдения", @@ -810,6 +917,23 @@ "mappings": { "2": { "then": "Панорамная камера" + }, + "1": { + "then": "Камера с поворотным механизмом" + } + }, + "question": "Какая это камера?" + }, + "8": { + "question": "Как расположена эта камера?" + }, + "5": { + "mappings": { + "2": { + "then": "Возможно, эта камера расположена снаружи" + }, + "1": { + "then": "Эта камера расположена снаружи" } } } @@ -822,7 +946,11 @@ }, "presets": { "0": { - "title": "Туалет" + "title": "Туалет", + "description": "Туалет или комната отдыха со свободным доступом" + }, + "1": { + "title": "Туалет с доступом для пользователей кресел-колясок" } }, "tagRenderings": { @@ -830,8 +958,12 @@ "mappings": { "2": { "then": "Недоступно" + }, + "0": { + "then": "Свободный доступ" } - } + }, + "question": "Есть ли свободный доступ к этим туалетам?" }, "2": { "mappings": { @@ -839,6 +971,20 @@ "then": "Это платные туалеты" } } + }, + "5": { + "question": "Какие это туалеты?" + }, + "4": { + "mappings": { + "1": { + "then": "Недоступно пользователям кресел-колясок" + } + } + }, + "3": { + "render": "Стоимость {charge}", + "question": "Сколько стоит посещение туалета?" } } }, @@ -862,12 +1008,43 @@ } }, "5": { - "render": "Название: {name}" + "render": "Название: {name}", + "mappings": { + "0": { + "then": "У этого дерева нет названия." + } + }, + "question": "Есть ли у этого дерева название?" + }, + "8": { + "render": "\"\"/ Wikidata: {wikidata}" + }, + "7": { + "render": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}" + }, + "4": { + "mappings": { + "1": { + "then": "Вечнозелёное." + }, + "0": { + "then": "Листопадное: у дерева опадают листья в определённое время года." + } + }, + "question": "Это дерево вечнозелёное или листопадное?" } }, "presets": { "2": { - "title": "Дерево" + "title": "Дерево", + "description": "Если вы не уверены в том, лиственное это дерево или хвойное." + }, + "1": { + "description": "Дерево с хвоей (иглами), например, сосна или ель.", + "title": "Хвойное дерево" + }, + "0": { + "title": "Лиственное дерево" } } }, From 6732c12a0c212135a08e154d80c3f7dd6ab6bf2e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 15:52:52 +0200 Subject: [PATCH 26/64] First version which caches changesets if not uploaded --- Logic/Osm/Changes.ts | 54 +++++++++++++++++++++------------ Logic/Osm/ChangesetHandler.ts | 21 ++++++------- Logic/Osm/OsmConnection.ts | 5 +-- Logic/Osm/OsmObject.ts | 19 ++++++++---- Logic/Web/LocalStorageSource.ts | 16 ++++++++++ Models/Constants.ts | 2 +- assets/tagRenderings/icons.json | 8 +++-- 7 files changed, 84 insertions(+), 41 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index de7c5dc0e0..9da36b048a 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -7,6 +7,7 @@ import FeatureSource from "../FeatureSource/FeatureSource"; import {TagsFilter} from "../Tags/TagsFilter"; import {Tag} from "../Tags/Tag"; import {OsmConnection} from "./OsmConnection"; +import {LocalStorageSource} from "../Web/LocalStorageSource"; /** * Handles all changes made to OSM. @@ -24,8 +25,13 @@ export class Changes implements FeatureSource { /** * All the pending changes */ - public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = - new UIEventSource<{ elementId: string; key: string; value: string }[]>([]); + public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = LocalStorageSource.GetParsed("pending-changes", []) + + /** + * All the pending new objects to upload + * @private + */ + private readonly newObjects: UIEventSource<{ id: number, lat: number, lon: number }[]> = LocalStorageSource.GetParsed("newObjects", []) /** * Adds a change to the pending changes @@ -82,8 +88,7 @@ export class Changes implements FeatureSource { if (flushreason !== undefined) { console.log(flushreason) } - this.uploadAll([], this.pending.data); - this.pending.setData([]); + this.uploadAll(); } /** @@ -93,12 +98,12 @@ export class Changes implements FeatureSource { */ public createElement(basicTags: Tag[], lat: number, lon: number) { console.log("Creating a new element with ", basicTags) - const osmNode = new OsmNode(Changes._nextId); + const newId = Changes._nextId; Changes._nextId--; - const id = "node/" + osmNode.id; - osmNode.lat = lat; - osmNode.lon = lon; + const id = "node/" + newId; + + const properties = {id: id}; const geojson = { @@ -135,22 +140,32 @@ export class Changes implements FeatureSource { properties["_backend"] = State.state.osmConnection.userDetails.data.backend } - // this.uploadAll([osmNode], changes); + + this.newObjects.data.push({id: newId, lat: lat, lon: lon}) + this.pending.data.push(...changes) + this.pending.ping(); + this.newObjects.ping(); return geojson; } private uploadChangesWithLatestVersions( - knownElements: OsmObject[], newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { + knownElements: OsmObject[]) { const knownById = new Map(); - knownElements.forEach(knownElement => { knownById.set(knownElement.type + "/" + knownElement.id, knownElement) }) + const newElements: OsmNode [] = this.newObjects.data.map(spec => { + const newElement = new OsmNode(spec.id); + newElement.lat = spec.lat; + newElement.lon = spec.lon; + return newElement + }) + // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements // We apply the changes on them - for (const change of pending) { + for (const change of this.pending.data) { if (parseInt(change.elementId.split("/")[1]) < 0) { // This is a new element - we should apply this on one of the new elements for (const newElement of newElements) { @@ -217,17 +232,19 @@ export class Changes implements FeatureSource { changes += ""; return changes; + }, + () => { + console.log("Upload successfull!") + this.newObjects.setData([]) + this.pending.setData([]); }); }; - private uploadAll( - newElements: OsmObject[], - pending: { elementId: string; key: string; value: string }[] - ) { + private uploadAll() { const self = this; - + const pending = this.pending.data; let neededIds: string[] = []; for (const change of pending) { const id = change.elementId; @@ -240,8 +257,7 @@ export class Changes implements FeatureSource { neededIds = Utils.Dedup(neededIds); OsmObject.DownloadAll(neededIds).addCallbackAndRunD(knownElements => { - console.log("KnownElements:", knownElements) - self.uploadChangesWithLatestVersions(knownElements, newElements, pending) + self.uploadChangesWithLatestVersions(knownElements) }) } diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index ef9f5f717c..98d45a790c 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -27,7 +27,7 @@ export class ChangesetHandler { } } - private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) { + private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) : void{ const nodes = response.getElementsByTagName("node"); // @ts-ignore for (const node of nodes) { @@ -69,7 +69,8 @@ export class ChangesetHandler { public UploadChangeset( layout: LayoutConfig, allElements: ElementStorage, - generateChangeXML: (csid: string) => string) { + generateChangeXML: (csid: string) => string, + whenDone : (csId: string) => void) { if (this.userDetails.data.csCount == 0) { // The user became a contributor! @@ -80,6 +81,7 @@ export class ChangesetHandler { if (this._dryRun) { const changesetXML = generateChangeXML("123456"); console.log(changesetXML); + whenDone("123456") return; } @@ -93,8 +95,7 @@ export class ChangesetHandler { console.log(changeset); self.AddChange(csId, changeset, allElements, - () => { - }, + whenDone, (e) => { console.error("UPLOADING FAILED!", e) } @@ -107,14 +108,13 @@ export class ChangesetHandler { csId, generateChangeXML(csId), allElements, - () => { - }, + whenDone, (e) => { console.warn("Could not upload, changeset is probably closed: ", e); // Mark the CS as closed... this.currentChangeset.setData(""); // ... and try again. As the cs is closed, no recursive loop can exist - self.UploadChangeset(layout, allElements, generateChangeXML); + self.UploadChangeset(layout, allElements, generateChangeXML, whenDone); } ) @@ -244,7 +244,6 @@ export class ChangesetHandler { }, function (err, response) { if (response === undefined) { console.log("err", err); - alert("Could not upload change (opening failed). Please file a bug report") return; } else { continuation(response); @@ -265,7 +264,7 @@ export class ChangesetHandler { private AddChange(changesetId: string, changesetXML: string, allElements: ElementStorage, - continuation: ((changesetId: string, idMapping: any) => void), + continuation: ((changesetId: string) => void), onFail: ((changesetId: string, reason: string) => void) = undefined) { this.auth.xhr({ method: 'POST', @@ -280,9 +279,9 @@ export class ChangesetHandler { } return; } - const mapping = ChangesetHandler.parseUploadChangesetResponse(response, allElements); + ChangesetHandler.parseUploadChangesetResponse(response, allElements); console.log("Uploaded changeset ", changesetId); - continuation(changesetId, mapping); + continuation(changesetId); }); } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index decc8a09cb..cba332447f 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -110,8 +110,9 @@ export class OsmConnection { public UploadChangeset( layout: LayoutConfig, allElements: ElementStorage, - generateChangeXML: (csid: string) => string) { - this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML); + generateChangeXML: (csid: string) => string, + whenDone: (csId: string) => void) { + this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone); } public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index e8f2047591..09ee7137c7 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -5,7 +5,8 @@ import {UIEventSource} from "../UIEventSource"; export abstract class OsmObject { - protected static backendURL = "https://www.openstreetmap.org/" + private static defaultBackend = "https://www.openstreetmap.org/" + protected static backendURL = OsmObject.defaultBackend; private static polygonFeatures = OsmObject.constructPolygonFeatures() private static objectCache = new Map>(); private static referencingWaysCache = new Map>(); @@ -37,15 +38,15 @@ export abstract class OsmObject { } static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource { - let src : UIEventSource; + let src: UIEventSource; if (OsmObject.objectCache.has(id)) { src = OsmObject.objectCache.get(id) - if(forceRefresh){ + if (forceRefresh) { src.setData(undefined) - }else{ + } else { return src; } - }else{ + } else { src = new UIEventSource(undefined) } const splitted = id.split("/"); @@ -157,7 +158,7 @@ export abstract class OsmObject { const minlat = bounds[1][0] const maxlat = bounds[0][0]; const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}` - Utils.downloadJson(url).then( data => { + Utils.downloadJson(url).then(data => { const elements: any[] = data.elements; const objects = OsmObject.ParseObjects(elements) callback(objects); @@ -291,6 +292,7 @@ export abstract class OsmObject { self.LoadData(element) self.SaveExtraData(element, nodes); + const meta = { "_last_edit:contributor": element.user, "_last_edit:contributor:uid": element.uid, @@ -299,6 +301,11 @@ export abstract class OsmObject { "_version_number": element.version } + if (OsmObject.backendURL !== OsmObject.defaultBackend) { + self.tags["_backend"] = OsmObject.backendURL + meta["_backend"] = OsmObject.backendURL; + } + continuation(self, meta); } ); diff --git a/Logic/Web/LocalStorageSource.ts b/Logic/Web/LocalStorageSource.ts index 050b124591..a89d2a5569 100644 --- a/Logic/Web/LocalStorageSource.ts +++ b/Logic/Web/LocalStorageSource.ts @@ -4,6 +4,22 @@ import {UIEventSource} from "../UIEventSource"; * UIEventsource-wrapper around localStorage */ export class LocalStorageSource { + + static GetParsed(key: string, defaultValue : any){ + return LocalStorageSource.Get(key).map( + str => { + if(str === undefined){ + return defaultValue + } + try{ + return JSON.parse(str) + }catch{ + return defaultValue + } + }, [], + value => JSON.stringify(value) + ) + } static Get(key: string, defaultValue: string = undefined): UIEventSource { try { diff --git a/Models/Constants.ts b/Models/Constants.ts index 79bcb5ca0a..33a52c40f6 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.3d"; + public static vNumber = "0.8.4"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/assets/tagRenderings/icons.json b/assets/tagRenderings/icons.json index e0bde24dad..5dbe8856a3 100644 --- a/assets/tagRenderings/icons.json +++ b/assets/tagRenderings/icons.json @@ -59,8 +59,12 @@ "render": "", "mappings": [ { - "if": "id~=-", - "then": "Uploading..." + "if": "id~.*/-.*", + "then": "" + }, + { + "if": "_backend~*", + "then": "" } ], "condition": "id~(node|way|relation)/[0-9]*" From 7ba6a82b354ded009351080775661e6f87e1659c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 19:18:51 +0200 Subject: [PATCH 27/64] Remove translation completeness check from layer overview generation --- scripts/generateLayerOverview.ts | 62 -------------------------------- 1 file changed, 62 deletions(-) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index b83b28be9b..7da47f21c4 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -77,63 +77,6 @@ class LayerOverviewUtils { return errorCount } - validateTranslationCompletenessOfObject(object: any, expectedLanguages: string[], context: string) { - const missingTranlations = [] - const translations: { tr: Translation, context: string }[] = []; - const queue: { object: any, context: string }[] = [{object: object, context: context}] - - while (queue.length > 0) { - const item = queue.pop(); - const o = item.object - for (const key in o) { - const v = o[key]; - if (v === undefined) { - continue; - } - if (v instanceof Translation || v?.translations !== undefined) { - translations.push({tr: v, context: item.context}); - } else if ( - ["string", "function", "boolean", "number"].indexOf(typeof (v)) < 0) { - queue.push({object: v, context: item.context + "." + key}) - } - } - } - - const missing = {} - const present = {} - for (const ln of expectedLanguages) { - missing[ln] = 0; - present[ln] = 0; - for (const translation of translations) { - if (translation.tr.translations["*"] !== undefined) { - continue; - } - const txt = translation.tr.translations[ln]; - const isMissing = txt === undefined || txt === "" || txt.toLowerCase().indexOf("todo") >= 0; - if (isMissing) { - missingTranlations.push(`${translation.context},${ln},${translation.tr.txt}`) - missing[ln]++ - } else { - present[ln]++; - } - } - } - - let message = `Translation completeness for ${context}` - let isComplete = true; - for (const ln of expectedLanguages) { - const amiss = missing[ln]; - const ok = present[ln]; - const total = amiss + ok; - message += ` ${ln}: ${ok}/${total}` - if (ok !== total) { - isComplete = false; - } - } - return missingTranlations - - } - main(args: string[]) { const lt = this.loadThemesAndLayers(); @@ -198,11 +141,6 @@ class LayerOverviewUtils { } } - if (missingTranslations.length > 0) { - console.log(missingTranslations.length, "missing translations") - writeFileSync("missing_translations.txt", missingTranslations.join("\n")) - } - if (layerErrorCount.length + themeErrorCount.length == 0) { console.log("All good!") From 3d7b8ab5648288997dc8d8acc27512fd68c8eb11 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 19:19:23 +0200 Subject: [PATCH 28/64] Remove translation completeness check from layer overview generation --- scripts/generateLayerOverview.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 7da47f21c4..545ebf8444 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -4,7 +4,6 @@ import LayerConfig from "../Customizations/JSON/LayerConfig"; import * as licenses from "../assets/generated/license_info.json" import LayoutConfig from "../Customizations/JSON/LayoutConfig"; import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson"; -import {Translation} from "../UI/i18n/Translation"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; import AllKnownLayers from "../Customizations/AllKnownLayers"; @@ -108,10 +107,6 @@ class LayerOverviewUtils { if (typeof layer === "string") { if (!knownLayerIds.has(layer)) { themeErrorCount.push(`Unknown layer id: ${layer} in theme ${themeFile.id}`) - } else { - const layerConfig = knownLayerIds.get(layer); - missingTranslations.push(...this.validateTranslationCompletenessOfObject(layerConfig, themeFile.language, "Layer " + layer)) - } } else { if (layer.builtin !== undefined) { @@ -129,7 +124,6 @@ class LayerOverviewUtils { .filter(l => typeof l != "string") // We remove all the builtin layer references as they don't work with ts-node for some weird reason .filter(l => l.builtin === undefined) - missingTranslations.push(...this.validateTranslationCompletenessOfObject(themeFile, themeFile.language, "Theme " + themeFile.id)) try { const theme = new LayoutConfig(themeFile, true, "test") From fbffb367a72436c727859d66397a72d2ada61cef Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 19:21:27 +0200 Subject: [PATCH 29/64] Fix wikidata icon --- assets/tagRenderings/icons.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/tagRenderings/icons.json b/assets/tagRenderings/icons.json index 5dbe8856a3..c1b9d5e273 100644 --- a/assets/tagRenderings/icons.json +++ b/assets/tagRenderings/icons.json @@ -1,15 +1,15 @@ { "wikipedialink": { "render": "WP", - "condition": "wikipedia~*", + "condition": { + "or": [ + "wikipedia~*", + "wikidata~*" + ] + }, "mappings": [ { - "if": { - "and": [ - "wikipedia=", - "wikidata~*" - ] - }, + "if": "wikipedia=", "then": "WD" } ] From 9bf78a62e49e282fa35c196e1b6256db7df1819c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 19:21:56 +0200 Subject: [PATCH 30/64] Remove unused file --- tslint.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tslint.json diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 6a204a045b..0000000000 --- a/tslint.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended", - "tslint-no-circular-imports" - ], - "jsRules": {}, - "rules": {}, - "rulesDirectory": [] -} \ No newline at end of file From 80cb9efaf05314f1759f79ddafe5a0ee16de817c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 20:56:59 +0200 Subject: [PATCH 31/64] Translation sync --- Models/TileRange.ts | 9 + assets/layers/bench/bench.json | 66 ++-- assets/layers/bench_at_pt/bench_at_pt.json | 6 +- .../bicycle_library/bicycle_library.json | 3 +- assets/layers/bike_cafe/bike_cafe.json | 27 +- .../bike_monitoring_station.json | 3 +- assets/layers/bike_parking/bike_parking.json | 18 +- .../bike_repair_station.json | 12 +- assets/layers/bike_shop/bike_shop.json | 36 +- .../layers/defibrillator/defibrillator.json | 3 +- assets/layers/ghost_bike/ghost_bike.json | 6 +- assets/themes/campersites/campersites.json | 39 +- .../openwindpowermap/openwindpowermap.json | 344 +++++++++--------- langs/layers/fi.json | 172 ++++----- langs/layers/ru.json | 148 ++++---- langs/themes/en.json | 62 ++++ langs/themes/nl.json | 72 ++-- langs/themes/ru.json | 2 +- 18 files changed, 606 insertions(+), 422 deletions(-) create mode 100644 Models/TileRange.ts diff --git a/Models/TileRange.ts b/Models/TileRange.ts new file mode 100644 index 0000000000..f3c59af238 --- /dev/null +++ b/Models/TileRange.ts @@ -0,0 +1,9 @@ +export interface TileRange { + xstart: number, + ystart: number, + xend: number, + yend: number, + total: number, + zoomlevel: number + +} \ No newline at end of file diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index 2ba7c03581..312b21cfcc 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -12,7 +12,8 @@ "ru": "Скамейки", "zh_Hans": "长椅", "zh_Hant": "長椅", - "nb_NO": "Benker" + "nb_NO": "Benker", + "fi": "Penkit" }, "minzoom": 14, "source": { @@ -31,7 +32,8 @@ "ru": "Скамейка", "zh_Hans": "长椅", "zh_Hant": "長椅", - "nb_NO": "Benk" + "nb_NO": "Benk", + "fi": "Penkki" } }, "tagRenderings": [ @@ -49,7 +51,8 @@ "ru": "Спинка", "zh_Hans": "靠背", "zh_Hant": "靠背", - "nb_NO": "Rygglene" + "nb_NO": "Rygglene", + "fi": "Selkänoja" }, "freeform": { "key": "backrest" @@ -69,7 +72,8 @@ "ru": "Со спинкой", "zh_Hans": "靠背:有", "zh_Hant": "靠背:有", - "nb_NO": "Rygglene: Ja" + "nb_NO": "Rygglene: Ja", + "fi": "Selkänoja: kyllä" } }, { @@ -86,7 +90,8 @@ "ru": "Без спинки", "zh_Hans": "靠背:无", "zh_Hant": "靠背:無", - "nb_NO": "Rygglene: Nei" + "nb_NO": "Rygglene: Nei", + "fi": "Selkänoja: ei" } } ], @@ -149,7 +154,8 @@ "ru": "Материал: {material}", "zh_Hans": "材质: {material}", "zh_Hant": "材質:{material}", - "nb_NO": "Materiale: {material}" + "nb_NO": "Materiale: {material}", + "fi": "Materiaali: {material}" }, "freeform": { "key": "material", @@ -170,7 +176,8 @@ "zh_Hans": "材质:木", "nb_NO": "Materiale: tre", "zh_Hant": "材質:木頭", - "pt_BR": "Material: madeira" + "pt_BR": "Material: madeira", + "fi": "Materiaali: puu" } }, { @@ -203,7 +210,8 @@ "zh_Hans": "材质:石头", "nb_NO": "Materiale: stein", "zh_Hant": "材質:石頭", - "pt_BR": "Material: pedra" + "pt_BR": "Material: pedra", + "fi": "Materiaali: kivi" } }, { @@ -220,7 +228,8 @@ "zh_Hans": "材质:混凝土", "nb_NO": "Materiale: betong", "zh_Hant": "材質:水泥", - "pt_BR": "Material: concreto" + "pt_BR": "Material: concreto", + "fi": "Materiaali: betoni" } }, { @@ -237,7 +246,8 @@ "zh_Hans": "材质:塑料", "nb_NO": "Materiale: plastikk", "zh_Hant": "材質:塑膠", - "pt_BR": "Material: plástico" + "pt_BR": "Material: plástico", + "fi": "Materiaali: muovi" } }, { @@ -254,7 +264,8 @@ "zh_Hans": "材质:不锈钢", "nb_NO": "Materiale: stål", "zh_Hant": "材質:鋼鐵", - "pt_BR": "Material: aço" + "pt_BR": "Material: aço", + "fi": "Materiaali: teräs" } } ], @@ -313,7 +324,8 @@ "zh_Hans": "颜色: {colour}", "zh_Hant": "顏色:{colour}", "nb_NO": "Farge: {colour}", - "pt_BR": "Cor: {colour}" + "pt_BR": "Cor: {colour}", + "fi": "Väri: {colour}" }, "question": { "en": "Which colour does this bench have?", @@ -345,7 +357,8 @@ "zh_Hans": "颜色:棕", "zh_Hant": "顏色:棕色", "nb_NO": "Farge: brun", - "pt_BR": "Cor: marrom" + "pt_BR": "Cor: marrom", + "fi": "Väri: ruskea" } }, { @@ -361,7 +374,8 @@ "zh_Hans": "颜色:绿", "zh_Hant": "顏色:綠色", "nb_NO": "Farge: grønn", - "pt_BR": "Cor: verde" + "pt_BR": "Cor: verde", + "fi": "Väri: vihreä" } }, { @@ -377,7 +391,8 @@ "zh_Hans": "颜色:灰", "zh_Hant": "顏色:灰色", "nb_NO": "Farge: grå", - "pt_BR": "Cor: cinza" + "pt_BR": "Cor: cinza", + "fi": "Väri: harmaa" } }, { @@ -393,7 +408,8 @@ "zh_Hans": "颜色:白", "zh_Hant": "顏色:白色", "nb_NO": "Farge: hvit", - "pt_BR": "Cor: branco" + "pt_BR": "Cor: branco", + "fi": "Väri: valkoinen" } }, { @@ -409,7 +425,8 @@ "zh_Hans": "颜色:红", "zh_Hant": "顏色:紅色", "nb_NO": "Farge: rød", - "pt_BR": "Cor: vermelho" + "pt_BR": "Cor: vermelho", + "fi": "Väri: punainen" } }, { @@ -425,7 +442,8 @@ "zh_Hans": "颜色:黑", "zh_Hant": "顏色:黑色", "nb_NO": "Farge: svart", - "pt_BR": "Cor: preto" + "pt_BR": "Cor: preto", + "fi": "Väri: musta" } }, { @@ -441,7 +459,8 @@ "zh_Hans": "颜色:蓝", "zh_Hant": "顏色:藍色", "nb_NO": "Farge: blå", - "pt_BR": "Cor: azul" + "pt_BR": "Cor: azul", + "fi": "Väri: sininen" } }, { @@ -457,7 +476,8 @@ "zh_Hans": "颜色:黄", "zh_Hant": "顏色:黃色", "nb_NO": "Farge: gul", - "pt_BR": "Cor: amarelo" + "pt_BR": "Cor: amarelo", + "fi": "Väri: keltainen" } } ] @@ -528,7 +548,8 @@ "zh_Hans": "长椅", "nb_NO": "Benk", "zh_Hant": "長椅", - "pt_BR": "Banco" + "pt_BR": "Banco", + "fi": "Penkki" }, "description": { "en": "Add a new bench", @@ -542,7 +563,8 @@ "zh_Hans": "增加一个新的长椅", "nb_NO": "Legg til en ny benk", "zh_Hant": "新增長椅", - "pt_BR": "Adicionar um novo banco" + "pt_BR": "Adicionar um novo banco", + "fi": "Lisää uusi penkki" } } ] diff --git a/assets/layers/bench_at_pt/bench_at_pt.json b/assets/layers/bench_at_pt/bench_at_pt.json index 85cadb86ec..bb2661e25f 100644 --- a/assets/layers/bench_at_pt/bench_at_pt.json +++ b/assets/layers/bench_at_pt/bench_at_pt.json @@ -37,7 +37,8 @@ "zh_Hans": "长椅", "nb_NO": "Benk", "zh_Hant": "長椅", - "pt_BR": "Banco" + "pt_BR": "Banco", + "fi": "Penkki" }, "mappings": [ { @@ -96,7 +97,8 @@ "id": "{name}", "zh_Hans": "{name}", "zh_Hant": "{name}", - "pt_BR": "{name}" + "pt_BR": "{name}", + "fi": "{name}" }, "freeform": { "key": "name" diff --git a/assets/layers/bicycle_library/bicycle_library.json b/assets/layers/bicycle_library/bicycle_library.json index cb6cb2f2e1..d56f3bc428 100644 --- a/assets/layers/bicycle_library/bicycle_library.json +++ b/assets/layers/bicycle_library/bicycle_library.json @@ -145,7 +145,8 @@ "fr": "Emprunter un vélo coûte 20 €/an et 20 € de garantie", "it": "Il prestito di una bicicletta costa 20 €/anno più 20 € di garanzia", "de": "Das Ausleihen eines Fahrrads kostet 20€ pro Jahr und 20€ Gebühr", - "zh_Hant": "租借單車價錢 €20/year 與 €20 保證金" + "zh_Hant": "租借單車價錢 €20/year 與 €20 保證金", + "ru": "Прокат велосипеда стоит €20/год и €20 залог" } } ] diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index 77b5823950..30dcb1760a 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -117,7 +117,8 @@ "de": "Dieses Fahrrad-Café bietet eine Fahrradpumpe an, die von jedem benutzt werden kann", "it": "Questo caffè in bici offre una pompa per bici liberamente utilizzabile", "zh_Hans": "这家自行车咖啡为每个人提供打气筒", - "zh_Hant": "這個單車咖啡廳有提供給任何人都能使用的單車打氣甬" + "zh_Hant": "這個單車咖啡廳有提供給任何人都能使用的單車打氣甬", + "ru": "В этом велосипедном кафе есть велосипедный насос для всеобщего использования" } }, { @@ -130,7 +131,8 @@ "de": "Dieses Fahrrad-Café bietet keine Fahrradpumpe an, die von jedem benutzt werden kann", "it": "Questo caffè in bici non offre una pompa per bici liberamente utilizzabile", "zh_Hans": "这家自行车咖啡不为每个人提供打气筒", - "zh_Hant": "這個單車咖啡廳並沒有為所有人提供單車打氣甬" + "zh_Hant": "這個單車咖啡廳並沒有為所有人提供單車打氣甬", + "ru": "В этом велосипедном кафе нет велосипедного насоса для всеобщего использования" } } ] @@ -144,7 +146,8 @@ "de": "Gibt es hier Werkzeuge, um das eigene Fahrrad zu reparieren?", "it": "Ci sono degli strumenti per riparare la propria bicicletta?", "zh_Hans": "这里有供你修车用的工具吗?", - "zh_Hant": "這裡是否有工具修理你的單車嗎?" + "zh_Hant": "這裡是否有工具修理你的單車嗎?", + "ru": "Есть ли здесь инструменты для починки вашего велосипеда?" }, "mappings": [ { @@ -157,7 +160,8 @@ "de": "Dieses Fahrrad-Café bietet Werkzeuge für die selbständige Reparatur an", "it": "Questo caffè in bici fornisce degli attrezzi per la riparazione fai-da-te", "zh_Hans": "这家自行车咖啡为DIY修理者提供工具", - "zh_Hant": "這個單車咖啡廳提供工具讓你修理" + "zh_Hant": "這個單車咖啡廳提供工具讓你修理", + "ru": "В этом велосипедном кафе есть инструменты для починки своего велосипеда" } }, { @@ -170,7 +174,8 @@ "de": "Dieses Fahrrad-Café bietet keine Werkzeuge für die selbständige Reparatur an", "it": "Questo caffè in bici non fornisce degli attrezzi per la riparazione fai-da-te", "zh_Hans": "这家自行车咖啡不为DIY修理者提供工具", - "zh_Hant": "這個單車咖啡廳並沒有提供工具讓你修理" + "zh_Hant": "這個單車咖啡廳並沒有提供工具讓你修理", + "ru": "В этом велосипедном кафе нет инструментов для починки своего велосипеда" } } ] @@ -184,7 +189,8 @@ "de": "Repariert dieses Fahrrad-Café Fahrräder?", "it": "Questo caffè in bici ripara le bici?", "zh_Hans": "这家自行车咖啡t提供修车服务吗?", - "zh_Hant": "這個單車咖啡廳是否能修理單車?" + "zh_Hant": "這個單車咖啡廳是否能修理單車?", + "ru": "Есть ли услуги ремонта велосипедов в этом велосипедном кафе?" }, "mappings": [ { @@ -197,7 +203,8 @@ "de": "Dieses Fahrrad-Café repariert Fahrräder", "it": "Questo caffè in bici ripara le bici", "zh_Hans": "这家自行车咖啡可以修车", - "zh_Hant": "這個單車咖啡廳修理單車" + "zh_Hant": "這個單車咖啡廳修理單車", + "ru": "В этом велосипедном кафе есть услуги ремонта велосипедов" } }, { @@ -210,7 +217,8 @@ "de": "Dieses Fahrrad-Café repariert keine Fahrräder", "it": "Questo caffè in bici non ripara le bici", "zh_Hans": "这家自行车咖啡不能修车", - "zh_Hant": "這個單車咖啡廳並不修理單車" + "zh_Hant": "這個單車咖啡廳並不修理單車", + "ru": "В этом велосипедном кафе нет услуг ремонта велосипедов" } } ] @@ -275,7 +283,8 @@ "fr": "Quand ce Café vélo est-t-il ouvert ?", "it": "Quando è aperto questo caffè in bici?", "zh_Hans": "这家自行车咖啡什么时候开门营业?", - "zh_Hant": "何時這個單車咖啡廳營運?" + "zh_Hant": "何時這個單車咖啡廳營運?", + "ru": "Каков режим работы этого велосипедного кафе?" }, "render": "{opening_hours_table(opening_hours)}", "freeform": { diff --git a/assets/layers/bike_monitoring_station/bike_monitoring_station.json b/assets/layers/bike_monitoring_station/bike_monitoring_station.json index 0f54f36de9..34b4d1b630 100644 --- a/assets/layers/bike_monitoring_station/bike_monitoring_station.json +++ b/assets/layers/bike_monitoring_station/bike_monitoring_station.json @@ -5,7 +5,8 @@ "nl": "Telstation", "fr": "Stations de contrôle", "it": "Stazioni di monitoraggio", - "zh_Hant": "監視站" + "zh_Hant": "監視站", + "ru": "Станции мониторинга" }, "minzoom": 12, "source": { diff --git a/assets/layers/bike_parking/bike_parking.json b/assets/layers/bike_parking/bike_parking.json index 368931aa87..be24e0a6d1 100644 --- a/assets/layers/bike_parking/bike_parking.json +++ b/assets/layers/bike_parking/bike_parking.json @@ -77,7 +77,8 @@ "de": "Dies ist ein Fahrrad-Parkplatz der Art: {bicycle_parking}", "hu": "Ez egy {bicycle_parking} típusú kerékpáros parkoló", "it": "È un parcheggio bici del tipo: {bicycle_parking}", - "zh_Hant": "這個單車停車場的類型是:{bicycle_parking}" + "zh_Hant": "這個單車停車場的類型是:{bicycle_parking}", + "ru": "Это велопарковка типа {bicycle_parking}" }, "freeform": { "key": "bicycle_parking", @@ -288,7 +289,8 @@ "fr": "Ce parking est couvert (il a un toit)", "hu": "A parkoló fedett", "it": "È un parcheggio coperto (ha un tetto)", - "zh_Hant": "這個停車場有遮蔽 (有屋頂)" + "zh_Hant": "這個停車場有遮蔽 (有屋頂)", + "ru": "Это крытая парковка (есть крыша/навес)" } }, { @@ -301,7 +303,8 @@ "fr": "Ce parking n'est pas couvert", "hu": "A parkoló nem fedett", "it": "Non è un parcheggio coperto", - "zh_Hant": "這個停車場沒有遮蔽" + "zh_Hant": "這個停車場沒有遮蔽", + "ru": "Это открытая парковка" } } ] @@ -324,7 +327,8 @@ "gl": "Lugar para {capacity} bicicletas", "de": "Platz für {capacity} Fahrräder", "it": "Posti per {capacity} bici", - "zh_Hant": "{capacity} 單車的地方" + "zh_Hant": "{capacity} 單車的地方", + "ru": "Место для {capacity} велосипеда(ов)" }, "freeform": { "key": "capacity", @@ -339,7 +343,8 @@ "fr": "Qui peut utiliser ce parking à vélo ?", "it": "Chi può usare questo parcheggio bici?", "de": "Wer kann diesen Fahrradparplatz nutzen?", - "zh_Hant": "誰可以使用這個單車停車場?" + "zh_Hant": "誰可以使用這個單車停車場?", + "ru": "Кто может пользоваться этой велопарковкой?" }, "render": { "en": "{access}", @@ -349,7 +354,8 @@ "it": "{access}", "ru": "{access}", "id": "{access}", - "zh_Hant": "{access}" + "zh_Hant": "{access}", + "fi": "{access}" }, "freeform": { "key": "access", diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index cc08a9acf9..ef3adb308a 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -218,7 +218,8 @@ "en": "When is this bicycle repair point open?", "fr": "Quand ce point de réparation de vélo est-il ouvert ?", "it": "Quando è aperto questo punto riparazione bici?", - "de": "Wann ist diese Fahrradreparaturstelle geöffnet?" + "de": "Wann ist diese Fahrradreparaturstelle geöffnet?", + "ru": "Когда работает эта точка обслуживания велосипедов?" }, "render": "{opening_hours_table()}", "freeform": { @@ -233,7 +234,8 @@ "en": "Always open", "fr": "Ouvert en permanence", "it": "Sempre aperto", - "de": "Immer geöffnet" + "de": "Immer geöffnet", + "ru": "Всегда открыто" } }, { @@ -512,7 +514,8 @@ "render": { "en": "./assets/layers/bike_repair_station/repair_station.svg", "ru": "./assets/layers/bike_repair_station/repair_station.svg", - "it": "./assets/layers/bike_repair_station/repair_station.svg" + "it": "./assets/layers/bike_repair_station/repair_station.svg", + "fi": "./assets/layers/bike_repair_station/repair_station.svg" }, "mappings": [ { @@ -584,7 +587,8 @@ "gl": "Bomba de ar", "de": "Fahrradpumpe", "it": "Pompa per bici", - "ru": "Велосипедный насос" + "ru": "Велосипедный насос", + "fi": "Pyöräpumppu" }, "tags": [ "amenity=bicycle_repair_station", diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index 72ced485e6..7f60ced897 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -6,7 +6,8 @@ "fr": "Magasin ou réparateur de vélo", "gl": "Tenda/arranxo de bicicletas", "de": "Fahrradwerkstatt/geschäft", - "it": "Venditore/riparatore bici" + "it": "Venditore/riparatore bici", + "ru": "Обслуживание велосипедов/магазин" }, "minzoom": 13, "source": { @@ -54,7 +55,8 @@ "fr": "Magasin ou réparateur de vélo", "gl": "Tenda/arranxo de bicicletas", "de": "Fahrradwerkstatt/geschäft", - "it": "Venditore/riparatore bici" + "it": "Venditore/riparatore bici", + "ru": "Обслуживание велосипедов/магазин" }, "mappings": [ { @@ -207,7 +209,8 @@ "fr": "Ce magasin s'appelle {name}", "gl": "Esta tenda de bicicletas chámase {name}", "de": "Dieses Fahrradgeschäft heißt {name}", - "it": "Questo negozio di biciclette è chiamato {name}" + "it": "Questo negozio di biciclette è chiamato {name}", + "ru": "Этот магазин велосипедов называется {name}" }, "freeform": { "key": "name" @@ -284,7 +287,8 @@ "fr": "Est-ce que ce magasin vend des vélos ?", "gl": "Esta tenda vende bicicletas?", "de": "Verkauft dieser Laden Fahrräder?", - "it": "Questo negozio vende bici?" + "it": "Questo negozio vende bici?", + "ru": "Продаются ли велосипеды в этом магазине?" }, "mappings": [ { @@ -368,7 +372,8 @@ "fr": "Ce magasin ne répare seulement des marques spécifiques", "gl": "Esta tenda só arranxa bicicletas dunha certa marca", "de": "Dieses Geschäft repariert nur Fahrräder einer bestimmten Marke", - "it": "Questo negozio ripara solo le biciclette di una certa marca" + "it": "Questo negozio ripara solo le biciclette di una certa marca", + "ru": "В этом магазине обслуживают велосипеды определённого бренда" } } ] @@ -466,7 +471,8 @@ "fr": "Est-ce que ce magasin offre une pompe en accès libre ?", "gl": "Esta tenda ofrece unha bomba de ar para uso de calquera persoa?", "de": "Bietet dieses Geschäft eine Fahrradpumpe zur Benutzung für alle an?", - "it": "Questo negozio offre l’uso a chiunque di una pompa per bici?" + "it": "Questo negozio offre l’uso a chiunque di una pompa per bici?", + "ru": "Предлагается ли в этом магазине велосипедный насос для всеобщего пользования?" }, "mappings": [ { @@ -477,7 +483,8 @@ "fr": "Ce magasin offre une pompe en acces libre", "gl": "Esta tenda ofrece unha bomba de ar para uso de calquera persoa", "de": "Dieses Geschäft bietet eine Fahrradpumpe für alle an", - "it": "Questo negozio offre l’uso pubblico di una pompa per bici" + "it": "Questo negozio offre l’uso pubblico di una pompa per bici", + "ru": "В этом магазине есть велосипедный насос для всеобщего пользования" } }, { @@ -488,7 +495,8 @@ "fr": "Ce magasin n'offre pas de pompe en libre accès", "gl": "Esta tenda non ofrece unha bomba de ar para uso de calquera persoa", "de": "Dieses Geschäft bietet für niemanden eine Fahrradpumpe an", - "it": "Questo negozio non offre l’uso pubblico di una pompa per bici" + "it": "Questo negozio non offre l’uso pubblico di una pompa per bici", + "ru": "В этом магазине нет велосипедного насоса для всеобщего пользования" } }, { @@ -509,7 +517,8 @@ "fr": "Est-ce qu'il y a des outils pour réparer son vélo dans ce magasin ?", "gl": "Hai ferramentas aquí para arranxar a túa propia bicicleta?", "de": "Gibt es hier Werkzeuge, um das eigene Fahrrad zu reparieren?", - "it": "Sono presenti degli attrezzi per riparare la propria bici?" + "it": "Sono presenti degli attrezzi per riparare la propria bici?", + "ru": "Есть ли здесь инструменты для починки собственного велосипеда?" }, "mappings": [ { @@ -541,7 +550,8 @@ "nl": "Het gereedschap aan om je fiets zelf te herstellen is enkel voor als je de fiets er kocht of huurt", "fr": "Des outils d'auto-réparation sont disponibles uniquement si vous avez acheté ou loué le vélo dans ce magasin", "it": "Gli attrezzi per la riparazione fai-da-te sono disponibili solamente se hai acquistato/noleggiato la bici nel negozio", - "de": "Werkzeuge für die Selbstreparatur sind nur verfügbar, wenn Sie das Fahrrad im Laden gekauft/gemietet haben" + "de": "Werkzeuge für die Selbstreparatur sind nur verfügbar, wenn Sie das Fahrrad im Laden gekauft/gemietet haben", + "ru": "Инструменты для починки доступны только при покупке/аренде велосипеда в магазине" } } ] @@ -563,7 +573,8 @@ "nl": "Deze winkel biedt fietsschoonmaak aan", "fr": "Ce magasin lave les vélos", "it": "Questo negozio lava le biciclette", - "de": "Dieses Geschäft reinigt Fahrräder" + "de": "Dieses Geschäft reinigt Fahrräder", + "ru": "В этом магазине оказываются услуги мойки/чистки велосипедов" } }, { @@ -583,7 +594,8 @@ "nl": "Deze winkel biedt geen fietsschoonmaak aan", "fr": "Ce magasin ne fait pas le nettoyage de vélo", "it": "Questo negozio non offre la pulizia della bicicletta", - "de": "Dieser Laden bietet keine Fahrradreinigung an" + "de": "Dieser Laden bietet keine Fahrradreinigung an", + "ru": "В этом магазине нет услуг мойки/чистки велосипедов" } } ] diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 04e3d29256..4074ee963c 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -567,7 +567,8 @@ "nl": "Extra informatie voor OpenStreetMap experts: {fixme}", "fr": "Informations supplémentaires pour les experts d'OpenStreetMap : {fixme}", "it": "Informazioni supplementari per gli esperti di OpenStreetMap: {fixme}", - "de": "Zusätzliche Informationen für OpenStreetMap-Experten: {fixme}" + "de": "Zusätzliche Informationen für OpenStreetMap-Experten: {fixme}", + "ru": "Дополнительная информация для экспертов OpenStreetMap: {fixme}" }, "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)", diff --git a/assets/layers/ghost_bike/ghost_bike.json b/assets/layers/ghost_bike/ghost_bike.json index b7951da3d4..2a2b8342d3 100644 --- a/assets/layers/ghost_bike/ghost_bike.json +++ b/assets/layers/ghost_bike/ghost_bike.json @@ -76,7 +76,8 @@ "nl": "Ter nagedachtenis van {name}", "de": "Im Gedenken an {name}", "it": "In ricordo di {name}", - "fr": "En souvenir de {name}" + "fr": "En souvenir de {name}", + "ru": "В знак памяти о {name}" }, "freeform": { "key": "name" @@ -149,7 +150,8 @@ "nl": "Geplaatst op {start_date}", "en": "Placed on {start_date}", "it": "Piazzata in data {start_date}", - "fr": "Placé le {start_date}" + "fr": "Placé le {start_date}", + "ru": "Установлен {start_date}" }, "freeform": { "key": "start_date", diff --git a/assets/themes/campersites/campersites.json b/assets/themes/campersites/campersites.json index 76039862d6..3c580c5fd5 100644 --- a/assets/themes/campersites/campersites.json +++ b/assets/themes/campersites/campersites.json @@ -23,7 +23,8 @@ "it": "Questo sito raccoglie tutti i luoghi ufficiali dove sostare con il camper e aree dove è possibile scaricare acque grigie e nere. Puoi aggiungere dettagli riguardanti i servizi forniti e il loro costo. Aggiungi foto e recensioni. Questo è al contempo un sito web e una web app. I dati sono memorizzati su OpenStreetMap in modo tale che siano per sempre liberi e riutilizzabili da qualsiasi app.", "ru": "На этом сайте собраны все официальные места остановки кемперов и места, где можно сбросить серую и черную воду. Вы можете добавить подробную информацию о предоставляемых услугах и их стоимости. Добавлять фотографии и отзывы. Это веб-сайт и веб-приложение. Данные хранятся в OpenStreetMap, поэтому они будут бесплатными всегда и могут быть повторно использованы любым приложением.", "ja": "このWebサイトでは、すべてのキャンピングカーの公式停車場所と、汚水を捨てることができる場所を収集します。提供されるサービスとコストに関する詳細を追加できます。写真とレビューを追加します。これはウェブサイトとウェブアプリです。データはOpenStreetMapに保存されるので、永遠に無料で、どんなアプリからでも再利用できます。", - "zh_Hant": "這個網站收集所有官方露營地點,以及那邊能排放廢水。你可以加上詳細的服務項目與價格,加上圖片以及評價。這是網站與網路 app,資料則是存在開放街圖,因此會永遠免費,而且可以被所有 app 再利用。" + "zh_Hant": "這個網站收集所有官方露營地點,以及那邊能排放廢水。你可以加上詳細的服務項目與價格,加上圖片以及評價。這是網站與網路 app,資料則是存在開放街圖,因此會永遠免費,而且可以被所有 app 再利用。", + "nl": "Deze website verzamelt en toont alle officiële plaatsen waar een camper mag overnachten en afvalwater kan lozen. Ook jij kan extra gegevens toevoegen, zoals welke services er geboden worden en hoeveel dit kot, ook afbeeldingen en reviews kan je toevoegen. De data wordt op OpenStreetMap opgeslaan en is dus altijd gratis te hergebruiken, ook door andere applicaties." }, "language": [ "en", @@ -53,7 +54,8 @@ "ru": "Площадки для кемпинга", "ja": "キャンプサイト", "fr": "Campings", - "zh_Hant": "露營地" + "zh_Hant": "露營地", + "nl": "Camperplaatsen" }, "minzoom": 10, "source": { @@ -71,7 +73,8 @@ "ru": "Место для кемпинга {name}", "ja": "キャンプサイト {name}", "fr": "Camping {name}", - "zh_Hant": "露營地 {name}" + "zh_Hant": "露營地 {name}", + "nl": "Camperplaats {name}" }, "mappings": [ { @@ -86,7 +89,8 @@ "ru": "Место для кемпинга без названия", "ja": "無名のキャンプサイト", "fr": "Camping sans nom", - "zh_Hant": "沒有名稱的露營地" + "zh_Hant": "沒有名稱的露營地", + "nl": "Camper site" } } ] @@ -97,7 +101,8 @@ "ru": "площадки для кемпинга", "ja": "キャンプサイト", "fr": "campings", - "zh_Hant": "露營地" + "zh_Hant": "露營地", + "nl": "camperplaatsen" }, "tagRenderings": [ "images", @@ -108,7 +113,8 @@ "ru": "Это место называется {name}", "ja": "この場所は {name} と呼ばれています", "fr": "Cet endroit s'appelle {nom}", - "zh_Hant": "這個地方叫做 {name}" + "zh_Hant": "這個地方叫做 {name}", + "nl": "Deze plaats heet {name}" }, "question": { "en": "What is this place called?", @@ -117,7 +123,8 @@ "it": "Come viene chiamato questo luogo?", "ja": "ここは何というところですか?", "fr": "Comment s'appelle cet endroit ?", - "zh_Hant": "這個地方叫做什麼?" + "zh_Hant": "這個地方叫做什麼?", + "nl": "Wat is de naam van deze plaats?" }, "freeform": { "key": "name" @@ -130,7 +137,8 @@ "ru": "Взимается ли в этом месте плата?", "ja": "ここは有料ですか?", "fr": "Cet endroit est-il payant ?", - "zh_Hant": "這個地方收費嗎?" + "zh_Hant": "這個地方收費嗎?", + "nl": "Moet men betalen om deze camperplaats te gebruiken?" }, "mappings": [ { @@ -144,7 +152,8 @@ "it": "Devi pagare per usarlo", "ru": "За использование нужно платить", "ja": "使用料を支払う必要がある", - "zh_Hant": "你要付費才能使用" + "zh_Hant": "你要付費才能使用", + "nl": "Gebruik is betalend" } }, { @@ -162,7 +171,8 @@ "ja": "無料で使用可能", "fr": "Peut être utilisé gratuitement", "nb_NO": "Kan brukes gratis", - "zh_Hant": "可以免費使用" + "zh_Hant": "可以免費使用", + "nl": "Kan gratis gebruikt worden" } }, { @@ -179,7 +189,8 @@ "ru": "Это место взимает {charge}", "ja": "この場所は{charge} が必要", "nb_NO": "Dette stedet tar {charge}", - "zh_Hant": "這個地方收費 {charge}" + "zh_Hant": "這個地方收費 {charge}", + "nl": "Deze plaats vraagt {charge}" }, "question": { "en": "How much does this place charge?", @@ -188,7 +199,8 @@ "ja": "ここはいくらかかりますか?", "fr": "Combien coûte cet endroit ?", "nb_NO": "pø", - "zh_Hant": "這個地方收多少費用?" + "zh_Hant": "這個地方收多少費用?", + "nl": "Hoeveel kost deze plaats?" }, "freeform": { "key": "charge" @@ -774,7 +786,8 @@ "question": { "en": "Who can use this dump station?", "ja": "このゴミ捨て場は誰が使えるんですか?", - "it": "Chi può utilizzare questo luogo di sversamento?" + "it": "Chi può utilizzare questo luogo di sversamento?", + "ru": "Кто может использовать эту станцию утилизации?" }, "mappings": [ { diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 39c2baa412..2330b885ec 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -1,165 +1,185 @@ { - "id": "openwindpowermap", - "title": { - "en": "OpenWindPowerMap" - }, - "maintainer": "Seppe Santens", - "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", - "description": { - "en": "A map for showing and editing wind turbines." - }, - "language": [ - "en" - ], - "version": "2021-06-18", - "startLat": 50.520, - "startLon": 4.643, - "startZoom": 8, - "clustering": { + "id": "openwindpowermap", + "title": { + "en": "OpenWindPowerMap" + }, + "maintainer": "Seppe Santens", + "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", + "description": { + "en": "A map for showing and editing wind turbines." + }, + "language": [ + "en", + "nl" + ], + "version": "2021-06-18", + "startLat": 50.52, + "startLon": 4.643, + "startZoom": 8, + "clustering": { "maxZoom": 8 }, - "layers": [ - { - "id": "windturbine", - "name": { - "en": "wind turbine" - }, - "source": { - "osmTags": "generator:source=wind" - }, - "minzoom": 10, - "wayHandling": 1, - "title": { - "render": { - "en": "wind turbine" - }, - "mappings": [ - { - "if": "name~*", - "then": { - "en": "{name}" - } - } - ] - }, - "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", - "iconSize": "40, 40, bottom", - "label": { - "mappings": [ - { - "if": "generator:output:electricity~^[0-9]+.*[W]$", - "then": "
{generator:output:electricity}
" - } - ] - }, - "tagRenderings": [ - { - "render": { - "en": "The power output of this wind turbine is {generator:output:electricity}." - }, - "question": { - "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" - }, - "freeform": { - "key": "generator:output:electricity" - } - }, - { - "render": { - "en": "This wind turbine is operated by {operator}." - }, - "question": { - "en": "Who operates this wind turbine?" - }, - "freeform": { - "key": "operator" - } - }, - { - "render": { - "en": "The total height (including rotor radius) of this wind turbine is {height} metres." - }, - "question": { - "en": "What is the total height of this wind turbine (including rotor radius), in metres?" - }, - "freeform": { - "key": "height", - "type": "float" - } - }, - { - "render": { - "en": "The rotor diameter of this wind turbine is {rotor:diameter} metres." - }, - "question": { - "en": "What is the rotor diameter of this wind turbine, in metres?" - }, - "freeform": { - "key": "rotor:diameter", - "type": "float" - } - }, - { - "render": { - "en": "This wind turbine went into operation on/in {start_date}." - }, - "question": { - "en": "When did this wind turbine go into operation?" - }, - "freeform": { - "key": "start_date", - "type": "date" - } - }, - "images" - ], - "presets": [ - { - "tags": [ - "power=generator", - "generator:source=wind" - ], - "title": { - "en": "wind turbine" - } - } - ] - } - ], - "units": [ - { - "appliesToKey": ["generator:output:electricity"], - "applicableUnits": [{ - "canonicalDenomination": "MW", - "alternativeDenomination": ["megawatts","megawatt"], - "human": { - "en": " megawatts", - "nl": " megawatt" - } - },{ - "canonicalDenomination": "kW", - "alternativeDenomination": ["kilowatts","kilowatt"], - "human": { - "en": " kilowatts", - "nl": " kilowatt" - } - },{ - "canonicalDenomination": "W", - "alternativeDenomination": ["watts","watt"], - "human": { - "en": " watts", - "nl": " watt" - } - },{ - "canonicalDenomination": "GW", - "alternativeDenomination": ["gigawatts","gigawatt"], - "human": { - "en": " gigawatts", - "nl": " gigawatt" - } - }], - "eraseInvalidValues": true - } - ], - "defaultBackgroundId": "CartoDB.Voyager" -} + "layers": [ + { + "id": "windturbine", + "name": { + "en": "wind turbine" + }, + "source": { + "osmTags": "generator:source=wind" + }, + "minzoom": 10, + "wayHandling": 1, + "title": { + "render": { + "en": "wind turbine" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "en": "{name}" + } + } + ] + }, + "icon": "./assets/themes/openwindpowermap/wind_turbine.svg", + "iconSize": "40, 40, bottom", + "label": { + "mappings": [ + { + "if": "generator:output:electricity~^[0-9]+.*[W]$", + "then": "
{generator:output:electricity}
" + } + ] + }, + "tagRenderings": [ + { + "render": { + "en": "The power output of this wind turbine is {generator:output:electricity}." + }, + "question": { + "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" + }, + "freeform": { + "key": "generator:output:electricity" + } + }, + { + "render": { + "en": "This wind turbine is operated by {operator}." + }, + "question": { + "en": "Who operates this wind turbine?" + }, + "freeform": { + "key": "operator" + } + }, + { + "render": { + "en": "The total height (including rotor radius) of this wind turbine is {height} metres." + }, + "question": { + "en": "What is the total height of this wind turbine (including rotor radius), in metres?" + }, + "freeform": { + "key": "height", + "type": "float" + } + }, + { + "render": { + "en": "The rotor diameter of this wind turbine is {rotor:diameter} metres." + }, + "question": { + "en": "What is the rotor diameter of this wind turbine, in metres?" + }, + "freeform": { + "key": "rotor:diameter", + "type": "float" + } + }, + { + "render": { + "en": "This wind turbine went into operation on/in {start_date}." + }, + "question": { + "en": "When did this wind turbine go into operation?" + }, + "freeform": { + "key": "start_date", + "type": "date" + } + }, + "images" + ], + "presets": [ + { + "tags": [ + "power=generator", + "generator:source=wind" + ], + "title": { + "en": "wind turbine" + } + } + ] + } + ], + "units": [ + { + "appliesToKey": [ + "generator:output:electricity" + ], + "applicableUnits": [ + { + "canonicalDenomination": "MW", + "alternativeDenomination": [ + "megawatts", + "megawatt" + ], + "human": { + "en": " megawatts", + "nl": " megawatt" + } + }, + { + "canonicalDenomination": "kW", + "alternativeDenomination": [ + "kilowatts", + "kilowatt" + ], + "human": { + "en": " kilowatts", + "nl": " kilowatt" + } + }, + { + "canonicalDenomination": "W", + "alternativeDenomination": [ + "watts", + "watt" + ], + "human": { + "en": " watts", + "nl": " watt" + } + }, + { + "canonicalDenomination": "GW", + "alternativeDenomination": [ + "gigawatts", + "gigawatt" + ], + "human": { + "en": " gigawatts", + "nl": " gigawatt" + } + } + ], + "eraseInvalidValues": true + } + ], + "defaultBackgroundId": "CartoDB.Voyager" +} \ No newline at end of file diff --git a/langs/layers/fi.json b/langs/layers/fi.json index 09fd6f9a85..71e6a3ca12 100644 --- a/langs/layers/fi.json +++ b/langs/layers/fi.json @@ -1,12 +1,86 @@ { - "bike_repair_station": { - "presets": { - "0": { - "title": "Pyöräpumppu" + "bench": { + "name": "Penkit", + "title": { + "render": "Penkki" + }, + "tagRenderings": { + "1": { + "render": "Selkänoja", + "mappings": { + "0": { + "then": "Selkänoja: kyllä" + }, + "1": { + "then": "Selkänoja: ei" + } + } + }, + "3": { + "render": "Materiaali: {material}", + "mappings": { + "0": { + "then": "Materiaali: puu" + }, + "2": { + "then": "Materiaali: kivi" + }, + "3": { + "then": "Materiaali: betoni" + }, + "4": { + "then": "Materiaali: muovi" + }, + "5": { + "then": "Materiaali: teräs" + } + } + }, + "5": { + "render": "Väri: {colour}", + "mappings": { + "0": { + "then": "Väri: ruskea" + }, + "1": { + "then": "Väri: vihreä" + }, + "2": { + "then": "Väri: harmaa" + }, + "3": { + "then": "Väri: valkoinen" + }, + "4": { + "then": "Väri: punainen" + }, + "5": { + "then": "Väri: musta" + }, + "6": { + "then": "Väri: sininen" + }, + "7": { + "then": "Väri: keltainen" + } + } } }, - "icon": { - "render": "./assets/layers/bike_repair_station/repair_station.svg" + "presets": { + "0": { + "title": "Penkki", + "description": "Lisää uusi penkki" + } + } + }, + "bench_at_pt": { + "title": { + "render": "Penkki" + }, + "tagRenderings": { + "1": { + "render": "{name}" + } } }, "bike_parking": { @@ -16,88 +90,14 @@ } } }, - "bench_at_pt": { - "tagRenderings": { - "1": { - "render": "{name}" - } + "bike_repair_station": { + "icon": { + "render": "./assets/layers/bike_repair_station/repair_station.svg" }, - "title": { - "render": "Penkki" - } - }, - "bench": { "presets": { "0": { - "description": "Lisää uusi penkki", - "title": "Penkki" + "title": "Pyöräpumppu" } - }, - "tagRenderings": { - "5": { - "mappings": { - "7": { - "then": "Väri: keltainen" - }, - "6": { - "then": "Väri: sininen" - }, - "5": { - "then": "Väri: musta" - }, - "4": { - "then": "Väri: punainen" - }, - "3": { - "then": "Väri: valkoinen" - }, - "2": { - "then": "Väri: harmaa" - }, - "1": { - "then": "Väri: vihreä" - }, - "0": { - "then": "Väri: ruskea" - } - }, - "render": "Väri: {colour}" - }, - "3": { - "mappings": { - "5": { - "then": "Materiaali: teräs" - }, - "4": { - "then": "Materiaali: muovi" - }, - "3": { - "then": "Materiaali: betoni" - }, - "2": { - "then": "Materiaali: kivi" - }, - "0": { - "then": "Materiaali: puu" - } - }, - "render": "Materiaali: {material}" - }, - "1": { - "mappings": { - "1": { - "then": "Selkänoja: ei" - }, - "0": { - "then": "Selkänoja: kyllä" - } - }, - "render": "Selkänoja" - } - }, - "title": { - "render": "Penkki" - }, - "name": "Penkit" + } } -} +} \ No newline at end of file diff --git a/langs/layers/ru.json b/langs/layers/ru.json index d8905f92f7..4f74e8815e 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -204,11 +204,33 @@ "2": { "question": "Есть ли в этом велосипедном кафе велосипедный насос для всеобщего использования?", "mappings": { - "1": { - "then": "В этом велосипедном кафе нет велосипедного насоса для всеобщего использования" - }, "0": { "then": "В этом велосипедном кафе есть велосипедный насос для всеобщего использования" + }, + "1": { + "then": "В этом велосипедном кафе нет велосипедного насоса для всеобщего использования" + } + } + }, + "3": { + "question": "Есть ли здесь инструменты для починки вашего велосипеда?", + "mappings": { + "0": { + "then": "В этом велосипедном кафе есть инструменты для починки своего велосипеда" + }, + "1": { + "then": "В этом велосипедном кафе нет инструментов для починки своего велосипеда" + } + } + }, + "4": { + "question": "Есть ли услуги ремонта велосипедов в этом велосипедном кафе?", + "mappings": { + "0": { + "then": "В этом велосипедном кафе есть услуги ремонта велосипедов" + }, + "1": { + "then": "В этом велосипедном кафе нет услуг ремонта велосипедов" } } }, @@ -223,28 +245,6 @@ }, "8": { "question": "Каков режим работы этого велосипедного кафе?" - }, - "4": { - "mappings": { - "1": { - "then": "В этом велосипедном кафе нет услуг ремонта велосипедов" - }, - "0": { - "then": "В этом велосипедном кафе есть услуги ремонта велосипедов" - } - }, - "question": "Есть ли услуги ремонта велосипедов в этом велосипедном кафе?" - }, - "3": { - "mappings": { - "1": { - "then": "В этом велосипедном кафе нет инструментов для починки своего велосипеда" - }, - "0": { - "then": "В этом велосипедном кафе есть инструменты для починки своего велосипеда" - } - }, - "question": "Есть ли здесь инструменты для починки вашего велосипеда?" } }, "presets": { @@ -253,6 +253,9 @@ } } }, + "bike_monitoring_station": { + "name": "Станции мониторинга" + }, "bike_parking": { "tagRenderings": { "1": { @@ -272,22 +275,22 @@ } } }, - "5": { - "render": "{access}", - "question": "Кто может пользоваться этой велопарковкой?" + "3": { + "mappings": { + "0": { + "then": "Это крытая парковка (есть крыша/навес)" + }, + "1": { + "then": "Это открытая парковка" + } + } }, "4": { "render": "Место для {capacity} велосипеда(ов)" }, - "3": { - "mappings": { - "1": { - "then": "Это открытая парковка" - }, - "0": { - "then": "Это крытая парковка (есть крыша/навес)" - } - } + "5": { + "question": "Кто может пользоваться этой велопарковкой?", + "render": "{access}" } } }, @@ -303,6 +306,14 @@ } }, "tagRenderings": { + "3": { + "question": "Когда работает эта точка обслуживания велосипедов?", + "mappings": { + "0": { + "then": "Всегда открыто" + } + } + }, "6": { "question": "Велосипедный насос все еще работает?", "mappings": { @@ -348,14 +359,6 @@ "then": "Есть манометр, но он сломан" } } - }, - "3": { - "question": "Когда работает эта точка обслуживания велосипедов?", - "mappings": { - "0": { - "then": "Всегда открыто" - } - } } }, "icon": { @@ -368,7 +371,9 @@ } }, "bike_shop": { + "name": "Обслуживание велосипедов/магазин", "title": { + "render": "Обслуживание велосипедов/магазин", "mappings": { "0": { "then": "Магазин спортивного инвентаря {name}" @@ -382,8 +387,7 @@ "4": { "then": "Магазин велосипедов {name}" } - }, - "render": "Обслуживание велосипедов/магазин" + } }, "description": "Магазин, специализирующийся на продаже велосипедов или сопутствующих товаров", "tagRenderings": { @@ -401,6 +405,7 @@ "question": "Какой адрес электронной почты у {name}?" }, "9": { + "question": "Продаются ли велосипеды в этом магазине?", "mappings": { "0": { "then": "В этом магазине продаются велосипеды" @@ -408,8 +413,7 @@ "1": { "then": "В этом магазине не продают велосипеды" } - }, - "question": "Продаются ли велосипеды в этом магазине?" + } }, "10": { "question": "В этом магазине ремонтируют велосипеды?", @@ -453,38 +457,37 @@ } } }, - "15": { - "question": "Здесь моют велосипеды?", - "mappings": { - "2": { - "then": "В этом магазине нет услуг мойки/чистки велосипедов" - }, - "0": { - "then": "В этом магазине оказываются услуги мойки/чистки велосипедов" - } - } - }, "13": { "question": "Предлагается ли в этом магазине велосипедный насос для всеобщего пользования?", "mappings": { - "1": { - "then": "В этом магазине нет велосипедного насоса для всеобщего пользования" - }, "0": { "then": "В этом магазине есть велосипедный насос для всеобщего пользования" + }, + "1": { + "then": "В этом магазине нет велосипедного насоса для всеобщего пользования" } } }, "14": { + "question": "Есть ли здесь инструменты для починки собственного велосипеда?", "mappings": { "2": { "then": "Инструменты для починки доступны только при покупке/аренде велосипеда в магазине" } - }, - "question": "Есть ли здесь инструменты для починки собственного велосипеда?" + } + }, + "15": { + "question": "Здесь моют велосипеды?", + "mappings": { + "0": { + "then": "В этом магазине оказываются услуги мойки/чистки велосипедов" + }, + "2": { + "then": "В этом магазине нет услуг мойки/чистки велосипедов" + } + } } - }, - "name": "Обслуживание велосипедов/магазин" + } }, "defibrillator": { "name": "Дефибрилляторы", @@ -539,6 +542,9 @@ }, "ghost_bike": { "tagRenderings": { + "2": { + "render": "В знак памяти о {name}" + }, "3": { "render": "Доступна более подробная информация" }, @@ -547,9 +553,6 @@ }, "5": { "render": "Установлен {start_date}" - }, - "2": { - "render": "В знак памяти о {name}" } } }, @@ -885,8 +888,5 @@ "question": "Вы хотите добавить описание?" } } - }, - "bike_monitoring_station": { - "name": "Станции мониторинга" } -} +} \ No newline at end of file diff --git a/langs/themes/en.json b/langs/themes/en.json index ff61b87823..48852d5a4c 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1084,6 +1084,68 @@ "shortDescription": "This theme shows all (touristic) maps that OpenStreetMap knows of", "description": "On this map you can find all maps OpenStreetMap knows - typically a big map on an information board showing the area, city or region, e.g. a tourist map on the back of a billboard, a map of a nature reserve, a map of cycling networks in the region, ...)

If a map is missing, you can easily map this map on OpenStreetMap." }, + "openwindpowermap": { + "title": "OpenWindPowerMap", + "description": "A map for showing and editing wind turbines.", + "layers": { + "0": { + "name": "wind turbine", + "title": { + "render": "wind turbine", + "mappings": { + "0": { + "then": "{name}" + } + } + }, + "tagRenderings": { + "0": { + "render": "The power output of this wind turbine is {generator:output:electricity}.", + "question": "What is the power output of this wind turbine? (e.g. 2.3 MW)" + }, + "1": { + "render": "This wind turbine is operated by {operator}.", + "question": "Who operates this wind turbine?" + }, + "2": { + "render": "The total height (including rotor radius) of this wind turbine is {height} metres.", + "question": "What is the total height of this wind turbine (including rotor radius), in metres?" + }, + "3": { + "render": "The rotor diameter of this wind turbine is {rotor:diameter} metres.", + "question": "What is the rotor diameter of this wind turbine, in metres?" + }, + "4": { + "render": "This wind turbine went into operation on/in {start_date}.", + "question": "When did this wind turbine go into operation?" + } + }, + "presets": { + "0": { + "title": "wind turbine" + } + } + } + }, + "units": { + "0": { + "applicableUnits": { + "0": { + "human": " megawatts" + }, + "1": { + "human": " kilowatts" + }, + "2": { + "human": " watts" + }, + "3": { + "human": " gigawatts" + } + } + } + } + }, "personal": { "title": "Personal theme", "description": "Create a personal theme based on all the available layers of all themes" diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 6746eff92b..ce6dfd8fcc 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -241,42 +241,42 @@ "campersite": { "title": "Kampeersite", "shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome", + "description": "Deze website verzamelt en toont alle officiële plaatsen waar een camper mag overnachten en afvalwater kan lozen. Ook jij kan extra gegevens toevoegen, zoals welke services er geboden worden en hoeveel dit kot, ook afbeeldingen en reviews kan je toevoegen. De data wordt op OpenStreetMap opgeslaan en is dus altijd gratis te hergebruiken, ook door andere applicaties.", "layers": { "0": { "name": "Camperplaatsen", - "tagRenderings": { - "3": { - "question": "Hoeveel kost deze plaats?", - "render": "Deze plaats vraagt {charge}" - }, - "2": { - "mappings": { - "1": { - "then": "Kan gratis gebruikt worden" - }, - "0": { - "then": "Gebruik is betalend" - } - }, - "question": "Moet men betalen om deze camperplaats te gebruiken?" - }, - "1": { - "question": "Wat is de naam van deze plaats?", - "render": "Deze plaats heet {name}" - } - }, - "description": "camperplaatsen", "title": { + "render": "Camperplaats {name}", "mappings": { "0": { "then": "Camper site" } + } + }, + "description": "camperplaatsen", + "tagRenderings": { + "1": { + "render": "Deze plaats heet {name}", + "question": "Wat is de naam van deze plaats?" }, - "render": "Camperplaats {name}" + "2": { + "question": "Moet men betalen om deze camperplaats te gebruiken?", + "mappings": { + "0": { + "then": "Gebruik is betalend" + }, + "1": { + "then": "Kan gratis gebruikt worden" + } + } + }, + "3": { + "render": "Deze plaats vraagt {charge}", + "question": "Hoeveel kost deze plaats?" + } } } - }, - "description": "Deze website verzamelt en toont alle officiële plaatsen waar een camper mag overnachten en afvalwater kan lozen. Ook jij kan extra gegevens toevoegen, zoals welke services er geboden worden en hoeveel dit kot, ook afbeeldingen en reviews kan je toevoegen. De data wordt op OpenStreetMap opgeslaan en is dus altijd gratis te hergebruiken, ook door andere applicaties." + } }, "climbing": { "title": "Open Klimkaart", @@ -890,6 +890,26 @@ "shortDescription": "Deze kaart bevat informatie voor natuurliefhebbers", "description": "Op deze kaart vind je informatie voor natuurliefhebbers, zoals info over het natuurgebied waar je inzit, vogelkijkhutten, informatieborden, ..." }, + "openwindpowermap": { + "units": { + "0": { + "applicableUnits": { + "0": { + "human": " megawatt" + }, + "1": { + "human": " kilowatt" + }, + "2": { + "human": " watt" + }, + "3": { + "human": " gigawatt" + } + } + } + } + }, "personal": { "title": "Persoonlijk thema", "description": "Stel je eigen thema samen door lagen te combineren van alle andere themas" @@ -1023,4 +1043,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/themes/ru.json b/langs/themes/ru.json index c2d70c10f3..cb0a7dbe71 100644 --- a/langs/themes/ru.json +++ b/langs/themes/ru.json @@ -525,4 +525,4 @@ "trees": { "title": "Деревья" } -} +} \ No newline at end of file From e594511e22fda8d5c48720c2f03fbf4c6b3fcd6a Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:03:17 +0200 Subject: [PATCH 32/64] Better trimming of canonical values, no console output if not actually rewriting --- Customizations/JSON/Denomination.ts | 2 +- Logic/SimpleMetaTagger.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Customizations/JSON/Denomination.ts b/Customizations/JSON/Denomination.ts index 09c5ab9775..8331d8adc5 100644 --- a/Customizations/JSON/Denomination.ts +++ b/Customizations/JSON/Denomination.ts @@ -152,7 +152,7 @@ export class Denomination { if (stripped === null) { return null; } - return stripped + " " + this.canonical.trim() + return (stripped + " " + this.canonical.trim()).trim(); } /** diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index c6269e222a..4453c2b4fe 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -95,7 +95,10 @@ export default class SimpleMetaTagger { const value = feature.properties[key] const [, denomination] = unit.findDenomination(value) let canonical = denomination?.canonicalValue(value) ?? undefined; - console.log("Rewritten ", key, " from", value, "into", canonical) + if(canonical === value){ + break; + } + console.log("Rewritten ", key, ` from '${value}' into '${canonical}'`) if(canonical === undefined && !unit.eraseInvalid) { break; } From 2dbdcaa2ba6694bdce3ec37b6b9a43bc7fe26081 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:03:41 +0200 Subject: [PATCH 33/64] Move TileRange to seperate file --- Models/TileRange.ts | 1 - Utils.ts | 12 ++---------- scripts/generateCache.ts | 3 ++- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Models/TileRange.ts b/Models/TileRange.ts index f3c59af238..e1dba5532f 100644 --- a/Models/TileRange.ts +++ b/Models/TileRange.ts @@ -5,5 +5,4 @@ export interface TileRange { yend: number, total: number, zoomlevel: number - } \ No newline at end of file diff --git a/Utils.ts b/Utils.ts index 05ebcbaabc..cb88356569 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,4 +1,5 @@ import * as colors from "./assets/colors.json" +import {TileRange} from "./Models/TileRange"; export class Utils { @@ -134,7 +135,7 @@ export class Utils { } return newArr; } - + public static MergeTags(a: any, b: any) { const t = {}; for (const k in a) { @@ -449,12 +450,3 @@ export class Utils { } } -export interface TileRange { - xstart: number, - ystart: number, - xend: number, - yend: number, - total: number, - zoomlevel: number - -} \ No newline at end of file diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index 77dfa86536..f7651c4b41 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -1,7 +1,7 @@ /** * Generates a collection of geojson files based on an overpass query for a given theme */ -import {TileRange, Utils} from "../Utils"; +import {Utils} from "../Utils"; Utils.runningFromConsole = true import {Overpass} from "../Logic/Osm/Overpass"; @@ -17,6 +17,7 @@ import MetaTagging from "../Logic/MetaTagging"; import LayerConfig from "../Customizations/JSON/LayerConfig"; import {GeoOperations} from "../Logic/GeoOperations"; import {UIEventSource} from "../Logic/UIEventSource"; +import {TileRange} from "../Models/TileRange"; function createOverpassObject(theme: LayoutConfig) { From 0306a2881100b8551c21b3e410d9db6ccbcf81f8 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:07:27 +0200 Subject: [PATCH 34/64] Disable zoom and fade animations on minimaps --- UI/Base/Minimap.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index 647fade477..73bf2354aa 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -44,7 +44,6 @@ export default class Minimap extends BaseUIElement { const self = this; // @ts-ignore const resizeObserver = new ResizeObserver(_ => { - console.log("Change in size detected!") self.InitMap(); self.leafletMap?.data?.invalidateSize() }); @@ -82,7 +81,9 @@ export default class Minimap extends BaseUIElement { scrollWheelZoom: this._allowMoving, doubleClickZoom: this._allowMoving, keyboard: this._allowMoving, - touchZoom: this._allowMoving + touchZoom: this._allowMoving, + zoomAnimation: this._allowMoving, + fadeAnimation: this._allowMoving }); map.setMaxBounds( From 1187d5b807040d0f6e2d738afd4e7a16cb411d7b Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:07:44 +0200 Subject: [PATCH 35/64] Disable zoom animation --- UI/ShowDataLayer.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 711f6b1c58..df45af45e2 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -80,9 +80,7 @@ export default class ShowDataLayer { if (zoomToFeatures) { try { - - mp.fitBounds(geoLayer.getBounds()) - + mp.fitBounds(geoLayer.getBounds(), {animate: false}) } catch (e) { console.error(e) } From 58ac26b0b30d85fc3f909073ddfde2dcb97b1c71 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:23:32 +0200 Subject: [PATCH 36/64] Fix warnings in climbing theme --- assets/themes/climbing/climbing.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index aa8d113106..33463ccdb2 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -727,7 +727,7 @@ "_contained_climbing_route_ids=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p.id)", "_difficulty_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:grade:french'])", "_length_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:length'])", - "_contained_climbing_routes_count=JSON.parse(_contained_climbing_routes).length" + "_contained_climbing_routes_count=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').length" ] }, { @@ -1400,8 +1400,8 @@ "_embedding_feature_properties=feat.overlapWith('climbing').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})", "_embedding_features_with_access=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.access !== undefined)[0]", "_embedding_feature_with_rock=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.rock !== undefined)[0] ?? '{}'", - "_embedding_features_with_rock:rock=JSON.parse(_embedding_feature_with_rock)?.rock", - "_embedding_features_with_rock:id=JSON.parse(_embedding_feature_with_rock)?.id", + "_embedding_features_with_rock:rock=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.rock", + "_embedding_features_with_rock:id=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.id", "_embedding_feature:access=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').access", "_embedding_feature:access:description=JSON.parse(feat.properties._embedding_features_with_access ?? '{}')['access:description']", "_embedding_feature:id=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').id" From 3ecfef9bc1e50ad3affd3ff07c758840ad0385bd Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 10 Jul 2021 21:41:56 +0200 Subject: [PATCH 37/64] Restore small icons in attribution, fix #413 --- Models/Constants.ts | 2 +- UI/BigComponents/Basemap.ts | 5 +++-- index.css | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 33a52c40f6..48c52eebfd 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.4"; + public static vNumber = "0.8.4-rc1"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/UI/BigComponents/Basemap.ts b/UI/BigComponents/Basemap.ts index 2da6415b6d..a4afd6ec8b 100644 --- a/UI/BigComponents/Basemap.ts +++ b/UI/BigComponents/Basemap.ts @@ -3,6 +3,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Loc from "../../Models/Loc"; import BaseLayer from "../../Models/BaseLayer"; import BaseUIElement from "../BaseUIElement"; +import {FixedUiElement} from "../Base/FixedUiElement"; export class Basemap { @@ -35,9 +36,8 @@ export class Basemap { ); this.map.attributionControl.setPrefix( - " | OpenStreetMap"); + "A"); - extraAttribution.AttachTo('leaflet-attribution') const self = this; currentLayer.addCallbackAndRun(layer => { @@ -77,6 +77,7 @@ export class Basemap { lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng}); }); + extraAttribution.AttachTo('leaflet-attribution') } diff --git a/index.css b/index.css index 47178a06f7..347c27a5d2 100644 --- a/index.css +++ b/index.css @@ -82,6 +82,10 @@ html, body { box-sizing: initial !important; } +.leaflet-control-attribution { + display: block ruby; +} + svg, img { box-sizing: content-box; width: 100%; From 4fd3f5cd4cc5a2f379eae492f19beb33c657bf1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= Date: Sun, 11 Jul 2021 12:25:16 +0200 Subject: [PATCH 38/64] Add link to Matrix channel on Element --- Docs/Making_Your_Own_Theme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Docs/Making_Your_Own_Theme.md b/Docs/Making_Your_Own_Theme.md index b669e986b7..8e550fe136 100644 --- a/Docs/Making_Your_Own_Theme.md +++ b/Docs/Making_Your_Own_Theme.md @@ -14,6 +14,7 @@ Before you start, you should have the following qualifications: - You are in contact with your local OpenStreetMap community and do know some other members to discuss tagging and to help testing If you do not have those qualifications, reach out to the MapComplete community channel on [Telegram](https://t.me/joinchat/HiMUavahRG--SCvC) +or [Matrix](https://app.element.io/#/room/#MapComplete:matrix.org). The custom theme generator -------------------------- From a78e3866e04d40ac73926e4c0d379da1452eb403 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 11 Jul 2021 12:52:33 +0200 Subject: [PATCH 39/64] Add GetParsed to localStorageSource --- Logic/Osm/Changes.ts | 4 ++-- Logic/Web/LocalStorageSource.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 9da36b048a..39b9f4e02f 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -25,13 +25,13 @@ export class Changes implements FeatureSource { /** * All the pending changes */ - public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = LocalStorageSource.GetParsed("pending-changes", []) + public readonly pending = LocalStorageSource.GetParsed<{ elementId: string, key: string, value: string }[]>("pending-changes", []) /** * All the pending new objects to upload * @private */ - private readonly newObjects: UIEventSource<{ id: number, lat: number, lon: number }[]> = LocalStorageSource.GetParsed("newObjects", []) + private readonly newObjects = LocalStorageSource.GetParsed<{ id: number, lat: number, lon: number }[]>("newObjects", []) /** * Adds a change to the pending changes diff --git a/Logic/Web/LocalStorageSource.ts b/Logic/Web/LocalStorageSource.ts index a89d2a5569..61009114af 100644 --- a/Logic/Web/LocalStorageSource.ts +++ b/Logic/Web/LocalStorageSource.ts @@ -5,7 +5,7 @@ import {UIEventSource} from "../UIEventSource"; */ export class LocalStorageSource { - static GetParsed(key: string, defaultValue : any){ + static GetParsed(key: string, defaultValue : T) : UIEventSource{ return LocalStorageSource.Get(key).map( str => { if(str === undefined){ From d581d3ec180c0efd525073f8760f6e275b62b609 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 11 Jul 2021 13:11:57 +0200 Subject: [PATCH 40/64] Update telegram link --- Docs/Making_Your_Own_Theme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Making_Your_Own_Theme.md b/Docs/Making_Your_Own_Theme.md index 8e550fe136..8549a8ab6f 100644 --- a/Docs/Making_Your_Own_Theme.md +++ b/Docs/Making_Your_Own_Theme.md @@ -13,7 +13,7 @@ Before you start, you should have the following qualifications: - You're theme will add well-understood tags (aka: the tags have a wiki page, are not controversial and are objective) - You are in contact with your local OpenStreetMap community and do know some other members to discuss tagging and to help testing -If you do not have those qualifications, reach out to the MapComplete community channel on [Telegram](https://t.me/joinchat/HiMUavahRG--SCvC) +If you do not have those qualifications, reach out to the MapComplete community channel on [Telegram](https://t.me/MapComplete) or [Matrix](https://app.element.io/#/room/#MapComplete:matrix.org). The custom theme generator From 8a83bef5a922292b369c55d004c30a726319fa37 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 11 Jul 2021 13:45:56 +0200 Subject: [PATCH 41/64] Add 'climbing=area' as alternative for 'climbing=site' --- assets/themes/climbing/climbing.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index aa8d113106..72a53780ba 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -547,7 +547,12 @@ } }, { - "if": "climbing=site", + "if": { + "or": [ + "climbing=site", + "climbing=area" + ] + }, "then": { "en": "Climbing site", "nl": "Klimsite" From 6e3c39e4758d5bfa832216e73e08cfca91b4b256 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 11 Jul 2021 15:44:17 +0200 Subject: [PATCH 42/64] Add option to show text field inline with the rendering; add option to fill out a default value --- Customizations/JSON/TagRenderingConfig.ts | 4 ++ Customizations/JSON/TagRenderingConfigJson.ts | 14 ++++- UI/Input/InputElementWrapper.ts | 35 +++++++++++ UI/Input/TextField.ts | 2 +- UI/Input/ValidatedTextField.ts | 2 +- UI/Popup/TagRenderingQuestion.ts | 22 +++++-- UI/SpecialVisualizations.ts | 1 + UI/SubstitutedTranslation.ts | 59 ++++++++++++++----- .../public_bookcase/public_bookcase.json | 3 +- index.css | 4 ++ 10 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 UI/Input/InputElementWrapper.ts diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index d7e55ed8d8..d3d4404939 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -26,6 +26,8 @@ export default class TagRenderingConfig { readonly key: string, readonly type: string, readonly addExtraTags: TagsFilter[]; + readonly inline: boolean, + readonly default?: string }; readonly multiAnswer: boolean; @@ -73,6 +75,8 @@ export default class TagRenderingConfig { type: json.freeform.type ?? "string", addExtraTags: json.freeform.addExtraTags?.map((tg, i) => FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [], + inline: json.freeform.inline ?? false, + default: json.freeform.default } diff --git a/Customizations/JSON/TagRenderingConfigJson.ts b/Customizations/JSON/TagRenderingConfigJson.ts index 7dfaae82bf..89871ec748 100644 --- a/Customizations/JSON/TagRenderingConfigJson.ts +++ b/Customizations/JSON/TagRenderingConfigJson.ts @@ -46,7 +46,19 @@ export interface TagRenderingConfigJson { **/ addExtraTags?: string[]; - + /** + * When set, influences the way a question is asked. + * Instead of showing a full-widht text field, the text field will be shown within the rendering of the question. + * + * This combines badly with special input elements, as it'll distort the layout. + */ + inline?: boolean + + /** + * default value to enter if no previous tagging is present. + * Normally undefined (aka do not enter anything) + */ + default?: string }, /** diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts new file mode 100644 index 0000000000..765a0d3b4b --- /dev/null +++ b/UI/Input/InputElementWrapper.ts @@ -0,0 +1,35 @@ +import {InputElement} from "./InputElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import BaseUIElement from "../BaseUIElement"; +import {Translation} from "../i18n/Translation"; +import {SubstitutedTranslation} from "../SubstitutedTranslation"; + +export default class InputElementWrapper extends InputElement { + public readonly IsSelected: UIEventSource; + private readonly _inputElement: InputElement; + private readonly _renderElement: BaseUIElement + + constructor(inputElement: InputElement, translation: Translation, key: string, tags: UIEventSource) { + super() + this._inputElement = inputElement; + this.IsSelected = inputElement.IsSelected + const mapping = new Map() + + mapping.set(key, inputElement) + + this._renderElement = new SubstitutedTranslation(translation, tags, mapping) + } + + GetValue(): UIEventSource { + return this._inputElement.GetValue(); + } + + IsValid(t: T): boolean { + return this._inputElement.IsValid(t); + } + + protected InnerConstructElement(): HTMLElement { + return this._renderElement.ConstructElement(); + } + +} \ No newline at end of file diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index 8f7d6ac442..da30733236 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -36,11 +36,11 @@ export class TextField extends InputElement { this.SetClass("form-text-field") let inputEl: HTMLElement if (options.htmlType === "area") { + this.SetClass("w-full box-border max-w-full") const el = document.createElement("textarea") el.placeholder = placeholder el.rows = options.textAreaRows el.cols = 50 - el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box" inputEl = el; } else { const el = document.createElement("input") diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 8ea3fb948b..2eeff8a543 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -282,7 +282,7 @@ export default class ValidatedTextField { }) ) unitDropDown.GetValue().setData(unit.defaultDenom) - unitDropDown.SetStyle("width: min-content") + unitDropDown.SetClass("w-min") input = new CombinedInputElement( input, diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 52b2962d81..20c0b00d21 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -24,6 +24,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils"; import BaseUIElement from "../BaseUIElement"; import {DropDown} from "../Input/DropDown"; import {Unit} from "../../Customizations/JSON/Denomination"; +import InputElementWrapper from "../Input/InputElementWrapper"; /** * Shows the question element. @@ -128,7 +129,7 @@ export default class TagRenderingQuestion extends Combine { } return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined: m.ifnot)) } - const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data); + const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource); const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0 if (mappings.length < 8 || configuration.multiAnswer || hasImages) { @@ -289,7 +290,7 @@ export default class TagRenderingQuestion extends Combine { (t0, t1) => t1.isEquivalent(t0)); } - private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement { + private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource): InputElement { const freeform = configuration.freeform; if (freeform === undefined) { return undefined; @@ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine { return undefined; } - let input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { + const tagsData = tags.data; + const input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { isValid: (str) => (str.length <= 255), country: () => tagsData._country, location: [tagsData._lat, tagsData._lon], @@ -336,12 +338,22 @@ export default class TagRenderingQuestion extends Combine { unit: applicableUnit }); - input.GetValue().setData(tagsData[configuration.freeform.key]); + input.GetValue().setData(tagsData[freeform.key] ?? freeform.default); - return new InputElementMap( + let inputTagsFilter : InputElement = new InputElementMap( input, (a, b) => a === b || (a?.isEquivalent(b) ?? false), pickString, toString ); + + if(freeform.inline){ + + inputTagsFilter.SetClass("w-16-imp") + inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags) + inputTagsFilter.SetClass("block") + + } + + return inputTagsFilter; } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 346e71e47a..2bbcbbb344 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -379,6 +379,7 @@ export default class SpecialVisualizations { } ] + static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); private static GenHelpMessage() { diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 03c7eb0747..43352aa5b4 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -7,19 +7,43 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio import {Utils} from "../Utils"; import {VariableUiElement} from "./Base/VariableUIElement"; import Combine from "./Base/Combine"; +import BaseUIElement from "./BaseUIElement"; export class SubstitutedTranslation extends VariableUiElement { public constructor( translation: Translation, - tagsSource: UIEventSource) { + tagsSource: UIEventSource, + mapping: Map = undefined) { + + const extraMappings: SpecialVisualization[] = []; + + mapping?.forEach((value, key) => { + console.log("KV:", key, value) + extraMappings.push( + { + funcName: key, + constr: (() => { + return value + }), + docs: "Dynamically injected input element", + args: [], + example: "" + } + ) + }) + super( Locale.language.map(language => { - const txt = translation.textFor(language) + let txt = translation.textFor(language); if (txt === undefined) { return undefined } - return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map( + mapping?.forEach((_, key) => { + txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`) + }) + + return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map( proto => { if (proto.fixed !== undefined) { return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags))); @@ -36,30 +60,35 @@ export class SubstitutedTranslation extends VariableUiElement { }) ) - this.SetClass("w-full") } - public static ExtractSpecialComponents(template: string): { - fixed?: string, special?: { + public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): { + fixed?: string, + special?: { func: SpecialVisualization, args: string[], style: string } }[] { - for (const knownSpecial of SpecialVisualizations.specialVisualizations) { + if (extraMappings.length > 0) { + + console.log("Extra mappings are", extraMappings) + } + + for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) { // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); if (matched != null) { // We found a special component that should be brought to live - const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]); + const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings); const argument = matched[2].trim(); const style = matched[3]?.substring(1) ?? "" - const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]); + const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings); const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); if (argument.length > 0) { const realArgs = argument.split(",").map(str => str.trim()); @@ -73,11 +102,13 @@ export class SubstitutedTranslation extends VariableUiElement { } let element; - element = {special:{ - args: args, - style: style, - func: knownSpecial - }} + element = { + special: { + args: args, + style: style, + func: knownSpecial + } + } return [...partBefore, element, ...partAfter] } } diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 896f97cea3..496b36ae19 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -137,7 +137,8 @@ }, "freeform": { "key": "capacity", - "type": "nat" + "type": "nat", + "inline": true } }, { diff --git a/index.css b/index.css index 347c27a5d2..0bd790f55c 100644 --- a/index.css +++ b/index.css @@ -105,6 +105,10 @@ a { width: min-content; } +.w-16-imp { + width: 4rem !important; +} + .space-between{ justify-content: space-between; } From 38231399c4bd92f033e9ef8abd483cc0ee742f58 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 11 Jul 2021 15:47:37 +0200 Subject: [PATCH 43/64] Use rounded-3xl for radio buttons instead of rounded-full --- UI/Input/RadioButton.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index fd5c006c2e..2822b2166f 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -103,7 +103,7 @@ export class RadioButton extends InputElement { const block = document.createElement("div") block.appendChild(input) block.appendChild(label) - block.classList.add("flex","w-full","border", "rounded-full", "border-gray-400","m-1") + block.classList.add("flex","w-full","border", "rounded-3xl", "border-gray-400","m-1") wrappers.push(block) form.appendChild(block) From 4eb131b3b103548c3be66daa1ea10357d1f12947 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 12 Jul 2021 00:05:56 +0200 Subject: [PATCH 44/64] Clip corners of the input-element-cone, which sometimes hid the save button resulting in unexpected click behaviour --- UI/Input/DirectionInput.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 12689d5d4d..93d932c667 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -66,6 +66,7 @@ export default class DirectionInput extends InputElement { }) this.RegisterTriggers(element) + element.style.overflow = "hidden" return element; } From 4276d4a47d92f8ef4901a036ee7849ead24b8235 Mon Sep 17 00:00:00 2001 From: Ward Date: Mon, 12 Jul 2021 11:34:01 +0200 Subject: [PATCH 45/64] auto generated stuff --- assets/layers/picnic_table/picnic_table.json | 9 +- assets/layers/playground/playground.json | 36 ++- .../public_bookcase/public_bookcase.json | 29 +- assets/layers/sport_pitch/sport_pitch.json | 51 ++-- .../surveillance_camera.json | 15 +- assets/layers/toilet/toilet.json | 24 +- assets/layers/tree_node/tree_node.json | 33 +- .../charging_stations/charging_stations.json | 13 +- assets/themes/shops/shops.json | 37 ++- langs/layers/ru.json | 284 +++++++++--------- langs/themes/nl.json | 112 +++---- 11 files changed, 363 insertions(+), 280 deletions(-) diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index e5be9d0774..672e41f07f 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -26,7 +26,8 @@ "en": "The layer showing picnic tables", "nl": "Deze laag toont picnictafels", "it": "Il livello che mostra i tavoli da picnic", - "fr": "La couche montrant les tables de pique-nique" + "fr": "La couche montrant les tables de pique-nique", + "ru": "Слой, отображающий столы для пикника" }, "tagRenderings": [ { @@ -34,13 +35,15 @@ "en": "What material is this picnic table made of?", "nl": "Van welk materiaal is deze picnictafel gemaakt?", "it": "Di che materiale è fatto questo tavolo da picnic?", - "de": "Aus welchem Material besteht dieser Picknicktisch?" + "de": "Aus welchem Material besteht dieser Picknicktisch?", + "ru": "Из чего изготовлен этот стол для пикника?" }, "render": { "en": "This picnic table is made of {material}", "nl": "Deze picnictafel is gemaakt van {material}", "it": "Questo tavolo da picnic è fatto di {material}", - "de": "Dieser Picknicktisch besteht aus {material}" + "de": "Dieser Picknicktisch besteht aus {material}", + "ru": "Этот стол для пикника сделан из {material}" }, "freeform": { "key": "material" diff --git a/assets/layers/playground/playground.json b/assets/layers/playground/playground.json index 54afa9a4c6..ec97eca2bf 100644 --- a/assets/layers/playground/playground.json +++ b/assets/layers/playground/playground.json @@ -93,7 +93,8 @@ "nl": "De ondergrond bestaat uit houtsnippers", "en": "The surface consist of woodchips", "it": "La superficie consiste di trucioli di legno", - "de": "Die Oberfläche besteht aus Holzschnitzeln" + "de": "Die Oberfläche besteht aus Holzschnitzeln", + "ru": "Покрытие из щепы" } }, { @@ -154,7 +155,8 @@ "en": "Is this playground lit at night?", "it": "È illuminato di notte questo parco giochi?", "fr": "Ce terrain de jeux est-il éclairé la nuit ?", - "de": "Ist dieser Spielplatz nachts beleuchtet?" + "de": "Ist dieser Spielplatz nachts beleuchtet?", + "ru": "Эта игровая площадка освещается ночью?" }, "mappings": [ { @@ -163,7 +165,8 @@ "nl": "Deze speeltuin is 's nachts verlicht", "en": "This playground is lit at night", "it": "Questo parco giochi è illuminato di notte", - "de": "Dieser Spielplatz ist nachts beleuchtet" + "de": "Dieser Spielplatz ist nachts beleuchtet", + "ru": "Эта детская площадка освещается ночью" } }, { @@ -172,7 +175,8 @@ "nl": "Deze speeltuin is 's nachts niet verlicht", "en": "This playground is not lit at night", "it": "Questo parco giochi non è illuminato di notte", - "de": "Dieser Spielplatz ist nachts nicht beleuchtet" + "de": "Dieser Spielplatz ist nachts nicht beleuchtet", + "ru": "Эта детская площадка не освещается ночью" } } ] @@ -189,7 +193,8 @@ "nl": "Wat is de minimale leeftijd om op deze speeltuin te mogen?", "en": "What is the minimum age required to access this playground?", "it": "Qual è l’età minima per accedere a questo parco giochi?", - "fr": "Quel est l'âge minimal requis pour accéder à ce terrain de jeux ?" + "fr": "Quel est l'âge minimal requis pour accéder à ce terrain de jeux ?", + "ru": "С какого возраста доступна эта детская площадка?" }, "freeform": { "key": "min_age", @@ -201,7 +206,8 @@ "nl": "Toegankelijk tot {max_age}", "en": "Accessible to kids of at most {max_age}", "it": "Accessibile ai bambini di età inferiore a {max_age}", - "fr": "Accessible aux enfants de {max_age} au maximum" + "fr": "Accessible aux enfants de {max_age} au maximum", + "ru": "Доступно детям до {max_age}" }, "question": { "nl": "Wat is de maximaal toegestane leeftijd voor deze speeltuin?", @@ -340,7 +346,8 @@ "en": "Is this playground accessible to wheelchair users?", "fr": "Ce terrain de jeux est-il accessible aux personnes en fauteuil roulant ?", "de": "Ist dieser Spielplatz für Rollstuhlfahrer zugänglich?", - "it": "Il campetto è accessibile a persone in sedia a rotelle?" + "it": "Il campetto è accessibile a persone in sedia a rotelle?", + "ru": "Доступна ли детская площадка пользователям кресел-колясок?" }, "mappings": [ { @@ -350,7 +357,8 @@ "en": "Completely accessible for wheelchair users", "fr": "Entièrement accessible aux personnes en fauteuil roulant", "de": "Vollständig zugänglich für Rollstuhlfahrer", - "it": "Completamente accessibile in sedia a rotelle" + "it": "Completamente accessibile in sedia a rotelle", + "ru": "Полностью доступна пользователям кресел-колясок" } }, { @@ -360,7 +368,8 @@ "en": "Limited accessibility for wheelchair users", "fr": "Accessibilité limitée pour les personnes en fauteuil roulant", "de": "Eingeschränkte Zugänglichkeit für Rollstuhlfahrer", - "it": "Accesso limitato in sedia a rotelle" + "it": "Accesso limitato in sedia a rotelle", + "ru": "Частично доступна пользователям кресел-колясок" } }, { @@ -370,7 +379,8 @@ "en": "Not accessible for wheelchair users", "fr": "Non accessible aux personnes en fauteuil roulant", "de": "Nicht zugänglich für Rollstuhlfahrer", - "it": "Non accessibile in sedia a rotelle" + "it": "Non accessibile in sedia a rotelle", + "ru": "Недоступна пользователям кресел-колясок" } } ] @@ -385,7 +395,8 @@ "nl": "Op welke uren is deze speeltuin toegankelijk?", "en": "When is this playground accessible?", "fr": "Quand ce terrain de jeux est-il accessible ?", - "it": "Quando si può accedere a questo campetto?" + "it": "Quando si può accedere a questo campetto?", + "ru": "Когда открыта эта игровая площадка?" }, "mappings": [ { @@ -394,7 +405,8 @@ "nl": "Van zonsopgang tot zonsondergang", "en": "Accessible from sunrise till sunset", "fr": "Accessible du lever au coucher du soleil", - "it": "Si può accedere dall'alba al tramonto" + "it": "Si può accedere dall'alba al tramonto", + "ru": "Открыто от рассвета до заката" } }, { diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 496b36ae19..fdb223d060 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -13,7 +13,8 @@ "nl": "Een straatkastje met boeken voor iedereen", "de": "Ein Bücherschrank am Straßenrand mit Büchern, für jedermann zugänglich", "fr": "Une armoire ou une boite contenant des livres en libre accès", - "it": "Una vetrinetta ai bordi della strada contenente libri, aperta al pubblico" + "it": "Una vetrinetta ai bordi della strada contenente libri, aperta al pubblico", + "ru": "Уличный шкаф с книгами, доступными для всех" }, "source": { "osmTags": "amenity=public_bookcase" @@ -94,7 +95,7 @@ "nl": "Wat is de naam van dit boekenuilkastje?", "de": "Wie heißt dieser öffentliche Bücherschrank?", "fr": "Quel est le nom de cette microbibliothèque ?", - "ru": "Как называется общественный книжный шкаф?", + "ru": "Как называется этот общественный книжный шкаф?", "it": "Come si chiama questa microbiblioteca pubblica?" }, "freeform": { @@ -125,7 +126,8 @@ "nl": "Er passen {capacity} boeken", "de": "{capacity} Bücher passen in diesen Bücherschrank", "fr": "{capacity} livres peuvent entrer dans cette microbibliothèque", - "it": "Questa microbiblioteca può contenere fino a {capacity} libri" + "it": "Questa microbiblioteca può contenere fino a {capacity} libri", + "ru": "{capacity} книг помещается в этот книжный шкаф" }, "question": { "en": "How many books fit into this public bookcase?", @@ -147,7 +149,8 @@ "nl": "Voor welke doelgroep zijn de meeste boeken in dit boekenruilkastje?", "de": "Welche Art von Büchern sind in diesem öffentlichen Bücherschrank zu finden?", "fr": "Quel type de livres peut-on dans cette microbibliothèque ?", - "it": "Che tipo di libri si possono trovare in questa microbiblioteca?" + "it": "Che tipo di libri si possono trovare in questa microbiblioteca?", + "ru": "Какие книги можно найти в этом общественном книжном шкафу?" }, "mappings": [ { @@ -179,7 +182,8 @@ "nl": "Boeken voor zowel kinderen als volwassenen", "de": "Sowohl Bücher für Kinder als auch für Erwachsene", "fr": "Livres pour enfants et adultes également", - "it": "Sia libri per l'infanzia, sia per l'età adulta" + "it": "Sia libri per l'infanzia, sia per l'età adulta", + "ru": "Книги и для детей, и для взрослых" } } ] @@ -232,7 +236,8 @@ "nl": "Is dit boekenruilkastje publiek toegankelijk?", "de": "Ist dieser öffentliche Bücherschrank frei zugänglich?", "fr": "Cette microbibliothèque est-elle librement accèssible ?", - "it": "Questa microbiblioteca è ad accesso libero?" + "it": "Questa microbiblioteca è ad accesso libero?", + "ru": "Имеется ли свободный доступ к этому общественному книжному шкафу?" }, "condition": "indoor=yes", "mappings": [ @@ -242,7 +247,8 @@ "nl": "Publiek toegankelijk", "de": "Öffentlich zugänglich", "fr": "Accèssible au public", - "it": "È ad accesso libero" + "it": "È ad accesso libero", + "ru": "Свободный доступ" }, "if": "access=yes" }, @@ -374,14 +380,16 @@ "nl": "Op welke dag werd dit boekenruilkastje geinstalleerd?", "de": "Wann wurde dieser öffentliche Bücherschrank installiert?", "fr": "Quand a été installée cette microbibliothèque ?", - "it": "Quando è stata inaugurata questa microbiblioteca?" + "it": "Quando è stata inaugurata questa microbiblioteca?", + "ru": "Когда был установлен этот общественный книжный шкаф?" }, "render": { "en": "Installed on {start_date}", "nl": "Geplaatst op {start_date}", "de": "Installiert am {start_date}", "fr": "Installée le {start_date}", - "it": "È stata inaugurata il {start_date}" + "it": "È stata inaugurata il {start_date}", + "ru": "Установлен {start_date}" }, "freeform": { "key": "start_date", @@ -402,7 +410,8 @@ "nl": "Is er een website over dit boekenruilkastje?", "de": "Gibt es eine Website mit weiteren Informationen über diesen öffentlichen Bücherschrank?", "fr": "Y a-t-il un site web avec plus d'informations sur cette microbibliothèque ?", - "it": "C'è un sito web con maggiori informazioni su questa microbiblioteca?" + "it": "C'è un sito web con maggiori informazioni su questa microbiblioteca?", + "ru": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?" }, "freeform": { "key": "website", diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json index efb49a0b2d..140e222b2a 100644 --- a/assets/layers/sport_pitch/sport_pitch.json +++ b/assets/layers/sport_pitch/sport_pitch.json @@ -32,7 +32,8 @@ "nl": "Een sportterrein", "fr": "Un terrain de sport", "en": "A sport pitch", - "it": "Un campo sportivo" + "it": "Un campo sportivo", + "ru": "Спортивная площадка" }, "tagRenderings": [ "images", @@ -64,7 +65,8 @@ "nl": "Hier kan men basketbal spelen", "fr": "Ici, on joue au basketball", "en": "Basketball is played here", - "it": "Qui si gioca a basket" + "it": "Qui si gioca a basket", + "ru": "Здесь можно играть в баскетбол" } }, { @@ -77,7 +79,8 @@ "nl": "Hier kan men voetbal spelen", "fr": "Ici, on joue au football", "en": "Soccer is played here", - "it": "Qui si gioca a calcio" + "it": "Qui si gioca a calcio", + "ru": "Здесь можно играть в футбол" } }, { @@ -104,7 +107,8 @@ "nl": "Hier kan men tennis spelen", "fr": "Ici, on joue au tennis", "en": "Tennis is played here", - "it": "Qui si gioca a tennis" + "it": "Qui si gioca a tennis", + "ru": "Здесь можно играть в теннис" } }, { @@ -117,7 +121,8 @@ "nl": "Hier kan men korfbal spelen", "fr": "Ici, on joue au korfball", "en": "Korfball is played here", - "it": "Qui si gioca a korfball" + "it": "Qui si gioca a korfball", + "ru": "Здесь можно играть в корфбол" } }, { @@ -130,7 +135,8 @@ "nl": "Hier kan men basketbal beoefenen", "fr": "Ici, on joue au basketball", "en": "Basketball is played here", - "it": "Qui si gioca a basket" + "it": "Qui si gioca a basket", + "ru": "Здесь можно играть в баскетбол" }, "hideInAnswer": true } @@ -141,7 +147,8 @@ "nl": "Wat is de ondergrond van dit sportveld?", "fr": "De quelle surface est fait ce terrain de sport ?", "en": "Which is the surface of this sport pitch?", - "it": "Qual è la superficie di questo campo sportivo?" + "it": "Qual è la superficie di questo campo sportivo?", + "ru": "Какое покрытие на этой спортивной площадке?" }, "render": { "nl": "De ondergrond is {surface}", @@ -211,7 +218,8 @@ "nl": "Is dit sportterrein publiek toegankelijk?", "fr": "Est-ce que ce terrain de sport est accessible au public ?", "en": "Is this sport pitch publicly accessible?", - "it": "Questo campo sportivo è aperto al pubblico?" + "it": "Questo campo sportivo è aperto al pubblico?", + "ru": "Есть ли свободный доступ к этой спортивной площадке?" }, "mappings": [ { @@ -220,7 +228,8 @@ "nl": "Publiek toegankelijk", "fr": "Accessible au public", "en": "Public access", - "it": "Aperto al pubblico" + "it": "Aperto al pubblico", + "ru": "Свободный доступ" } }, { @@ -229,7 +238,8 @@ "nl": "Beperkt toegankelijk (enkel na reservatie, tijdens bepaalde uren, ...)", "fr": "Accès limité (par exemple uniquement sur réservation, à certains horaires…)", "en": "Limited access (e.g. only with an appointment, during certain hours, ...)", - "it": "Accesso limitato (p.es. solo con prenotazione, in certi orari, ...)" + "it": "Accesso limitato (p.es. solo con prenotazione, in certi orari, ...)", + "ru": "Ограниченный доступ (напр., только по записи, в определённые часы, ...)" } }, { @@ -238,7 +248,8 @@ "nl": "Enkel toegankelijk voor leden van de bijhorende sportclub", "fr": "Accessible uniquement aux membres du club", "en": "Only accessible for members of the club", - "it": "Accesso limitato ai membri dell'associazione" + "it": "Accesso limitato ai membri dell'associazione", + "ru": "Доступ только членам клуба" } }, { @@ -257,7 +268,8 @@ "nl": "Moet men reserveren om gebruik te maken van dit sportveld?", "fr": "Doit-on réserver pour utiliser ce terrain de sport ?", "en": "Does one have to make an appointment to use this sport pitch?", - "it": "È necessario prenotarsi per usare questo campo sportivo?" + "it": "È necessario prenotarsi per usare questo campo sportivo?", + "ru": "Нужна ли предварительная запись для доступа на эту спортивную площадку?" }, "condition": { "and": [ @@ -282,7 +294,8 @@ "nl": "Reserveren is sterk aangeraden om gebruik te maken van dit sportterrein", "fr": "Il est recommendé de réserver pour utiliser ce terrain de sport", "en": "Making an appointment is recommended when using this sport pitch", - "it": "La prenotazione è consigliata per usare questo campo sportivo" + "it": "La prenotazione è consigliata per usare questo campo sportivo", + "ru": "Желательна предварительная запись для доступа на эту спортивную площадку" } }, { @@ -291,7 +304,8 @@ "nl": "Reserveren is mogelijk, maar geen voorwaarde", "fr": "Il est possible de réserver, mais ce n'est pas nécéssaire pour utiliser ce terrain de sport", "en": "Making an appointment is possible, but not necessary to use this sport pitch", - "it": "La prenotazione è consentita, ma non è obbligatoria per usare questo campo sportivo" + "it": "La prenotazione è consentita, ma non è obbligatoria per usare questo campo sportivo", + "ru": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна" } }, { @@ -300,7 +314,8 @@ "nl": "Reserveren is niet mogelijk", "fr": "On ne peut pas réserver", "en": "Making an appointment is not possible", - "it": "Non è possibile prenotare" + "it": "Non è possibile prenotare", + "ru": "Невозможна предварительная запись" } } ] @@ -336,7 +351,8 @@ "nl": "Wanneer is dit sportveld toegankelijk?", "fr": "Quand ce terrain est-il accessible ?", "en": "When is this pitch accessible?", - "it": "Quando è aperto questo campo sportivo?" + "it": "Quando è aperto questo campo sportivo?", + "ru": "В какое время доступна эта площадка?" }, "render": "Openingsuren: {opening_hours_table()}", "freeform": { @@ -446,7 +462,8 @@ "nl": "Ping-pong tafel", "fr": "Table de ping-pong", "en": "Tabletennis table", - "it": "Tavolo da tennistavolo" + "it": "Tavolo da tennistavolo", + "ru": "Стол для настольного тенниса" }, "tags": [ "leisure=pitch", diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index e5c872be25..20f1e77bf0 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -39,7 +39,8 @@ "en": "What kind of camera is this?", "nl": "Wat voor soort camera is dit?", "fr": "Quel genre de caméra est-ce ?", - "it": "Di che tipo di videocamera si tratta?" + "it": "Di che tipo di videocamera si tratta?", + "ru": "Какая это камера?" }, "mappings": [ { @@ -65,7 +66,8 @@ "en": "A dome camera (which can turn)", "nl": "Een dome (bolvormige camera die kan draaien)", "fr": "Une caméra dôme (qui peut tourner)", - "it": "Una videocamera a cupola (che può ruotare)" + "it": "Una videocamera a cupola (che può ruotare)", + "ru": "Камера с поворотным механизмом" } }, { @@ -230,7 +232,8 @@ "en": "This camera is located outdoors", "nl": "Deze camera bevindt zich buiten", "fr": "Cette caméra est située à l'extérieur", - "it": "Questa videocamera si trova all'aperto" + "it": "Questa videocamera si trova all'aperto", + "ru": "Эта камера расположена снаружи" } }, { @@ -239,7 +242,8 @@ "en": "This camera is probably located outdoors", "nl": "Deze camera bevindt zich waarschijnlijk buiten", "fr": "Cette caméra est probablement située à l'extérieur", - "it": "Questa videocamera si trova probabilmente all'esterno" + "it": "Questa videocamera si trova probabilmente all'esterno", + "ru": "Возможно, эта камера расположена снаружи" }, "hideInAnswer": true } @@ -374,7 +378,8 @@ "en": "How is this camera placed?", "nl": "Hoe is deze camera geplaatst?", "fr": "Comment cette caméra est-elle placée ?", - "it": "Com'è posizionata questa telecamera?" + "it": "Com'è posizionata questa telecamera?", + "ru": "Как расположена эта камера?" }, "render": { "en": "Mounting method: {mount}", diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 06ea04860b..a33ddf7423 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -57,7 +57,8 @@ "de": "Eine öffentlich zugängliche Toilette", "fr": "Des toilettes", "nl": "Een publieke toilet", - "it": "Servizi igienici aperti al pubblico" + "it": "Servizi igienici aperti al pubblico", + "ru": "Туалет или комната отдыха со свободным доступом" } }, { @@ -66,7 +67,8 @@ "de": "Toiletten mit rollstuhlgerechter Toilette", "fr": "Toilettes accessible aux personnes à mobilité réduite", "nl": "Een rolstoeltoegankelijke toilet", - "it": "Servizi igienici accessibili per persone in sedia a rotelle" + "it": "Servizi igienici accessibili per persone in sedia a rotelle", + "ru": "Туалет с доступом для пользователей кресел-колясок" }, "tags": [ "amenity=toilets", @@ -89,7 +91,8 @@ "de": "Sind diese Toiletten öffentlich zugänglich?", "fr": "Ces toilettes sont-elles accessibles au public ?", "nl": "Zijn deze toiletten publiek toegankelijk?", - "it": "Questi servizi igienici sono aperti al pubblico?" + "it": "Questi servizi igienici sono aperti al pubblico?", + "ru": "Есть ли свободный доступ к этим туалетам?" }, "render": { "en": "Access is {access}", @@ -112,7 +115,8 @@ "de": "Öffentlicher Zugang", "fr": "Accès publique", "nl": "Publiek toegankelijk", - "it": "Accesso pubblico" + "it": "Accesso pubblico", + "ru": "Свободный доступ" } }, { @@ -186,14 +190,16 @@ "de": "Wie viel muss man für diese Toiletten bezahlen?", "fr": "Quel est le prix d'accès de ces toilettes ?", "nl": "Hoeveel moet men betalen om deze toiletten te gebruiken?", - "it": "Quanto costa l'accesso a questi servizi igienici?" + "it": "Quanto costa l'accesso a questi servizi igienici?", + "ru": "Сколько стоит посещение туалета?" }, "render": { "en": "The fee is {charge}", "de": "Die Gebühr beträgt {charge}", "fr": "Le prix est {charge}", "nl": "De toiletten gebruiken kost {charge}", - "it": "La tariffa è {charge}" + "it": "La tariffa è {charge}", + "ru": "Стоимость {charge}" }, "condition": "fee=yes", "freeform": { @@ -227,7 +233,8 @@ "de": "Kein Zugang für Rollstuhlfahrer", "fr": "Non accessible aux personnes à mobilité réduite", "nl": "Niet toegankelijk voor rolstoelgebruikers", - "it": "Non accessibile in sedia a rotelle" + "it": "Non accessibile in sedia a rotelle", + "ru": "Недоступно пользователям кресел-колясок" } } ] @@ -238,7 +245,8 @@ "de": "Welche Art von Toiletten sind das?", "fr": "De quel type sont ces toilettes ?", "nl": "Welke toiletten zijn dit?", - "it": "Di che tipo di servizi igienici si tratta?" + "it": "Di che tipo di servizi igienici si tratta?", + "ru": "Какие это туалеты?" }, "mappings": [ { diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index a95bfdb013..1f3cc7fdeb 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -230,7 +230,8 @@ "question": { "nl": "Is deze boom groenblijvend of bladverliezend?", "en": "Is this tree evergreen or deciduous?", - "it": "È un sempreverde o caduco?" + "it": "È un sempreverde o caduco?", + "ru": "Это дерево вечнозелёное или листопадное?" }, "mappings": [ { @@ -242,7 +243,8 @@ "then": { "nl": "Bladverliezend: de boom is een periode van het jaar kaal.", "en": "Deciduous: the tree loses its leaves for some time of the year.", - "it": "Caduco: l’albero perde le sue foglie per un periodo dell’anno." + "it": "Caduco: l’albero perde le sue foglie per un periodo dell’anno.", + "ru": "Листопадное: у дерева опадают листья в определённое время года." } }, { @@ -255,7 +257,8 @@ "nl": "Groenblijvend.", "en": "Evergreen.", "it": "Sempreverde.", - "fr": "À feuilles persistantes." + "fr": "À feuilles persistantes.", + "ru": "Вечнозелёное." } } ], @@ -278,7 +281,8 @@ "nl": "Heeft de boom een naam?", "en": "Does the tree have a name?", "it": "L’albero ha un nome?", - "fr": "L'arbre a-t-il un nom ?" + "fr": "L'arbre a-t-il un nom ?", + "ru": "Есть ли у этого дерева название?" }, "freeform": { "key": "name", @@ -298,7 +302,8 @@ "nl": "De boom heeft geen naam.", "en": "The tree does not have a name.", "it": "L’albero non ha un nome.", - "fr": "L'arbre n'a pas de nom." + "fr": "L'arbre n'a pas de nom.", + "ru": "У этого дерева нет названия." } } ], @@ -399,7 +404,8 @@ "render": { "nl": "\"\"/ Onroerend Erfgoed-ID: {ref:OnroerendErfgoed}", "en": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", - "it": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}" + "it": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}", + "ru": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}" }, "question": { "nl": "Wat is het ID uitgegeven door Onroerend Erfgoed Vlaanderen?", @@ -421,7 +427,8 @@ "render": { "nl": "\"\"/ Wikidata: {wikidata}", "en": "\"\"/ Wikidata: {wikidata}", - "it": "\"\"/ Wikidata: {wikidata}" + "it": "\"\"/ Wikidata: {wikidata}", + "ru": "\"\"/ Wikidata: {wikidata}" }, "question": { "nl": "Wat is het Wikidata-ID van deze boom?", @@ -484,7 +491,8 @@ "nl": "Loofboom", "en": "Broadleaved tree", "it": "Albero latifoglia", - "fr": "Arbre feuillu" + "fr": "Arbre feuillu", + "ru": "Лиственное дерево" }, "description": { "nl": "Een boom van een soort die blaadjes heeft, bijvoorbeeld eik of populier.", @@ -501,12 +509,14 @@ "title": { "nl": "Naaldboom", "en": "Needleleaved tree", - "it": "Albero aghifoglia" + "it": "Albero aghifoglia", + "ru": "Хвойное дерево" }, "description": { "nl": "Een boom van een soort met naalden, bijvoorbeeld den of spar.", "en": "A tree of a species with needles, such as pine or spruce.", - "it": "Un albero di una specie con aghi come il pino o l’abete." + "it": "Un albero di una specie con aghi come il pino o l’abete.", + "ru": "Дерево с хвоей (иглами), например, сосна или ель." } }, { @@ -524,7 +534,8 @@ "nl": "Wanneer je niet zeker bent of het nu een loof- of naaldboom is.", "en": "If you're not sure whether it's a broadleaved or needleleaved tree.", "it": "Qualora non si sia sicuri se si tratta di un albero latifoglia o aghifoglia.", - "fr": "Si vous n'êtes pas sûr(e) de savoir s'il s'agit d'un arbre à feuilles larges ou à aiguilles." + "fr": "Si vous n'êtes pas sûr(e) de savoir s'il s'agit d'un arbre à feuilles larges ou à aiguilles.", + "ru": "Если вы не уверены в том, лиственное это дерево или хвойное." } } ] diff --git a/assets/themes/charging_stations/charging_stations.json b/assets/themes/charging_stations/charging_stations.json index fc5040c6cb..159cd5819f 100644 --- a/assets/themes/charging_stations/charging_stations.json +++ b/assets/themes/charging_stations/charging_stations.json @@ -6,7 +6,8 @@ "ru": "Зарядные станции", "ja": "充電ステーション", "zh_Hant": "充電站", - "it": "Stazioni di ricarica" + "it": "Stazioni di ricarica", + "nl": "Oplaadpunten" }, "shortDescription": { "en": "A worldwide map of charging stations", @@ -29,6 +30,7 @@ "ja", "zh_Hant", "it", + "nl", "nb_NO" ], "maintainer": "", @@ -48,7 +50,8 @@ "ja": "充電ステーション", "zh_Hant": "充電站", "nb_NO": "Ladestasjoner", - "it": "Stazioni di ricarica" + "it": "Stazioni di ricarica", + "nl": "Oplaadpunten" }, "minzoom": 10, "source": { @@ -65,7 +68,8 @@ "ja": "充電ステーション", "zh_Hant": "充電站", "nb_NO": "Ladestasjon", - "it": "Stazione di ricarica" + "it": "Stazione di ricarica", + "nl": "Oplaadpunt" } }, "description": { @@ -74,7 +78,8 @@ "ja": "充電ステーション", "zh_Hant": "充電站", "nb_NO": "En ladestasjon", - "it": "Una stazione di ricarica" + "it": "Una stazione di ricarica", + "nl": "Een oplaadpunt" }, "tagRenderings": [ "images", diff --git a/assets/themes/shops/shops.json b/assets/themes/shops/shops.json index e50029a62b..3883a9ef83 100644 --- a/assets/themes/shops/shops.json +++ b/assets/themes/shops/shops.json @@ -23,6 +23,7 @@ "ja", "zh_Hant", "ru", + "nl", "ca", "id" ], @@ -41,7 +42,8 @@ "en": "Shop", "fr": "Magasin", "ru": "Магазин", - "ja": "店" + "ja": "店", + "nl": "Winkel" }, "minzoom": 16, "source": { @@ -56,7 +58,8 @@ "en": "Shop", "fr": "Magasin", "ru": "Магазин", - "ja": "店" + "ja": "店", + "nl": "Winkel" }, "mappings": [ { @@ -90,7 +93,8 @@ "description": { "en": "A shop", "fr": "Un magasin", - "ja": "ショップ" + "ja": "ショップ", + "nl": "Een winkel" }, "tagRenderings": [ "images", @@ -99,7 +103,8 @@ "en": "What is the name of this shop?", "fr": "Qu'est-ce que le nom de ce magasin?", "ru": "Как называется магазин?", - "ja": "このお店の名前は何ですか?" + "ja": "このお店の名前は何ですか?", + "nl": "Wat is de naam van deze winkel?" }, "render": "This shop is called {name}", "freeform": { @@ -143,7 +148,8 @@ "en": "Supermarket", "fr": "Supermarché", "ru": "Супермаркет", - "ja": "スーパーマーケット" + "ja": "スーパーマーケット", + "nl": "Supermarkt" } }, { @@ -169,7 +175,8 @@ "en": "Hairdresser", "fr": "Coiffeur", "ru": "Парикмахерская", - "ja": "理容師" + "ja": "理容師", + "nl": "Kapper" } }, { @@ -181,7 +188,8 @@ "then": { "en": "Bakery", "fr": "Boulangerie", - "ja": "ベーカリー" + "ja": "ベーカリー", + "nl": "Bakkerij" } }, { @@ -223,7 +231,8 @@ "question": { "en": "What is the phone number?", "fr": "Quel est le numéro de téléphone ?", - "ja": "電話番号は何番ですか?" + "ja": "電話番号は何番ですか?", + "nl": "Wat is het telefoonnummer?" }, "freeform": { "key": "phone", @@ -242,7 +251,8 @@ "question": { "en": "What is the website of this shop?", "fr": "Quel est le site internet de ce magasin ?", - "ja": "このお店のホームページは何ですか?" + "ja": "このお店のホームページは何ですか?", + "nl": "Wat is de website van deze winkel?" }, "freeform": { "key": "website", @@ -277,7 +287,8 @@ "question": { "en": "What are the opening hours of this shop?", "fr": "Quels sont les horaires d'ouverture de ce magasin ?", - "ja": "この店の営業時間は何時から何時までですか?" + "ja": "この店の営業時間は何時から何時までですか?", + "nl": "Wat zijn de openingsuren van deze winkel?" }, "freeform": { "key": "opening_hours", @@ -316,13 +327,15 @@ "en": "Shop", "fr": "Magasin", "ru": "Магазин", - "ja": "店" + "ja": "店", + "nl": "Winkel" }, "description": { "en": "Add a new shop", "fr": "Ajouter un nouveau magasin", "ru": "Добавить новый магазин", - "ja": "新しい店を追加する" + "ja": "新しい店を追加する", + "nl": "Voeg een nieuwe winkel toe" } } ], diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 55015961ab..0cd328a6ae 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -603,8 +603,11 @@ "title": { "render": "Стол для пикника" }, + "description": "Слой, отображающий столы для пикника", "tagRenderings": { "0": { + "question": "Из чего изготовлен этот стол для пикника?", + "render": "Этот стол для пикника сделан из {material}", "mappings": { "0": { "then": "Это деревянный стол для пикника" @@ -612,17 +615,14 @@ "1": { "then": "Это бетонный стол для пикника" } - }, - "render": "Этот стол для пикника сделан из {material}", - "question": "Из чего изготовлен этот стол для пикника?" + } } }, "presets": { "0": { "title": "Стол для пикника" } - }, - "description": "Слой, отображающий столы для пикника" + } }, "playground": { "name": "Детские площадки", @@ -645,6 +645,9 @@ "1": { "then": "Поверхность - песок" }, + "2": { + "then": "Покрытие из щепы" + }, "3": { "then": "Поверхность - брусчатка" }, @@ -653,9 +656,17 @@ }, "5": { "then": "Поверхность - бетон" + } + } + }, + "2": { + "question": "Эта игровая площадка освещается ночью?", + "mappings": { + "0": { + "then": "Эта детская площадка освещается ночью" }, - "2": { - "then": "Покрытие из щепы" + "1": { + "then": "Эта детская площадка не освещается ночью" } } }, @@ -663,6 +674,9 @@ "render": "Доступно для детей старше {min_age} лет", "question": "С какого возраста доступна эта детская площадка?" }, + "4": { + "render": "Доступно детям до {max_age}" + }, "6": { "mappings": { "4": { @@ -676,47 +690,33 @@ "8": { "render": "{phone}" }, - "10": { - "mappings": { - "1": { - "then": "Всегда доступен" - }, - "2": { - "then": "Всегда доступен" - }, - "0": { - "then": "Открыто от рассвета до заката" - } - }, - "question": "Когда открыта эта игровая площадка?" - }, "9": { + "question": "Доступна ли детская площадка пользователям кресел-колясок?", "mappings": { - "2": { - "then": "Недоступна пользователям кресел-колясок" + "0": { + "then": "Полностью доступна пользователям кресел-колясок" }, "1": { "then": "Частично доступна пользователям кресел-колясок" }, - "0": { - "then": "Полностью доступна пользователям кресел-колясок" + "2": { + "then": "Недоступна пользователям кресел-колясок" } - }, - "question": "Доступна ли детская площадка пользователям кресел-колясок?" + } }, - "4": { - "render": "Доступно детям до {max_age}" - }, - "2": { + "10": { + "question": "Когда открыта эта игровая площадка?", "mappings": { - "1": { - "then": "Эта детская площадка не освещается ночью" - }, "0": { - "then": "Эта детская площадка освещается ночью" + "then": "Открыто от рассвета до заката" + }, + "1": { + "then": "Всегда доступен" + }, + "2": { + "then": "Всегда доступен" } - }, - "question": "Эта игровая площадка освещается ночью?" + } } }, "presets": { @@ -727,6 +727,7 @@ }, "public_bookcase": { "name": "Книжные шкафы", + "description": "Уличный шкаф с книгами, доступными для всех", "title": { "render": "Книжный шкаф", "mappings": { @@ -751,10 +752,11 @@ } }, "3": { - "question": "Сколько книг помещается в этом общественном книжном шкафу?", - "render": "{capacity} книг помещается в этот книжный шкаф" + "render": "{capacity} книг помещается в этот книжный шкаф", + "question": "Сколько книг помещается в этом общественном книжном шкафу?" }, "4": { + "question": "Какие книги можно найти в этом общественном книжном шкафу?", "mappings": { "0": { "then": "В основном детские книги" @@ -765,27 +767,25 @@ "2": { "then": "Книги и для детей, и для взрослых" } - }, - "question": "Какие книги можно найти в этом общественном книжном шкафу?" - }, - "11": { - "render": "Более подробная информация на сайте", - "question": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?" - }, - "10": { - "render": "Установлен {start_date}", - "question": "Когда был установлен этот общественный книжный шкаф?" + } }, "6": { + "question": "Имеется ли свободный доступ к этому общественному книжному шкафу?", "mappings": { "0": { "then": "Свободный доступ" } - }, - "question": "Имеется ли свободный доступ к этому общественному книжному шкафу?" + } + }, + "10": { + "question": "Когда был установлен этот общественный книжный шкаф?", + "render": "Установлен {start_date}" + }, + "11": { + "render": "Более подробная информация на сайте", + "question": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?" } - }, - "description": "Уличный шкаф с книгами, доступными для всех" + } }, "slow_roads": { "tagRenderings": { @@ -819,30 +819,32 @@ "title": { "render": "Спортивная площадка" }, + "description": "Спортивная площадка", "tagRenderings": { "1": { "mappings": { - "2": { - "then": "Это стол для пинг-понга" - }, - "5": { + "0": { "then": "Здесь можно играть в баскетбол" }, - "4": { - "then": "Здесь можно играть в корфбол" - }, - "3": { - "then": "Здесь можно играть в теннис" - }, "1": { "then": "Здесь можно играть в футбол" }, - "0": { + "2": { + "then": "Это стол для пинг-понга" + }, + "3": { + "then": "Здесь можно играть в теннис" + }, + "4": { + "then": "Здесь можно играть в корфбол" + }, + "5": { "then": "Здесь можно играть в баскетбол" } } }, "2": { + "question": "Какое покрытие на этой спортивной площадке?", "render": "Поверхность - {surface}", "mappings": { "0": { @@ -860,55 +862,53 @@ "4": { "then": "Поверхность - бетон" } - }, - "question": "Какое покрытие на этой спортивной площадке?" - }, - "7": { - "mappings": { - "1": { - "then": "Всегда доступен" - } - }, - "question": "В какое время доступна эта площадка?" - }, - "4": { - "mappings": { - "1": { - "then": "Желательна предварительная запись для доступа на эту спортивную площадку" - }, - "3": { - "then": "Невозможна предварительная запись" - }, - "2": { - "then": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна" - } - }, - "question": "Нужна ли предварительная запись для доступа на эту спортивную площадку?" + } }, "3": { + "question": "Есть ли свободный доступ к этой спортивной площадке?", "mappings": { - "2": { - "then": "Доступ только членам клуба" + "0": { + "then": "Свободный доступ" }, "1": { "then": "Ограниченный доступ (напр., только по записи, в определённые часы, ...)" }, - "0": { - "then": "Свободный доступ" + "2": { + "then": "Доступ только членам клуба" } - }, - "question": "Есть ли свободный доступ к этой спортивной площадке?" + } + }, + "4": { + "question": "Нужна ли предварительная запись для доступа на эту спортивную площадку?", + "mappings": { + "1": { + "then": "Желательна предварительная запись для доступа на эту спортивную площадку" + }, + "2": { + "then": "Предварительная запись для доступа на эту спортивную площадку возможна, но не обязательна" + }, + "3": { + "then": "Невозможна предварительная запись" + } + } + }, + "7": { + "question": "В какое время доступна эта площадка?", + "mappings": { + "1": { + "then": "Всегда доступен" + } + } } }, "presets": { - "1": { - "title": "Спортивная площадка" - }, "0": { "title": "Стол для настольного тенниса" + }, + "1": { + "title": "Спортивная площадка" } - }, - "description": "Спортивная площадка" + } }, "surveillance_camera": { "name": "Камеры наблюдения", @@ -917,28 +917,28 @@ }, "tagRenderings": { "1": { + "question": "Какая это камера?", "mappings": { - "2": { - "then": "Панорамная камера" - }, "1": { "then": "Камера с поворотным механизмом" + }, + "2": { + "then": "Панорамная камера" } - }, - "question": "Какая это камера?" - }, - "8": { - "question": "Как расположена эта камера?" + } }, "5": { "mappings": { - "2": { - "then": "Возможно, эта камера расположена снаружи" - }, "1": { "then": "Эта камера расположена снаружи" + }, + "2": { + "then": "Возможно, эта камера расположена снаружи" } } + }, + "8": { + "question": "Как расположена эта камера?" } } }, @@ -958,15 +958,15 @@ }, "tagRenderings": { "1": { + "question": "Есть ли свободный доступ к этим туалетам?", "mappings": { - "2": { - "then": "Недоступно" - }, "0": { "then": "Свободный доступ" + }, + "2": { + "then": "Недоступно" } - }, - "question": "Есть ли свободный доступ к этим туалетам?" + } }, "2": { "mappings": { @@ -975,8 +975,9 @@ } } }, - "5": { - "question": "Какие это туалеты?" + "3": { + "question": "Сколько стоит посещение туалета?", + "render": "Стоимость {charge}" }, "4": { "mappings": { @@ -985,9 +986,8 @@ } } }, - "3": { - "render": "Стоимость {charge}", - "question": "Сколько стоит посещение туалета?" + "5": { + "question": "Какие это туалеты?" } } }, @@ -1010,44 +1010,44 @@ } } }, + "4": { + "question": "Это дерево вечнозелёное или листопадное?", + "mappings": { + "0": { + "then": "Листопадное: у дерева опадают листья в определённое время года." + }, + "1": { + "then": "Вечнозелёное." + } + } + }, "5": { "render": "Название: {name}", + "question": "Есть ли у этого дерева название?", "mappings": { "0": { "then": "У этого дерева нет названия." } - }, - "question": "Есть ли у этого дерева название?" - }, - "8": { - "render": "\"\"/ Wikidata: {wikidata}" + } }, "7": { "render": "\"\"/ Onroerend Erfgoed ID: {ref:OnroerendErfgoed}" }, - "4": { - "mappings": { - "1": { - "then": "Вечнозелёное." - }, - "0": { - "then": "Листопадное: у дерева опадают листья в определённое время года." - } - }, - "question": "Это дерево вечнозелёное или листопадное?" + "8": { + "render": "\"\"/ Wikidata: {wikidata}" } }, "presets": { + "0": { + "title": "Лиственное дерево" + }, + "1": { + "title": "Хвойное дерево", + "description": "Дерево с хвоей (иглами), например, сосна или ель." + }, "2": { "title": "Дерево", "description": "Если вы не уверены в том, лиственное это дерево или хвойное." - }, - "1": { - "description": "Дерево с хвоей (иглами), например, сосна или ель.", - "title": "Хвойное дерево" - }, - "0": { - "title": "Лиственное дерево" } } }, diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 6d7f84a0a7..7966e9e2d1 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -278,6 +278,18 @@ } } }, + "charging_stations": { + "title": "Oplaadpunten", + "layers": { + "0": { + "name": "Oplaadpunten", + "title": { + "render": "Oplaadpunt" + }, + "description": "Een oplaadpunt" + } + } + }, "climbing": { "title": "Open Klimkaart", "description": "Op deze kaart vind je verschillende klimgelegenheden, zoals klimzalen, bolderzalen en klimmen in de natuur", @@ -924,6 +936,50 @@ "shortDescription": "Een kaart met speeltuinen", "description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen" }, + "shops": { + "layers": { + "0": { + "name": "Winkel", + "title": { + "render": "Winkel" + }, + "description": "Een winkel", + "tagRenderings": { + "1": { + "question": "Wat is de naam van deze winkel?" + }, + "2": { + "mappings": { + "1": { + "then": "Supermarkt" + }, + "3": { + "then": "Kapper" + }, + "4": { + "then": "Bakkerij" + } + } + }, + "3": { + "question": "Wat is het telefoonnummer?" + }, + "4": { + "question": "Wat is de website van deze winkel?" + }, + "6": { + "question": "Wat zijn de openingsuren van deze winkel?" + } + }, + "presets": { + "0": { + "title": "Winkel", + "description": "Voeg een nieuwe winkel toe" + } + } + } + } + }, "speelplekken": { "title": "Welkom bij de groendoener!", "shortDescription": "Speelplekken in de Antwerpse Zuidrand", @@ -1042,61 +1098,5 @@ } } } - }, - "charging_stations": { - "layers": { - "0": { - "description": "Een oplaadpunt", - "title": { - "render": "Oplaadpunt" - }, - "name": "Oplaadpunten" - } - }, - "title": "Oplaadpunten" - }, - "shops": { - "layers": { - "0": { - "tagRenderings": { - "4": { - "question": "Wat is de website van deze winkel?" - }, - "3": { - "question": "Wat is het telefoonnummer?" - }, - "2": { - "mappings": { - "4": { - "then": "Bakkerij" - }, - "3": { - "then": "Kapper" - }, - "1": { - "then": "Supermarkt" - } - } - }, - "1": { - "question": "Wat is de naam van deze winkel?" - }, - "6": { - "question": "Wat zijn de openingsuren van deze winkel?" - } - }, - "description": "Een winkel", - "title": { - "render": "Winkel" - }, - "name": "Winkel", - "presets": { - "0": { - "title": "Winkel", - "description": "Voeg een nieuwe winkel toe" - } - } - } - } } } \ No newline at end of file From ede32273891b66b0c4f12d932f4bdb8505bf7670 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 12 Jul 2021 13:16:08 +0200 Subject: [PATCH 46/64] Exclude a few files from start-script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d09ca2b7ca..6f26034508 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "main": "index.js", "scripts": { "increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096", - "start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", + "start": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory && parcel *.html UI/** Logic/** assets/*.json assets/generated/* assets/layers/*/*.svg assets/tagRendering/*.json assets/themes/*/*.svg assets/themes/*/*.png vendor/* vendor/*/*", "test": "ts-node test/TestAll.ts", "init": "npm ci && npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean", "add-weblate-upstream": "git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layer-translations/ ; git remote update weblate-layers", From c819ae2270723850eb8b097abd86533fe8abf74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= Date: Sun, 11 Jul 2021 11:05:42 +0000 Subject: [PATCH 47/64] Translated using Weblate (German) Currently translated at 27.7% (113 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/de/ --- langs/themes/de.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/langs/themes/de.json b/langs/themes/de.json index 3588564f03..95abb60b17 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -71,7 +71,7 @@ "render": "Erstellt von {artist_name}" }, "3": { - "question": "Auf welcher Website gibt es mehr Informationen über dieses Kunstwerk?", + "question": "Gibt es eine Website mit weiteren Informationen über dieses Kunstwerk?", "render": "Weitere Informationen auf dieser Webseite" }, "4": { @@ -327,5 +327,8 @@ "toilets": { "title": "Offene Toilette Karte", "description": "Eine Karte der öffentlichen Toiletten" + }, + "bicyclelib": { + "title": "Fahrradbibliothek" } -} \ No newline at end of file +} From b9e15697f4a0dc9501663f4b6739c38ee2f8ddbd Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 12 Jul 2021 13:38:38 +0200 Subject: [PATCH 48/64] Robustify morescreen, remove unused var --- UI/BigComponents/MoreScreen.ts | 4 ++++ scripts/generateLayerOverview.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index bfab0567d6..91ab436b28 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -62,6 +62,10 @@ export default class MoreScreen extends Combine { let officialThemes = AllKnownLayouts.layoutsList let buttons = officialThemes.map((layout) => { + if(layout === undefined){ + console.trace("Layout is undefined") + return undefined + } const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass); if(layout.id === personal.id){ return new VariableUiElement( diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 545ebf8444..d903867c84 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -98,7 +98,6 @@ class LayerOverviewUtils { } let themeErrorCount = [] - let missingTranslations = [] for (const themeFile of themeFiles) { if (typeof themeFile.language === "string") { themeErrorCount.push("The theme " + themeFile.id + " has a string as language. Please use a list of strings") From 0c8387fffb2131a3e09e4fc71f33443cd3e57e3c Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Mon, 12 Jul 2021 14:48:22 +0200 Subject: [PATCH 49/64] =?UTF-8?q?=F0=9F=94=A7=20VS=20code=20line=20ending?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..37441beed9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "files.eol": "\n" +} \ No newline at end of file From e7dd70ee5d2f1a626bdcb85b0b552192793e0ea9 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Mon, 12 Jul 2021 14:49:17 +0200 Subject: [PATCH 50/64] =?UTF-8?q?=F0=9F=94=A7=20Basic=20Devcontainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/Dockerfile | 16 ++++++++++++++++ .devcontainer/devcontainer.json | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..fcc5f3644b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,16 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node/.devcontainer/base.Dockerfile + +# [Choice] Node.js version: 16, 14, 12 +ARG VARIANT="16-buster" +FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node packages +# RUN su node -c "npm install -g " diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..f55f8d808c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node +{ + "name": "MapComplete", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 12, 14, 16 + "args": { + "VARIANT": "16" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [1234], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "npm install && npm run init", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node" +} \ No newline at end of file From 4e00ed847a85a96e6d81f8fb55ffe61cf24470cd Mon Sep 17 00:00:00 2001 From: seppesantens Date: Mon, 12 Jul 2021 19:33:31 +0000 Subject: [PATCH 51/64] Translated using Weblate (Dutch) Currently translated at 55.5% (226 of 407 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/nl/ --- langs/themes/nl.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/langs/themes/nl.json b/langs/themes/nl.json index b23136e56b..ca29b7880f 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -263,6 +263,9 @@ "1": { "question": "Wat is de naam van deze plaats?", "render": "Deze plaats heet {name}" + }, + "9": { + "render": "Officiële website: : {website}" } }, "description": "camperplaatsen", From c26783187b411490b80327c82a6fb557648d4b18 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 13 Jul 2021 00:40:27 +0200 Subject: [PATCH 52/64] Attempt to fix #422 --- Logic/Osm/Changes.ts | 18 ++++++++--- Logic/Osm/ChangesetHandler.ts | 59 ++++++++++++++++++++++------------- Logic/Osm/OsmConnection.ts | 5 +-- Models/Constants.ts | 2 +- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 39b9f4e02f..2865041748 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -29,10 +29,11 @@ export class Changes implements FeatureSource { /** * All the pending new objects to upload - * @private */ private readonly newObjects = LocalStorageSource.GetParsed<{ id: number, lat: number, lon: number }[]>("newObjects", []) + private readonly isUploading = new UIEventSource(false); + /** * Adds a change to the pending changes */ @@ -190,6 +191,12 @@ export class Changes implements FeatureSource { console.log("No changes in any object"); return; } + const self = this; + + if (this.isUploading.data) { + return; + } + this.isUploading.setData(true) console.log("Beginning upload..."); // At last, we build the changeset and upload @@ -235,9 +242,12 @@ export class Changes implements FeatureSource { }, () => { console.log("Upload successfull!") - this.newObjects.setData([]) - this.pending.setData([]); - }); + self.newObjects.setData([]) + self.pending.setData([]); + self.isUploading.setData(false) + }, + () => self.isUploading.setData(false) + ); }; diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index 98d45a790c..8fba438037 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -27,7 +27,7 @@ export class ChangesetHandler { } } - private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) : void{ + private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage): void { const nodes = response.getElementsByTagName("node"); // @ts-ignore for (const node of nodes) { @@ -70,7 +70,8 @@ export class ChangesetHandler { layout: LayoutConfig, allElements: ElementStorage, generateChangeXML: (csid: string) => string, - whenDone : (csId: string) => void) { + whenDone: (csId: string) => void, + onFail: () => void) { if (this.userDetails.data.csCount == 0) { // The user became a contributor! @@ -98,8 +99,11 @@ export class ChangesetHandler { whenDone, (e) => { console.error("UPLOADING FAILED!", e) + onFail() } ) + }, { + onFail: onFail }) } else { // There still exists an open changeset (or at least we hope so) @@ -114,8 +118,7 @@ export class ChangesetHandler { // Mark the CS as closed... this.currentChangeset.setData(""); // ... and try again. As the cs is closed, no recursive loop can exist - self.UploadChangeset(layout, allElements, generateChangeXML, whenDone); - + self.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); } ) @@ -161,18 +164,22 @@ export class ChangesetHandler { const self = this; this.OpenChangeset(layout, (csId: string) => { - // The cs is open - let us actually upload! - const changes = generateChangeXML(csId) + // The cs is open - let us actually upload! + const changes = generateChangeXML(csId) - self.AddChange(csId, changes, allElements, (csId) => { - console.log("Successfully deleted ", object.id) - self.CloseChangeset(csId, continuation) - }, (csId) => { - alert("Deletion failed... Should not happend") - // FAILED - self.CloseChangeset(csId, continuation) - }) - }, true, reason) + self.AddChange(csId, changes, allElements, (csId) => { + console.log("Successfully deleted ", object.id) + self.CloseChangeset(csId, continuation) + }, (csId) => { + alert("Deletion failed... Should not happend") + // FAILED + self.CloseChangeset(csId, continuation) + }) + }, { + isDeletionCS: true, + deletionReason: reason + } + ) } private CloseChangeset(changesetId: string = undefined, continuation: (() => void) = () => { @@ -204,15 +211,20 @@ export class ChangesetHandler { private OpenChangeset( layout: LayoutConfig, continuation: (changesetId: string) => void, - isDeletionCS: boolean = false, - deletionReason: string = undefined) { - + options?: { + isDeletionCS?: boolean, + deletionReason?: string, + onFail?: () => void + } + ) { + options = options ?? {} + options.isDeletionCS = options.isDeletionCS ?? false const commentExtra = layout.changesetmessage !== undefined ? " - " + layout.changesetmessage : ""; let comment = `Adding data with #MapComplete for theme #${layout.id}${commentExtra}` - if (isDeletionCS) { + if (options.isDeletionCS) { comment = `Deleting a point with #MapComplete for theme #${layout.id}${commentExtra}` - if (deletionReason) { - comment += ": " + deletionReason; + if (options.deletionReason) { + comment += ": " + options.deletionReason; } } @@ -221,7 +233,7 @@ export class ChangesetHandler { const metadata = [ ["created_by", `MapComplete ${Constants.vNumber}`], ["comment", comment], - ["deletion", isDeletionCS ? "yes" : undefined], + ["deletion", options.isDeletionCS ? "yes" : undefined], ["theme", layout.id], ["language", Locale.language.data], ["host", window.location.host], @@ -244,6 +256,9 @@ export class ChangesetHandler { }, function (err, response) { if (response === undefined) { console.log("err", err); + if(options.onFail){ + options.onFail() + } return; } else { continuation(response); diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index cba332447f..37c8fa1d2e 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -111,8 +111,9 @@ export class OsmConnection { layout: LayoutConfig, allElements: ElementStorage, generateChangeXML: (csid: string) => string, - whenDone: (csId: string) => void) { - this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone); + whenDone: (csId: string) => void, + onFail: () => {}) { + this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); } public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { diff --git a/Models/Constants.ts b/Models/Constants.ts index 48c52eebfd..e54e0ea412 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.4-rc1"; + public static vNumber = "0.8.4-rc2"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { From 8ddca45d242decde87421e2ca7032dc379f14006 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Tue, 13 Jul 2021 13:57:31 +0200 Subject: [PATCH 53/64] Add documentation about deployment in Windows --- Docs/Development_deployment.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Docs/Development_deployment.md b/Docs/Development_deployment.md index ffa69eab3f..732336b571 100644 --- a/Docs/Development_deployment.md +++ b/Docs/Development_deployment.md @@ -18,9 +18,9 @@ Development ----------- - **Windows users**: All scripts are made for linux devices. Use the Ubuntu terminal for Windows (or even better - make the switch ;) ). If you are using Visual Studio, open everything in a 'new WSL Window'. + **Windows users**: All scripts are made for linux devices. Use the Ubuntu terminal for Windows (or even better - make the switch ;) ). If you are using Visual Studio Code you can use a [WSL Remote](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) window, or use the Devcontainer (see more details later). - To develop and build MapComplete, yo + To develop and build MapComplete, you 0. Make sure you have a recent version of nodejs - at least 12.0, preferably 15 0. Make a fork and clone the repository. @@ -29,6 +29,30 @@ 4. Run `npm run start` to host a local testversion at http://localhost:1234/index.html 5. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. + Development using Windows + ------------------------ + + For Windows you can use the devcontainer, or the WSL subsystem. + + To use the devcontainer in Visual Studio Code: + +0. Make sure you have installed the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension and it's dependencies. +1. Make a fork and clone the repository. +2. After cloning, Visual Studio Code will ask you if you want to use the devcontainer. +3. Then you can either clone it again in a volume (for better performance), or open the current folder in a container. +4. By now, you should be able to run `npm run start` to host a local testversion at http://localhost:1234/index.html +5. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. + + To use the WSL in Visual Studio Code: + +0. Make sure you have installed the [Remote - WSL]() extension and it's dependencies. +1. Open a remote WSL window using the button in the bottom left. +2. Make a fork and clone the repository. +3. Install `npm` using `sudo apt install npm`. +4. Run `npm run init` and generate some additional dependencies and generated files. Note that it'll install the dependencies too +5. Run `npm run start` to host a local testversion at http://localhost:1234/index.html +6. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` or `userlayout=true#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. + Automatic deployment -------------------- From 9ff02d2dba61ee15d465660dd94d02e04038d7da Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Tue, 13 Jul 2021 14:00:11 +0200 Subject: [PATCH 54/64] Remove npm run install (not needed) --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f55f8d808c..2ea9a5be8b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,7 +22,7 @@ "forwardPorts": [1234], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm install && npm run init", + "postCreateCommand": "npm run init", // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node" From 0ff6ac4af9d9b7540101f2a7c00fef0291f5239e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 13 Jul 2021 14:31:03 +0200 Subject: [PATCH 55/64] Clear pending changes if they are already applied --- Logic/Osm/Changes.ts | 4 +++- Models/Constants.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 2865041748..ff50cc022b 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -188,7 +188,9 @@ export class Changes implements FeatureSource { } } if (changedElements.length == 0 && newElements.length == 0) { - console.log("No changes in any object"); + console.log("No changes in any object - clearing"); + this.pending.setData([]) + this.newObjects.setData([]) return; } const self = this; diff --git a/Models/Constants.ts b/Models/Constants.ts index e54e0ea412..75423405e4 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.4-rc2"; + public static vNumber = "0.8.4-rc3"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { From ca1871262e3c680cc0538f7a9c4fac48eb51fa4d Mon Sep 17 00:00:00 2001 From: Ward Date: Tue, 13 Jul 2021 14:39:50 +0200 Subject: [PATCH 56/64] working location button --- Logic/Actors/GeoLocationHandler.ts | 433 +++++++++++++++-------------- 1 file changed, 222 insertions(+), 211 deletions(-) diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index e86c1baa7c..18a62220de 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -1,244 +1,255 @@ import * as L from "leaflet"; -import {UIEventSource} from "../UIEventSource"; -import {Utils} from "../../Utils"; +import { UIEventSource } from "../UIEventSource"; +import { Utils } from "../../Utils"; import Svg from "../../Svg"; import Img from "../../UI/Base/Img"; -import {LocalStorageSource} from "../Web/LocalStorageSource"; +import { LocalStorageSource } from "../Web/LocalStorageSource"; import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; -import {VariableUiElement} from "../../UI/Base/VariableUIElement"; +import { VariableUiElement } from "../../UI/Base/VariableUIElement"; export default class GeoLocationHandler extends VariableUiElement { + /** + * Wether or not the geolocation is active, aka the user requested the current location + * @private + */ + private readonly _isActive: UIEventSource; - /** - * Wether or not the geolocation is active, aka the user requested the current location - * @private - */ - private readonly _isActive: UIEventSource; + /** + * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user + * @private + */ + private readonly _isLocked: UIEventSource; - /** - * The callback over the permission API - * @private - */ - private readonly _permission: UIEventSource; - /*** - * The marker on the map, in order to update it - * @private - */ - private _marker: L.Marker; - /** - * Literally: _currentGPSLocation.data != undefined - * @private - */ - private readonly _hasLocation: UIEventSource; - private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>; - /** - * Kept in order to update the marker - * @private - */ - private readonly _leafletMap: UIEventSource; - /** - * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs - * @private - */ - private _lastUserRequest: Date; - /** - * A small flag on localstorage. If the user previously granted the geolocation, it will be set. - * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. - * - * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. - * If the user denies the geolocation this time, we unset this flag - * @private - */ - private readonly _previousLocationGrant: UIEventSource; - private readonly _layoutToUse: UIEventSource; + /** + * The callback over the permission API + * @private + */ + private readonly _permission: UIEventSource; + /*** + * The marker on the map, in order to update it + * @private + */ + private _marker: L.Marker; + /** + * Literally: _currentGPSLocation.data != undefined + * @private + */ + private readonly _hasLocation: UIEventSource; + private readonly _currentGPSLocation: UIEventSource<{ + latlng: any; + accuracy: number; + }>; + /** + * Kept in order to update the marker + * @private + */ + private readonly _leafletMap: UIEventSource; + /** + * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs + * @private + */ + private _lastUserRequest: Date; + /** + * A small flag on localstorage. If the user previously granted the geolocation, it will be set. + * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. + * + * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. + * If the user denies the geolocation this time, we unset this flag + * @private + */ + private readonly _previousLocationGrant: UIEventSource; + private readonly _layoutToUse: UIEventSource; + constructor( + currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, + leafletMap: UIEventSource, + layoutToUse: UIEventSource + ) { + const hasLocation = currentGPSLocation.map( + (location) => location !== undefined + ); + const previousLocationGrant = LocalStorageSource.Get( + "geolocation-permissions" + ); + const isActive = new UIEventSource(false); - constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, - leafletMap: UIEventSource, - layoutToUse: UIEventSource) { + super( + hasLocation.map( + (hasLocation) => { + if (hasLocation) { + return Svg.crosshair_blue_ui(); + } + if (isActive.data) { + return Svg.crosshair_blue_center_ui(); + } + return Svg.crosshair_ui(); + }, + [isActive] + ) + ); + this._isActive = isActive; + this._isLocked = new UIEventSource(false); + this._permission = new UIEventSource(""); + this._previousLocationGrant = previousLocationGrant; + this._currentGPSLocation = currentGPSLocation; + this._leafletMap = leafletMap; + this._layoutToUse = layoutToUse; + this._hasLocation = hasLocation; + const self = this; - const hasLocation = currentGPSLocation.map((location) => location !== undefined); - const previousLocationGrant = LocalStorageSource.Get("geolocation-permissions") - const isActive = new UIEventSource(false); + const currentPointer = this._isActive.map( + (isActive) => { + if (isActive && !self._hasLocation.data) { + return "cursor-wait"; + } + return "cursor-pointer"; + }, + [this._hasLocation] + ); + currentPointer.addCallbackAndRun((pointerClass) => { + self.SetClass(pointerClass); + }); - super( - hasLocation.map(hasLocation => { + this.onClick(() => self.init(true)); + this.init(false); - if (hasLocation) { - return Svg.crosshair_blue_ui() - } - if (isActive.data) { - return Svg.crosshair_blue_center_ui(); - } - return Svg.crosshair_ui(); - }, [isActive]) + this._currentGPSLocation.addCallback((location) => { + self._previousLocationGrant.setData("granted"); + + const timeSinceRequest = + (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; + if (timeSinceRequest < 30) { + self.MoveToCurrentLoction(16); + } else if (self._isLocked.data) { + self.MoveToCurrentLoction(); + } + + let color = "#1111cc"; + try { + color = getComputedStyle(document.body).getPropertyValue( + "--catch-detail-color" ); - this._isActive = isActive; - this._permission = new UIEventSource("") - this._previousLocationGrant = previousLocationGrant; - this._currentGPSLocation = currentGPSLocation; - this._leafletMap = leafletMap; - this._layoutToUse = layoutToUse; - this._hasLocation = hasLocation; - const self = this; + } catch (e) { + console.error(e); + } + const icon = L.icon({ + iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), + iconSize: [40, 40], // size of the icon + iconAnchor: [20, 20], // point of the icon which will correspond to marker's location + }); - const currentPointer = this._isActive.map(isActive => { - if (isActive && !self._hasLocation.data) { - return "cursor-wait" - } - return "cursor-pointer" - }, [this._hasLocation]) - currentPointer.addCallbackAndRun(pointerClass => { - self.SetClass(pointerClass); - }) + const map = self._leafletMap.data; + console.log("check for map", map); + const newMarker = L.marker(location.latlng, { icon: icon }); + newMarker.addTo(map); - this.onClick(() => self.init(true)) - this.init(false) + if (self._marker !== undefined) { + map.removeLayer(self._marker); + } + self._marker = newMarker; + }); + } + private init(askPermission: boolean) { + const self = this; + + if (self._isActive.data) { + self.MoveToCurrentLoction(16); + return; } - private init(askPermission: boolean) { - - const self = this; - const map = this._leafletMap.data; - - this._currentGPSLocation.addCallback((location) => { - self._previousLocationGrant.setData("granted"); - - const timeSinceRequest = (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; - if (timeSinceRequest < 30) { - self.MoveToCurrentLoction(16) - } - - let color = "#1111cc"; - try { - color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") - } catch (e) { - console.error(e) - } - const icon = L.icon( - { - iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), - iconSize: [40, 40], // size of the icon - iconAnchor: [20, 20], // point of the icon which will correspond to marker's location - }) - - const newMarker = L.marker(location.latlng, {icon: icon}); - newMarker.addTo(map); - - if (self._marker !== undefined) { - map.removeLayer(self._marker); - } - self._marker = newMarker; - }); - - try { - - navigator?.permissions?.query({name: 'geolocation'}) - ?.then(function (status) { - console.log("Geolocation is already", status) - if (status.state === "granted") { - self.StartGeolocating(false); - } - self._permission.setData(status.state); - status.onchange = function () { - self._permission.setData(status.state); - } - }); - - } catch (e) { - console.error(e) - } - if (askPermission) { - self.StartGeolocating(true); - } else if (this._previousLocationGrant.data === "granted") { - this._previousLocationGrant.setData(""); + try { + navigator?.permissions + ?.query({ name: "geolocation" }) + ?.then(function (status) { + console.log("Geolocation is already", status); + if (status.state === "granted") { self.StartGeolocating(false); - } - + } + self._permission.setData(status.state); + status.onchange = function () { + self._permission.setData(status.state); + }; + }); + } catch (e) { + console.error(e); } - private locate() { - const self = this; - const map: any = this._leafletMap.data; + if (askPermission) { + self.StartGeolocating(true); + } else if (this._previousLocationGrant.data === "granted") { + this._previousLocationGrant.setData(""); + self.StartGeolocating(false); + } + } - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition(function (position) { - self._currentGPSLocation.setData({ - latlng: [position.coords.latitude, position.coords.longitude], - accuracy: position.coords.accuracy - }); - }, function () { - console.warn("Could not get location with navigator.geolocation") - }); - return; - } else { - map.findAccuratePosition({ - maxWait: 10000, // defaults to 10000 - desiredAccuracy: 50 // defaults to 20 - }); - } + private MoveToCurrentLoction(targetZoom?: number) { + const location = this._currentGPSLocation.data; + this._lastUserRequest = undefined; + + if ( + this._currentGPSLocation.data.latlng[0] === 0 && + this._currentGPSLocation.data.latlng[1] === 0 + ) { + console.debug("Not moving to GPS-location: it is null island"); + return; } - private MoveToCurrentLoction(targetZoom = 16) { - const location = this._currentGPSLocation.data; - this._lastUserRequest = undefined; + // We check that the GPS location is not out of bounds + const b = this._layoutToUse.data.lockLocation; + let inRange = true; + if (b) { + if (b !== true) { + // B is an array with our locklocation + inRange = + b[0][0] <= location.latlng[0] && + location.latlng[0] <= b[1][0] && + b[0][1] <= location.latlng[1] && + location.latlng[1] <= b[1][1]; + } + } + if (!inRange) { + console.log( + "Not zooming to GPS location: out of bounds", + b, + location.latlng + ); + } else { + this._leafletMap.data.setView(location.latlng, targetZoom); + } + } + private StartGeolocating(zoomToGPS = true) { + const self = this; + console.log("Starting geolocation"); - if (this._currentGPSLocation.data.latlng[0] === 0 && this._currentGPSLocation.data.latlng[1] === 0) { - console.debug("Not moving to GPS-location: it is null island") - return; - } - - // We check that the GPS location is not out of bounds - const b = this._layoutToUse.data.lockLocation - let inRange = true; - if (b) { - if (b !== true) { - // B is an array with our locklocation - inRange = b[0][0] <= location.latlng[0] && location.latlng[0] <= b[1][0] && - b[0][1] <= location.latlng[1] && location.latlng[1] <= b[1][1]; - } - } - if (!inRange) { - console.log("Not zooming to GPS location: out of bounds", b, location.latlng) - } else { - this._leafletMap.data.setView( - location.latlng, targetZoom - ); - } + this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); + if (self._permission.data === "denied") { + self._previousLocationGrant.setData(""); + return ""; + } + if (this._currentGPSLocation.data !== undefined) { + this.MoveToCurrentLoction(16); } - private StartGeolocating(zoomToGPS = true) { - const self = this; - console.log("Starting geolocation") + console.log("Searching location using GPS"); - this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); - if (self._permission.data === "denied") { - self._previousLocationGrant.setData(""); - return ""; - } - if (this._currentGPSLocation.data !== undefined) { - this.MoveToCurrentLoction(16) - } - - - console.log("Searching location using GPS") - this.locate(); - - - if (!self._isActive.data) { - self._isActive.setData(true); - Utils.DoEvery(60000, () => { - - if (document.visibilityState !== "visible") { - console.log("Not starting gps: document not visible") - return; - } - this.locate(); - }) - } + if (self._isActive.data) { + return; } + self._isActive.setData(true); -} \ No newline at end of file + navigator.geolocation.watchPosition( + function (position) { + self._currentGPSLocation.setData({ + latlng: [position.coords.latitude, position.coords.longitude], + accuracy: position.coords.accuracy, + }); + }, + function () { + console.warn("Could not get location with navigator.geolocation"); + } + ); + } +} From 4c4b0356c18ae5859aef7eeb33a04a216d547a9f Mon Sep 17 00:00:00 2001 From: Ward Date: Tue, 13 Jul 2021 14:57:51 +0200 Subject: [PATCH 57/64] location tracking centering screen --- Logic/Actors/GeoLocationHandler.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 18a62220de..f8e38f50c4 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -72,23 +72,26 @@ export default class GeoLocationHandler extends VariableUiElement { "geolocation-permissions" ); const isActive = new UIEventSource(false); + const isLocked = new UIEventSource(false); super( hasLocation.map( - (hasLocation) => { - if (hasLocation) { + (hasLocationData) => { + if (isLocked.data) { + return Svg.up_ui(); + } else if (hasLocationData) { return Svg.crosshair_blue_ui(); - } - if (isActive.data) { + } else if (isActive.data) { return Svg.crosshair_blue_center_ui(); + } else { + return Svg.crosshair_ui(); } - return Svg.crosshair_ui(); }, - [isActive] + [isActive, isLocked] ) ); this._isActive = isActive; - this._isLocked = new UIEventSource(false); + this._isLocked = isLocked; this._permission = new UIEventSource(""); this._previousLocationGrant = previousLocationGrant; this._currentGPSLocation = currentGPSLocation; @@ -110,7 +113,12 @@ export default class GeoLocationHandler extends VariableUiElement { self.SetClass(pointerClass); }); - this.onClick(() => self.init(true)); + this.onClick(() => { + self.init(true); + if (self._isActive.data) { + self._isLocked.setData(!self._isLocked.data); + } + }); this.init(false); this._currentGPSLocation.addCallback((location) => { @@ -139,7 +147,6 @@ export default class GeoLocationHandler extends VariableUiElement { }); const map = self._leafletMap.data; - console.log("check for map", map); const newMarker = L.marker(location.latlng, { icon: icon }); newMarker.addTo(map); From 54a084d81c653d5a27d7a480a75c330c8e8557ec Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 13 Jul 2021 18:34:20 +0200 Subject: [PATCH 58/64] Add units to height of windmills --- .../openwindpowermap/openwindpowermap.json | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 2330b885ec..0f938fbd4b 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -62,7 +62,8 @@ "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" }, "freeform": { - "key": "generator:output:electricity" + "key": "generator:output:electricity", + "type": "pfloat" } }, { @@ -85,7 +86,7 @@ }, "freeform": { "key": "height", - "type": "float" + "type": "pfloat" } }, { @@ -128,6 +129,23 @@ } ], "units": [ + { + "appliesToKey": [ + "height","rotor:diameter" + ], + "applicableUnits": [ + { + "canonicalDenomination": "m", + "alternativeDenomination": [ + "meter" + ], + "human": { + "en": " meter", + "nl": " meter" + } + } + ] + }, { "appliesToKey": [ "generator:output:electricity" From f5cc180eeaf90dd5b1e3b7c6149af03d25a86a7c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 13 Jul 2021 18:36:31 +0200 Subject: [PATCH 59/64] Add new units to the bottom as not to break translations --- .../openwindpowermap/openwindpowermap.json | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 0f938fbd4b..28201b9e58 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -129,24 +129,7 @@ } ], "units": [ - { - "appliesToKey": [ - "height","rotor:diameter" - ], - "applicableUnits": [ - { - "canonicalDenomination": "m", - "alternativeDenomination": [ - "meter" - ], - "human": { - "en": " meter", - "nl": " meter" - } - } - ] - }, - { + { "appliesToKey": [ "generator:output:electricity" ], @@ -197,6 +180,23 @@ } ], "eraseInvalidValues": true + }, + { + "appliesToKey": [ + "height","rotor:diameter" + ], + "applicableUnits": [ + { + "canonicalDenomination": "m", + "alternativeDenomination": [ + "meter" + ], + "human": { + "en": " meter", + "nl": " meter" + } + } + ] } ], "defaultBackgroundId": "CartoDB.Voyager" From 42d0071b26b3b619cf6561a5f1003b61176b3f6b Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 13 Jul 2021 18:52:02 +0200 Subject: [PATCH 60/64] Use canonical rendering in windpowermap --- Customizations/JSON/Denomination.ts | 6 +++++- Customizations/JSON/LayoutConfigJson.ts | 4 ++++ Logic/SimpleMetaTagger.ts | 5 +++++ UI/SpecialVisualizations.ts | 1 - assets/themes/openwindpowermap/openwindpowermap.json | 6 +++--- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Customizations/JSON/Denomination.ts b/Customizations/JSON/Denomination.ts index 8331d8adc5..2b9779f944 100644 --- a/Customizations/JSON/Denomination.ts +++ b/Customizations/JSON/Denomination.ts @@ -3,6 +3,7 @@ import UnitConfigJson from "./UnitConfigJson"; import Translations from "../../UI/i18n/Translations"; import BaseUIElement from "../../UI/BaseUIElement"; import Combine from "../../UI/Base/Combine"; +import {FixedUiElement} from "../../UI/Base/FixedUiElement"; export class Unit { public readonly appliesToKeys: Set; @@ -81,7 +82,10 @@ export class Unit { return undefined; } const [stripped, denom] = this.findDenomination(value) - const human = denom.human + const human = denom?.human + if(human === undefined){ + return new FixedUiElement(stripped ?? value); + } const elems = denom.prefix ? [human, stripped] : [stripped, human]; return new Combine(elems) diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Customizations/JSON/LayoutConfigJson.ts index 8ced24bd7e..374de70e07 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Customizations/JSON/LayoutConfigJson.ts @@ -225,6 +225,10 @@ export interface LayoutConfigJson { * * Not only do we want to write consistent data to OSM, we also want to present this consistently to the user. * This is handled by defining units. + * + * # Rendering + * + * To render a value with long (human) denomination, use {canonical(key)} * * # Usage * diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 4453c2b4fe..a704e22592 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -84,6 +84,7 @@ export default class SimpleMetaTagger { }, (feature => { const units = State.state.layoutToUse.data.units ?? []; + let rewritten = false; for (const key in feature.properties) { if (!feature.properties.hasOwnProperty(key)) { continue; @@ -104,10 +105,14 @@ export default class SimpleMetaTagger { } feature.properties[key] = canonical; + rewritten = true; break; } } + if(rewritten){ + State.state.allElements.getEventSourceById(feature.id).ping(); + } }) ) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 2bbcbbb344..309060b364 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -369,7 +369,6 @@ export default class SpecialVisualizations { if (unit === undefined) { return value; } - return unit.asHumanLongValue(value); }, diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 28201b9e58..b1e4ecaa7c 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -56,7 +56,7 @@ "tagRenderings": [ { "render": { - "en": "The power output of this wind turbine is {generator:output:electricity}." + "en": "The power output of this wind turbine is {canonical(generator:output:electricity)}." }, "question": { "en": "What is the power output of this wind turbine? (e.g. 2.3 MW)" @@ -79,7 +79,7 @@ }, { "render": { - "en": "The total height (including rotor radius) of this wind turbine is {height} metres." + "en": "The total height (including rotor radius) of this wind turbine is {canonical(height)}." }, "question": { "en": "What is the total height of this wind turbine (including rotor radius), in metres?" @@ -91,7 +91,7 @@ }, { "render": { - "en": "The rotor diameter of this wind turbine is {rotor:diameter} metres." + "en": "The rotor diameter of this wind turbine is {canonical(rotor:diameter)}." }, "question": { "en": "What is the rotor diameter of this wind turbine, in metres?" From 315e2f7fd1f91ba7520e001b8e8daf70297469a2 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 14 Jul 2021 00:17:15 +0200 Subject: [PATCH 61/64] Add precise input method, enabled for public bookcases --- Customizations/JSON/LayerConfig.ts | 16 ++- Customizations/JSON/LayerConfigJson.ts | 10 ++ Logic/Actors/AvailableBaseLayers.ts | 21 ++-- Models/BaseLayer.ts | 2 + Svg.ts | 7 +- UI/BigComponents/SimpleAddUI.ts | 95 +++++++++------ UI/Input/LocationInput.ts | 112 ++++++++++++++++++ .../public_bookcase/public_bookcase.json | 5 +- assets/svg/crosshair-empty.svg | 83 +++++++++++++ test.ts | 31 +++-- 10 files changed, 318 insertions(+), 64 deletions(-) create mode 100644 UI/Input/LocationInput.ts create mode 100644 assets/svg/crosshair-empty.svg diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 0acf8198f1..48e96f4a21 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -54,6 +54,7 @@ export default class LayerConfig { title: Translation, tags: Tag[], description?: Translation, + preciseInput?: {preferredBackground?: string} }[]; tagRenderings: TagRenderingConfig []; @@ -130,12 +131,19 @@ export default class LayerConfig { this.minzoom = json.minzoom ?? 0; this.maxzoom = json.maxzoom ?? 1000; this.wayHandling = json.wayHandling ?? 0; - this.presets = (json.presets ?? []).map((pr, i) => - ({ + this.presets = (json.presets ?? []).map((pr, i) => { + if(pr.preciseInput === true){ + pr.preciseInput = { + preferredBackground: undefined + } + } + return ({ title: Translations.T(pr.title, `${context}.presets[${i}].title`), tags: pr.tags.map(t => FromJSON.SimpleTag(t)), - description: Translations.T(pr.description, `${context}.presets[${i}].description`) - })) + description: Translations.T(pr.description, `${context}.presets[${i}].description`), + preciseInput: pr.preciseInput + }); + }) /** Given a key, gets the corresponding property from the json (or the default if not found diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index d81307fd9f..6739e33fdc 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -217,6 +217,16 @@ export interface LayerConfigJson { * (The first sentence is until the first '.'-character in the description) */ description?: string | any, + + /** + * If set, the user will prompted to confirm the location before actually adding the data. + * THis will be with a 'drag crosshair'-method. + * + * If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category. + */ + preciseInput?: true | { + preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" + } }[], /** diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts index 2fd679571d..52b1c12de1 100644 --- a/Logic/Actors/AvailableBaseLayers.ts +++ b/Logic/Actors/AvailableBaseLayers.ts @@ -1,11 +1,13 @@ import * as editorlayerindex from "../../assets/editor-layer-index.json" import BaseLayer from "../../Models/BaseLayer"; import * as L from "leaflet"; +import {TileLayer} from "leaflet"; import * as X from "leaflet-providers"; import {UIEventSource} from "../UIEventSource"; import {GeoOperations} from "../GeoOperations"; -import {TileLayer} from "leaflet"; import {Utils} from "../../Utils"; +import Loc from "../../Models/Loc"; +import {isBoolean} from "util"; /** * Calculates which layers are available at the current location @@ -24,14 +26,16 @@ export default class AvailableBaseLayers { false, false), feature: null, max_zoom: 19, - min_zoom: 0 + min_zoom: 0, + isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context) + category: "osmbasedmap" } public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex()); public availableEditorLayers: UIEventSource; - constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>) { + constructor(location: UIEventSource) { const self = this; this.availableEditorLayers = location.map( @@ -140,7 +144,9 @@ export default class AvailableBaseLayers { min_zoom: props.min_zoom ?? 1, name: props.name, layer: leafletLayer, - feature: layer + feature: layer, + isBest: props.best ?? false, + category: props.category }); } return layers; @@ -152,15 +158,16 @@ export default class AvailableBaseLayers { function l(id: string, name: string): BaseLayer { try { const layer: any = () => L.tileLayer.provider(id, undefined); - const baseLayer: BaseLayer = { + return { feature: null, id: id, name: name, layer: layer, min_zoom: layer.minzoom, - max_zoom: layer.maxzoom + max_zoom: layer.maxzoom, + category: "osmbasedmap", + isBest: false } - return baseLayer } catch (e) { console.error("Could not find provided layer", name, e); return null; diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts index 01eb8e9d75..84556fc690 100644 --- a/Models/BaseLayer.ts +++ b/Models/BaseLayer.ts @@ -7,4 +7,6 @@ export default interface BaseLayer { max_zoom: number, min_zoom: number; feature: any, + isBest?: boolean, + category?: "map" | "osmbasedmap" | "photo" | "historicphoto" | string } \ No newline at end of file diff --git a/Svg.ts b/Svg.ts index 9a5c94b8f2..89baa7becd 100644 --- a/Svg.ts +++ b/Svg.ts @@ -89,6 +89,11 @@ export default class Svg { public static crosshair_blue_svg() { return new Img(Svg.crosshair_blue, true);} public static crosshair_blue_ui() { return new FixedUiElement(Svg.crosshair_blue_img);} + public static crosshair_empty = " image/svg+xml " + public static crosshair_empty_img = Img.AsImageElement(Svg.crosshair_empty) + public static crosshair_empty_svg() { return new Img(Svg.crosshair_empty, true);} + public static crosshair_empty_ui() { return new FixedUiElement(Svg.crosshair_empty_img);} + public static crosshair = " image/svg+xml " public static crosshair_img = Img.AsImageElement(Svg.crosshair) public static crosshair_svg() { return new Img(Svg.crosshair, true);} @@ -334,4 +339,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair-empty.svg": Svg.crosshair_empty,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 75dd3e4037..05fb52c64c 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -16,6 +16,9 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import Toggle from "../Input/Toggle"; import UserDetails from "../../Logic/Osm/OsmConnection"; import {Translation} from "../i18n/Translation"; +import LocationInput from "../Input/LocationInput"; +import {InputElement} from "../Input/InputElement"; +import Loc from "../../Models/Loc"; /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -25,14 +28,18 @@ import {Translation} from "../i18n/Translation"; * - A 'read your unread messages before adding a point' */ +/*private*/ interface PresetInfo { description: string | Translation, name: string | BaseUIElement, - icon: BaseUIElement, + icon: () => BaseUIElement, tags: Tag[], layerToAddTo: { layerDef: LayerConfig, isDisplayed: UIEventSource + }, + preciseInput?: { + preferredBackground?: string } } @@ -48,18 +55,16 @@ export default class SimpleAddUI extends Toggle { new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false}) ]); - - - + + const selectedPreset = new UIEventSource(undefined); isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened - - function createNewPoint(tags: any[]){ - const loc = State.state.LastClickLocation.data; - let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); + + function createNewPoint(tags: any[], location: { lat: number, lon: number }) { + let feature = State.state.changes.createElement(tags, location.lat, location.lon); State.state.selectedElement.setData(feature); } - + const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) const addUi = new VariableUiElement( @@ -68,8 +73,8 @@ export default class SimpleAddUI extends Toggle { return presetsOverview } return SimpleAddUI.CreateConfirmButton(preset, - tags => { - createNewPoint(tags) + (tags, location) => { + createNewPoint(tags, location) selectedPreset.setData(undefined) }, () => { selectedPreset.setData(undefined) @@ -86,7 +91,7 @@ export default class SimpleAddUI extends Toggle { addUi, State.state.layerUpdater.runningQuery ), - Translations.t.general.add.zoomInFurther.Clone().SetClass("alert") , + Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) ), readYourMessages, @@ -103,22 +108,41 @@ export default class SimpleAddUI extends Toggle { } - private static CreateConfirmButton(preset: PresetInfo, - confirm: (tags: any[]) => void, + confirm: (tags: any[], location: { lat: number, lon: number }) => void, cancel: () => void): BaseUIElement { + let location = State.state.LastClickLocation; + let preciseInput: InputElement = undefined + if (preset.preciseInput !== undefined) { + preciseInput = new LocationInput({ + preferCategory: preset.preciseInput.preferredBackground ?? State.state.backgroundLayer, + centerLocation: + new UIEventSource({ + lat: location.data.lat, + lon: location.data.lon, + zoom: 19 + }) + }) + preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") + } - const confirmButton = new SubtleButton(preset.icon, + + let confirmButton: BaseUIElement = new SubtleButton(preset.icon(), new Combine([ Translations.t.general.add.addNew.Subs({category: preset.name}), Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert") ]).SetClass("flex flex-col") ).SetClass("font-bold break-words") - .onClick(() => confirm(preset.tags)); + .onClick(() => { + confirm(preset.tags, (preciseInput?.GetValue() ?? location).data); + }); + + if (preciseInput !== undefined) { + confirmButton = new Combine([preciseInput, confirmButton]) + } - - const openLayerControl = + const openLayerControl = new SubtleButton( Svg.layers_ui(), new Combine([ @@ -128,9 +152,9 @@ export default class SimpleAddUI extends Toggle { Translations.t.general.add.openLayerControl ]) ) - - .onClick(() => State.state.layerControlIsOpened.setData(true)) - + + .onClick(() => State.state.layerControlIsOpened.setData(true)) + const openLayerOrConfirm = new Toggle( confirmButton, openLayerControl, @@ -140,12 +164,12 @@ export default class SimpleAddUI extends Toggle { const cancelButton = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel - ).onClick(cancel ) + ).onClick(cancel) return new Combine([ Translations.t.general.add.confirmIntro.Subs({title: preset.name}), - State.state.osmConnection.userDetails.data.dryRun ? - Translations.t.general.testing.Clone().SetClass("alert") : undefined , + State.state.osmConnection.userDetails.data.dryRun ? + Translations.t.general.testing.Clone().SetClass("alert") : undefined, openLayerOrConfirm, cancelButton, preset.description, @@ -180,11 +204,11 @@ export default class SimpleAddUI extends Toggle { } - private static CreatePresetSelectButton(preset: PresetInfo){ + private static CreatePresetSelectButton(preset: PresetInfo) { - const tagInfo =SimpleAddUI.CreateTagInfoFor(preset, false); + const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, false); return new SubtleButton( - preset.icon, + preset.icon(), new Combine([ Translations.t.general.add.addNew.Subs({ category: preset.name @@ -194,29 +218,30 @@ export default class SimpleAddUI extends Toggle { ]).SetClass("flex flex-col") ) } - -/* -* Generates the list with all the buttons.*/ + + /* + * Generates the list with all the buttons.*/ private static CreatePresetButtons(selectedPreset: UIEventSource): BaseUIElement { const allButtons = []; for (const layer of State.state.filteredLayers.data) { - - if(layer.isDisplayed.data === false && State.state.featureSwitchLayers){ + + if (layer.isDisplayed.data === false && State.state.featureSwitchLayers) { continue; } - + const presets = layer.layerDef.presets; for (const preset of presets) { const tags = TagUtils.KVtoProperties(preset.tags ?? []); - let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html + let icon:() => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html .SetClass("w-12 h-12 block relative"); const presetInfo: PresetInfo = { tags: preset.tags, layerToAddTo: layer, name: preset.title, description: preset.description, - icon: icon + icon: icon, + preciseInput: preset.preciseInput } const button = SimpleAddUI.CreatePresetSelectButton(presetInfo); diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts new file mode 100644 index 0000000000..cee2f5cbfb --- /dev/null +++ b/UI/Input/LocationInput.ts @@ -0,0 +1,112 @@ +import {InputElement} from "./InputElement"; +import Loc from "../../Models/Loc"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Minimap from "../Base/Minimap"; +import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; +import BaseLayer from "../../Models/BaseLayer"; +import Combine from "../Base/Combine"; +import Svg from "../../Svg"; + +export default class LocationInput extends InputElement { + + IsSelected: UIEventSource = new UIEventSource(false); + private _centerLocation: UIEventSource; + private readonly preferCategory; + + constructor(options?: { + centerLocation?: UIEventSource, + preferCategory?: string | UIEventSource, + }) { + super(); + options = options ?? {} + options.centerLocation = options.centerLocation ?? new UIEventSource({lat: 0, lon: 0, zoom: 1}) + this._centerLocation = options.centerLocation; + + if(typeof options.preferCategory === "string"){ + options.preferCategory = new UIEventSource(options.preferCategory); + } + this.preferCategory = options.preferCategory ?? new UIEventSource(undefined) + this.SetClass("block h-full") + } + + GetValue(): UIEventSource { + return this._centerLocation; + } + + IsValid(t: Loc): boolean { + return t !== undefined; + } + + protected InnerConstructElement(): HTMLElement { + const layer: UIEventSource = new AvailableBaseLayers(this._centerLocation).availableEditorLayers.map(allLayers => { + // First float all 'best layers' to the top + allLayers.sort((a, b) => { + if (a.isBest && b.isBest) { + return 0; + } + if (!a.isBest) { + return 1 + } + + return -1; + } + ) + if (this.preferCategory) { + const self = this; + //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top + allLayers.sort((a, b) => { + const preferred = self.preferCategory.data + if (a.category === preferred && b.category === preferred) { + return 0; + } + if (a.category !== preferred) { + return 1 + } + + return -1; + } + ) + } + return allLayers[0] + }, [this.preferCategory] + ) + layer.addCallbackAndRunD(layer => console.log(layer)) + const map = new Minimap( + { + location: this._centerLocation, + background: layer + } + ) + map.leafletMap.addCallbackAndRunD(leaflet => { + console.log(leaflet.getBounds(), leaflet.getBounds().pad(0.15)) + leaflet.setMaxBounds( + leaflet.getBounds().pad(0.15) + ) + }) + + layer.map(layer => { + + const leaflet = map.leafletMap.data + if (leaflet === undefined || layer === undefined) { + return; + } + + leaflet.setMaxZoom(layer.max_zoom) + leaflet.setMinZoom(layer.max_zoom - 3) + leaflet.setZoom(layer.max_zoom - 1) + + }, [map.leafletMap]) + return new Combine([ + new Combine([ + Svg.crosshair_empty_ui() + .SetClass("block relative") + .SetStyle("left: -1.25rem; top: -1.25rem; width: 2.5rem; height: 2.5rem") + ]).SetClass("block w-0 h-0 z-10 relative") + .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%"), + map + .SetClass("z-0 relative block w-full h-full bg-gray-100") + + ]).ConstructElement(); + } + +} \ No newline at end of file diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index fdb223d060..1efe04b3a9 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -73,7 +73,10 @@ }, "tags": [ "amenity=public_bookcase" - ] + ], + "preciseInput": { + "preferredBackground": "photo" + } } ], "tagRenderings": [ diff --git a/assets/svg/crosshair-empty.svg b/assets/svg/crosshair-empty.svg new file mode 100644 index 0000000000..36a6e18f8a --- /dev/null +++ b/assets/svg/crosshair-empty.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/test.ts b/test.ts index eb29b9921d..5d077d3544 100644 --- a/test.ts +++ b/test.ts @@ -7,6 +7,9 @@ import {UIEventSource} from "./Logic/UIEventSource"; import {Tag} from "./Logic/Tags/Tag"; import {QueryParameters} from "./Logic/Web/QueryParameters"; import {Translation} from "./UI/i18n/Translation"; +import LocationInput from "./UI/Input/LocationInput"; +import Loc from "./Models/Loc"; +import {VariableUiElement} from "./UI/Base/VariableUIElement"; /*import ValidatedTextField from "./UI/Input/ValidatedTextField"; import Combine from "./UI/Base/Combine"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; @@ -148,19 +151,15 @@ function TestMiniMap() { featureSource.ping() } //*/ -QueryParameters.GetQueryParameter("test", "true").setData("true") -State.state= new State(undefined) -const id = "node/5414688303" -State.state.allElements.addElementById(id, new UIEventSource({id: id})) -new Combine([ - new DeleteWizard(id, { - noDeleteOptions: [ - { - if:[ new Tag("access","private")], - then: new Translation({ - en: "Very private! Delete now or me send lawfull lawyer" - }) - } - ] - }), -]).AttachTo("maindiv") + +const li = new LocationInput({ + preferCategory:"photo", + centerLocation: + new UIEventSource({ + lat: 51.21576, lon: 3.22001, zoom: 19 + }) +}) + li.SetStyle("height: 20rem") + .AttachTo("maindiv") + +new VariableUiElement(li.GetValue().map(v => JSON.stringify(v, null, " "))).AttachTo("extradiv") \ No newline at end of file From a7024cc1faf4735bbe9f5e45732fd56525931547 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 14 Jul 2021 00:23:32 +0200 Subject: [PATCH 62/64] Add designated lock icon --- Logic/Actors/GeoLocationHandler.ts | 4 ++-- Svg.ts | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index f8e38f50c4..ed944ed0ab 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -78,7 +78,7 @@ export default class GeoLocationHandler extends VariableUiElement { hasLocation.map( (hasLocationData) => { if (isLocked.data) { - return Svg.up_ui(); + return Svg.crosshair_locked_ui(); } else if (hasLocationData) { return Svg.crosshair_blue_ui(); } else if (isActive.data) { @@ -114,10 +114,10 @@ export default class GeoLocationHandler extends VariableUiElement { }); this.onClick(() => { - self.init(true); if (self._isActive.data) { self._isLocked.setData(!self._isLocked.data); } + self.init(true); }); this.init(false); diff --git a/Svg.ts b/Svg.ts index 89baa7becd..0266e43e8e 100644 --- a/Svg.ts +++ b/Svg.ts @@ -94,6 +94,11 @@ export default class Svg { public static crosshair_empty_svg() { return new Img(Svg.crosshair_empty, true);} public static crosshair_empty_ui() { return new FixedUiElement(Svg.crosshair_empty_img);} + public static crosshair_locked = " image/svg+xml " + public static crosshair_locked_img = Img.AsImageElement(Svg.crosshair_locked) + public static crosshair_locked_svg() { return new Img(Svg.crosshair_locked, true);} + public static crosshair_locked_ui() { return new FixedUiElement(Svg.crosshair_locked_img);} + public static crosshair = " image/svg+xml " public static crosshair_img = Img.AsImageElement(Svg.crosshair) public static crosshair_svg() { return new Img(Svg.crosshair, true);} @@ -339,4 +344,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair-empty.svg": Svg.crosshair_empty,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair-empty.svg": Svg.crosshair_empty,"crosshair-locked.svg": Svg.crosshair_locked,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} From 196894cca7813f2abb5a353e033e390b37a2b86a Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 14 Jul 2021 00:25:09 +0200 Subject: [PATCH 63/64] Add designated lock icon --- assets/svg/crosshair-locked.svg | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 assets/svg/crosshair-locked.svg diff --git a/assets/svg/crosshair-locked.svg b/assets/svg/crosshair-locked.svg new file mode 100644 index 0000000000..b1a741c287 --- /dev/null +++ b/assets/svg/crosshair-locked.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From 99512724e011ef13a2a503be36b701b3d8cb1626 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 14 Jul 2021 10:05:10 +0200 Subject: [PATCH 64/64] Fix build --- Customizations/JSON/LayerConfigJson.ts | 2 +- Logic/Actors/GeoLocationHandler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index 6739e33fdc..ca272ecb05 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -225,7 +225,7 @@ export interface LayerConfigJson { * If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category. */ preciseInput?: true | { - preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" + preferredBackground: "osmbasedmap" | "photo" | "historicphoto" | "map" | string } }[], diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index ed944ed0ab..6d37e6f4ed 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -114,7 +114,7 @@ export default class GeoLocationHandler extends VariableUiElement { }); this.onClick(() => { - if (self._isActive.data) { + if (self._hasLocation.data) { self._isLocked.setData(!self._isLocked.data); } self.init(true);