diff --git a/.github/actions/setup-and-validate/action.yml b/.github/actions/setup-and-validate/action.yml index 51201b9589..baa0af7a25 100644 --- a/.github/actions/setup-and-validate/action.yml +++ b/.github/actions/setup-and-validate/action.yml @@ -19,7 +19,7 @@ runs: shell: bash - name: REUSE compliance check - uses: fsfe/reuse-action@v2 + uses: fsfe/reuse-action@952281636420dd0b691786c93e9d3af06032f138 - name: create generated dir run: mkdir ./assets/generated diff --git a/.github/workflows/deploy_pietervdvn.yml b/.github/workflows/deploy_pietervdvn.yml index ec51d7ea70..817f25606b 100644 --- a/.github/workflows/deploy_pietervdvn.yml +++ b/.github/workflows/deploy_pietervdvn.yml @@ -89,7 +89,7 @@ jobs: env: TARGET_BRANCH: ${{ env.TARGET_BRANCH }} - - uses: mshick/add-pr-comment@v1 + - uses: mshick/add-pr-comment@a96c578acba98b60f16c6866d5f20478dc4ef68b name: Comment the PR with the review URL if: ${{ success() && github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/master' }} with: diff --git a/404.html b/404.html index 8d425e109f..9b667602b6 100644 --- a/404.html +++ b/404.html @@ -3,6 +3,7 @@ + diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 04e74522d8..c7724e9fc2 100644 --- a/assets/layers/atm/atm.json +++ b/assets/layers/atm/atm.json @@ -504,4 +504,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index 0234018a9d..9bea58a071 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -890,6 +890,9 @@ "mappings": [ { "if": "tourism=artwork", + "addExtraTags": [ + "not:tourism:artwork=" + ], "then": { "en": "This bench has an integrated artwork", "nl": "Deze bank heeft een geïntegreerd kunstwerk", @@ -902,7 +905,7 @@ } }, { - "if": "tourism=", + "if": "not:tourism:artwork=yes", "then": { "en": "This bench does not have an integrated artwork", "nl": "Deze bank heeft geen geïntegreerd kunstwerk", @@ -913,7 +916,18 @@ "cs": "Tato lavička nemá integrované umělecké dílo", "he": "לספסל זה אין יצירת אמנות משולבת", "pl": "Ta ławka nie ma wbudowanego dzieła sztuki" - } + }, + "addExtraTags": [ + "tourism=" + ] + }, + { + "if": "tourism=", + "then": { + "en": "This bench probably doesn't have an integrated artwork", + "nl": "Deze bank heeft waarschijnlijk geen geïntegreerd kunstwerk" + }, + "hideInAnswer": true } ], "questionHint": { diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index d90821ded4..ea86ad10a7 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -363,5 +363,6 @@ "fr": "Un vélo café est un café à destination des cyclistes avec, par exemple, des services tels qu’une pompe, et de nombreuses décorations liées aux vélos, etc.", "cs": "Cyklokavárna je kavárna zaměřená na cyklisty, například se službami, jako je pumpa, se spoustou výzdoby související s jízdními koly, …", "ca": "Un cafè ciclista és un cafè enfocat a ciclistes, per exemple, amb serveis com una manxa, amb molta decoració relacionada amb el ciclisme, …" - } + }, + "deletion": true } diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 174b24c58f..62361dea1f 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -5294,4 +5294,4 @@ }, "neededChangesets": 10 } -} \ No newline at end of file +} diff --git a/assets/layers/elongated_coin/elongated_coin.json b/assets/layers/elongated_coin/elongated_coin.json index ac5d68e545..872bb92a39 100644 --- a/assets/layers/elongated_coin/elongated_coin.json +++ b/assets/layers/elongated_coin/elongated_coin.json @@ -386,4 +386,4 @@ "accepts_debit_cards", "accepts_credit_cards" ] -} \ No newline at end of file +} diff --git a/assets/layers/matchpoint/matchpoint.json b/assets/layers/matchpoint/matchpoint.json deleted file mode 100644 index 16375a75a2..0000000000 --- a/assets/layers/matchpoint/matchpoint.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "matchpoint", - "description": "The default rendering for a locationInput which snaps onto another object", - "source": "special", - "mapRendering": [ - { - "location": [ - "point", - "centroid" - ], - "icon": "./assets/svg/crosshair-empty.svg" - } - ] -} diff --git a/assets/layers/rainbow_crossings/rainbow_crossings.json b/assets/layers/rainbow_crossings/rainbow_crossings.json index 4eb94edcee..a24c729d78 100644 --- a/assets/layers/rainbow_crossings/rainbow_crossings.json +++ b/assets/layers/rainbow_crossings/rainbow_crossings.json @@ -134,4 +134,4 @@ "lineCap": "square" } ] -} \ No newline at end of file +} diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index f85b8abd6b..86ac8e6c3b 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -23,7 +23,7 @@ "osmTags": "amenity=recycling" }, "calculatedTags": [ - "_waste_amount=Object.values(Object.keys(feat.properties).filter((key) => key.startsWith('recycling:')).reduce((cur, key) => { return Object.assign(cur, { [key]: feat.properties[key] })}, {})).reduce((n, x) => n + (x == \"yes\"), 0);" + "_waste_amount=Object.keys(feat.properties).filter(key => key.startsWith('recycling:')).filter(k => feat.properties[k] === 'yes').length" ], "minzoom": 10, "title": { @@ -1569,4 +1569,4 @@ "enableRelocation": true, "enableImproveAccuracy": true } -} \ No newline at end of file +} diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json index a94b25a4f0..c2ea234a9c 100644 --- a/assets/layers/school/school.json +++ b/assets/layers/school/school.json @@ -20,7 +20,7 @@ } }, "calculatedTags": [ - "_enclosing=feat.enclosingFeatures('school').map(f => f.feat.properties.id)", + "_enclosing=enclosingFeatures(feat)('school').map(f => f.feat.properties.id)", "_is_enclosed=feat.properties._enclosing != '[]'" ], "isShown": { diff --git a/assets/layers/shower/shower.json b/assets/layers/shower/shower.json index cc1761692e..5dc7a8aede 100644 --- a/assets/layers/shower/shower.json +++ b/assets/layers/shower/shower.json @@ -244,4 +244,4 @@ "fr": "Une couche affichant les douches (publiques)", "ca": "Una capa que mostra dutxes (públiques)" } -} \ No newline at end of file +} diff --git a/assets/layers/surveillance_camera/ALPR.svg b/assets/layers/surveillance_camera/ALPR.svg new file mode 100644 index 0000000000..a25527c21a --- /dev/null +++ b/assets/layers/surveillance_camera/ALPR.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/surveillance_camera/ALPR.svg.license b/assets/layers/surveillance_camera/ALPR.svg.license new file mode 100644 index 0000000000..ed02883002 --- /dev/null +++ b/assets/layers/surveillance_camera/ALPR.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Pieter Vander Vennet +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/surveillance_camera/ALPR_Example.jpg b/assets/layers/surveillance_camera/ALPR_Example.jpg new file mode 100644 index 0000000000..b16999e684 Binary files /dev/null and b/assets/layers/surveillance_camera/ALPR_Example.jpg differ diff --git a/assets/layers/surveillance_camera/ALPR_Example.jpg.license b/assets/layers/surveillance_camera/ALPR_Example.jpg.license new file mode 100644 index 0000000000..a9e2bface9 --- /dev/null +++ b/assets/layers/surveillance_camera/ALPR_Example.jpg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: synx508 +SPDX-License-Identifier: CC-BY-NC 2.0 \ No newline at end of file diff --git a/assets/layers/surveillance_camera/ALPR_Example2.jpg b/assets/layers/surveillance_camera/ALPR_Example2.jpg new file mode 100644 index 0000000000..f8a535de98 Binary files /dev/null and b/assets/layers/surveillance_camera/ALPR_Example2.jpg differ diff --git a/assets/layers/surveillance_camera/ALPR_Example2.jpg.license b/assets/layers/surveillance_camera/ALPR_Example2.jpg.license new file mode 100644 index 0000000000..a0a3c03243 --- /dev/null +++ b/assets/layers/surveillance_camera/ALPR_Example2.jpg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: https://commons.wikimedia.org/wiki/User:Mbrickn +SPDX-License-Identifier: CC-BY 4.0 \ No newline at end of file diff --git a/assets/layers/surveillance_camera/license_info.json b/assets/layers/surveillance_camera/license_info.json new file mode 100644 index 0000000000..a0e23e32e0 --- /dev/null +++ b/assets/layers/surveillance_camera/license_info.json @@ -0,0 +1,30 @@ +[ + { + "path": "ALPR.svg", + "license": "CC0-1.0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, + { + "path": "ALPR_Example.jpg", + "license": "CC-BY-NC 2.0", + "authors": [ + "synx508" + ], + "sources": [ + "https://www.flickr.com/photos/synx508/5742253934/" + ] + }, + { + "path": "ALPR_Example2.jpg", + "license": "CC-BY 4.0", + "authors": [ + "https://commons.wikimedia.org/wiki/User:Mbrickn" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:ANPR_Camera_Front.jpg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index dbbb75865d..2e59fb3975 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -40,6 +40,32 @@ }, "tagRenderings": [ "images", + { + "id": "has_alpr", + "question": { + "en": "Can this camera automatically detect license plates?" + }, + "questionHint": { + "en": "An ALPR (Automatic License Plate Reader) typically has two lenses and an array of infrared LEDS in between." + }, + "mappings": [ + { + "if": "surveillance:type=camera", + "then": { + "en": "This is a camera without number plate recognition." + } + }, + { + "if": "surveillance:type=ALPR", + "then": { + "en": "This is an ALPR (Automatic License Plate Reader)" + }, + "icon": { + "path": "./assets/layers/surveillance_camera/ALPR.svg" + } + } + ] + }, { "question": { "en": "What kind of camera is this?", @@ -53,11 +79,7 @@ }, "mappings": [ { - "if": { - "and": [ - "camera:type=fixed" - ] - }, + "if": "camera:type=fixed", "then": { "en": "A fixed (non-moving) camera", "nl": "Een vaste camera", @@ -66,14 +88,11 @@ "de": "Eine fest montierte (nicht bewegliche) Kamera", "ca": "Una càmera fixa (no movible)", "es": "Cámara fija (no móvil)" - } + }, + "icon": "./assets/themes/surveillance/cam_right.svg" }, { - "if": { - "and": [ - "camera:type=dome" - ] - }, + "if": "camera:type=dome", "then": { "en": "A dome camera (which can turn)", "nl": "Een dome (bolvormige camera die kan draaien)", @@ -83,7 +102,8 @@ "de": "Eine Kuppelkamera (drehbar)", "ca": "Càmera de cúpula (que pot girar)", "es": "Cámara con domo (que se puede girar)" - } + }, + "icon": "./assets/themes/surveillance/dome.svg" }, { "if": { @@ -595,6 +615,40 @@ "fr": "une caméra de surveillance fixée au mur" }, "snapToLayer": "walls_and_buildings" + }, + { + "tags": [ + "man_made=surveillance", + "surveillance:type=ALPR" + ], + "title": { + "en": "an ALPR camera (Automatic Number Plate Reader)" + }, + "description": { + "en": "An ALPR typically has two lenses and an array of infrared lights." + }, + "exampleImages": [ + "./assets/layers/surveillance_camera/ALPR_Example.jpg", + "./assets/layers/surveillance_camera/ALPR_Example2.jpg" + ] + }, + { + "tags": [ + "man_made=surveillance", + "surveillance:type=ALPR", + "camera:mount=wall" + ], + "title": { + "en": "an ALPR camera (Automatic Number Plate Reader) mounted on a wall" + }, + "description": { + "en": "An ALPR typically has two lenses and an array of infrared lights." + }, + "exampleImages": [ + "./assets/layers/surveillance_camera/ALPR_Example.jpg", + "./assets/layers/surveillance_camera/ALPR_Example2.jpg" + ], + "snapToLayer": "walls_and_buildings" } ], "mapRendering": [ @@ -602,6 +656,10 @@ "icon": { "render": "./assets/themes/surveillance/logo.svg", "mappings": [ + { + "if": "surveillance:type=ALPR", + "then": "./assets/layers/surveillance_camera/ALPR.svg" + }, { "if": "camera:type=dome", "then": "./assets/themes/surveillance/dome.svg" @@ -619,15 +677,17 @@ "iconSize": { "mappings": [ { - "if": "camera:type=dome", - "then": "50,50,center" - }, - { - "if": "_direction:leftright~*", + "if": { + "and": [ + "camera:type=fixed", + "surveillance:type=camera", + "_direction:leftright~*" + ] + }, "then": "100,35,center" } ], - "render": "50,50,center" + "render": "35,35,center" }, "location": [ "point", @@ -638,7 +698,12 @@ "render": "calc({_direction:numerical}deg + 90deg)", "mappings": [ { - "if": "camera:type=dome", + "if": { + "or": [ + "camera:type=dome", + "surveillance:type=ALPR" + ] + }, "then": "0" }, { diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index 59f8c07ccb..722b26d013 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -74,8 +74,7 @@ "images", { "id": "plantnet", - "render": "{plantnet_detection()}", - "condition": "species:wikidata=" + "render": "{plantnet_detection()}" }, { "id": "tree-species-wikidata", diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index 13d583563f..10d21c736b 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -23,7 +23,8 @@ "_d=feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? ''", "_mastodon_candidate_a=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ", "_mastodon_link=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.getAttribute(\"rel\")?.indexOf('me') >= 0)[0]?.href})(feat) ", - "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a" + "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a", + "__current_background:='initial_value'" ], "tagRenderings": [ { @@ -103,6 +104,72 @@ "*": "{logout()}" } }, + { + "id": "background-layer-readonly", + "condition": { + "and": [ + "_theme:backgroundLayer~*", + "mapcomplete-preferred-background-layer~*", + "_theme:backgroundLayer!:={mapcomplete-preferred-background-layer}" + ] + }, + "render": { + "en": "This thematic map has a predefined background layer set. Your default theme setting does not apply" + } + }, + { + "id": "background-layer", + "question": { + "en": "What background layer should be shown by default?" + }, + "condition": "_theme:backgroundLayer=", + "mappings": [ + { + "if": "mapcomplete-preferred-background-layer=", + "then": { + "en": "Use the default background layer" + } + }, + { + "if": "mapcomplete-preferred-background-layer=osm", + "then": { + "en": "Use OpenStreetMap-carto as default layer" + } + }, + { + "if": "mapcomplete-preferred-background-layer=photo", + "then": { + "en": "Use aerial imagery as default background" + } + }, + { + "if": "mapcomplete-preferred-background-layer=map", + "then": { + "en": "Use a non-openstreetmap based map as default background" + } + }, + { + "if": "mapcomplete-preferred-background-layer:={__current_background}", + "then": { + "en": "Use the current background layer ({__current_background}) as default background" + }, + "hideInAnswer": { + "or": [ + "__current_background=", + "__current_background=osm", + "__current_background=initial_value" + ] + } + }, + { + "if": "mapcomplete-preferred-background-layer~*", + "then": { + "en": "Use background layer {mapcomplete-preferred-background-layer} as default background" + }, + "hideInAnswer": true + } + ] + }, { "id": "picture-license", "description": "This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants", diff --git a/assets/themes/atm/atm.json b/assets/themes/atm/atm.json index 0034cceb20..6d9e827ecc 100644 --- a/assets/themes/atm/atm.json +++ b/assets/themes/atm/atm.json @@ -172,4 +172,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/blind_osm/blind_osm.json b/assets/themes/blind_osm/blind_osm.json index 9095f965c1..9532bd0cf2 100644 --- a/assets/themes/blind_osm/blind_osm.json +++ b/assets/themes/blind_osm/blind_osm.json @@ -31,7 +31,7 @@ "startLat": 52.99238, "startLon": 6.570614, "startZoom": 20, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "builtin": "cycleways_and_roads", diff --git a/assets/themes/campersite/campersite.json b/assets/themes/campersite/campersite.json index 43ed75b0f2..def4b0124b 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -1607,4 +1607,4 @@ ] }, "credits": "joost schouppe" -} +} \ No newline at end of file diff --git a/assets/themes/charging_stations/charging_stations.json b/assets/themes/charging_stations/charging_stations.json index 8697b901a8..91137ad156 100644 --- a/assets/themes/charging_stations/charging_stations.json +++ b/assets/themes/charging_stations/charging_stations.json @@ -57,7 +57,6 @@ "startLon": 0, "startZoom": 1, "widenFactor": 1.5, - "defaultBackgroundId": "CartoDB.Voyager", "layers": [ "charging_station" ] diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index d89247da8c..e139cc7e9a 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -465,4 +465,4 @@ "toilet" ], "credits": "Christian Neumann " -} +} \ No newline at end of file diff --git a/assets/themes/cycle_highways/cycle_highways.json b/assets/themes/cycle_highways/cycle_highways.json index 024b347ba6..98df8a06c1 100644 --- a/assets/themes/cycle_highways/cycle_highways.json +++ b/assets/themes/cycle_highways/cycle_highways.json @@ -265,6 +265,6 @@ ] } ], - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "credits": "L'imaginaire" } \ No newline at end of file diff --git a/assets/themes/cycle_infra/cycle_infra.json b/assets/themes/cycle_infra/cycle_infra.json index 02297eb733..66d5839a91 100644 --- a/assets/themes/cycle_infra/cycle_infra.json +++ b/assets/themes/cycle_infra/cycle_infra.json @@ -45,7 +45,6 @@ "cs": "Mapa, kde můžete prohlížet a upravovat věci související s cyklistickou infrastrukturou. Vytvořeno během #osoc21." }, "hideFromOverview": false, - "defaultBackgroundId": "CartoDB.Voyager", "icon": "./assets/themes/cycle_infra/cycle-infra.svg", "startLat": 51, "startLon": 3.75, diff --git a/assets/themes/cyclofix/cyclofix.json b/assets/themes/cyclofix/cyclofix.json index 80d11b7e3c..a99d009c79 100644 --- a/assets/themes/cyclofix/cyclofix.json +++ b/assets/themes/cyclofix/cyclofix.json @@ -36,7 +36,6 @@ "credits": "Originally created during Open Summer of Code by Pieter Fiers, Thibault Declercq, Pierre Barban, Joost Schouppe and Pieter Vander Vennet", "icon": "./assets/themes/cyclofix/logo.svg", "startLat": 0, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 0, "startZoom": 1, "widenFactor": 2, diff --git a/assets/themes/drinking_water/drinking_water.json b/assets/themes/drinking_water/drinking_water.json index 95432c6d6b..eb6cd55de9 100644 --- a/assets/themes/drinking_water/drinking_water.json +++ b/assets/themes/drinking_water/drinking_water.json @@ -37,7 +37,6 @@ }, "icon": "./assets/themes/drinking_water/logo.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/education/education.json b/assets/themes/education/education.json index a5860ec19a..fc8aa5260e 100644 --- a/assets/themes/education/education.json +++ b/assets/themes/education/education.json @@ -24,7 +24,6 @@ "eu": "Hezkuntza", "pl": "Edukacja" }, - "defaultBackgroundId": "CartoDB.Voyager", "startLat": 0, "startLon": 0, "startZoom": 0, diff --git a/assets/themes/elongated_coin/elongated_coin.json b/assets/themes/elongated_coin/elongated_coin.json index 6cb1ca911c..4ac8f93a1a 100644 --- a/assets/themes/elongated_coin/elongated_coin.json +++ b/assets/themes/elongated_coin/elongated_coin.json @@ -19,4 +19,4 @@ "startLat": 53.0565, "startLon": 8.7492, "startZoom": 11 -} +} \ No newline at end of file diff --git a/assets/themes/etymology/etymology.json b/assets/themes/etymology/etymology.json index 074b624fe7..e901d7153e 100644 --- a/assets/themes/etymology/etymology.json +++ b/assets/themes/etymology/etymology.json @@ -288,4 +288,4 @@ } ], "hideFromOverview": false -} +} \ No newline at end of file diff --git a/assets/themes/fritures/fritures.json b/assets/themes/fritures/fritures.json index 18804d3322..64cf09969e 100644 --- a/assets/themes/fritures/fritures.json +++ b/assets/themes/fritures/fritures.json @@ -69,4 +69,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/ghostbikes/ghostbikes.json b/assets/themes/ghostbikes/ghostbikes.json index 3d80bbc440..74f25481a1 100644 --- a/assets/themes/ghostbikes/ghostbikes.json +++ b/assets/themes/ghostbikes/ghostbikes.json @@ -44,7 +44,7 @@ "layers": [ "ghost_bike" ], - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "clustering": { "maxZoom": 0 } diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 1a35d878d4..b3931dcb11 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -773,4 +773,4 @@ "overpassMaxZoom": 15, "osmApiTileSize": 17, "credits": "Pieter Vander Vennet" -} +} \ No newline at end of file diff --git a/assets/themes/healthcare/healthcare.json b/assets/themes/healthcare/healthcare.json index 70052f91e1..488a88fe3c 100644 --- a/assets/themes/healthcare/healthcare.json +++ b/assets/themes/healthcare/healthcare.json @@ -27,7 +27,6 @@ }, "icon": "./assets/layers/doctors/doctors.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/indoors/indoors.json b/assets/themes/indoors/indoors.json index bef9d9bbe5..60ae89052c 100644 --- a/assets/themes/indoors/indoors.json +++ b/assets/themes/indoors/indoors.json @@ -27,7 +27,6 @@ }, "icon": "./assets/layers/entrance/entrance.svg", "startLat": 51.17181, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.144383, "startZoom": 14, "widenFactor": 2, diff --git a/assets/themes/maps/maps.json b/assets/themes/maps/maps.json index c484a92c12..4d88672cd7 100644 --- a/assets/themes/maps/maps.json +++ b/assets/themes/maps/maps.json @@ -47,7 +47,7 @@ "startLon": 0, "startZoom": 1, "widenFactor": 5, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ "map" ] diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json index 7166320549..38544e0b7b 100644 --- a/assets/themes/onwheels/onwheels.json +++ b/assets/themes/onwheels/onwheels.json @@ -24,7 +24,6 @@ }, "icon": "./assets/themes/onwheels/crest.svg", "startLat": 50.86622, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.350103, "startZoom": 17, "widenFactor": 2, @@ -525,4 +524,4 @@ ] }, "enableDownload": true -} +} \ No newline at end of file diff --git a/assets/themes/openwindpowermap/openwindpowermap.json b/assets/themes/openwindpowermap/openwindpowermap.json index 2b21d36c9c..a453698d52 100644 --- a/assets/themes/openwindpowermap/openwindpowermap.json +++ b/assets/themes/openwindpowermap/openwindpowermap.json @@ -41,6 +41,5 @@ "layers": [ "windturbine" ], - "defaultBackgroundId": "CartoDB.Voyager", "credits": "Seppe Santens" } \ No newline at end of file diff --git a/assets/themes/osm_community_index/osm_community_index.json b/assets/themes/osm_community_index/osm_community_index.json index 229f9aca47..16aa4e481d 100644 --- a/assets/themes/osm_community_index/osm_community_index.json +++ b/assets/themes/osm_community_index/osm_community_index.json @@ -29,7 +29,6 @@ }, "icon": "./assets/themes/osm_community_index/osm.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "clustering": false, diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json index e63b216c6c..650095516c 100644 --- a/assets/themes/pets/pets.json +++ b/assets/themes/pets/pets.json @@ -144,14 +144,7 @@ "width": 5 } ], - "presets": [ - { - "tags": [ - "shop=yes", - "dog=yes" - ] - } - ], + "=presets": [], "source": { "=osmTags": { "and": [ diff --git a/assets/themes/playgrounds/playgrounds.json b/assets/themes/playgrounds/playgrounds.json index 50820e0912..50fd8786d9 100644 --- a/assets/themes/playgrounds/playgrounds.json +++ b/assets/themes/playgrounds/playgrounds.json @@ -71,4 +71,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/themes/postboxes/postboxes.json b/assets/themes/postboxes/postboxes.json index 172977b86f..de88895986 100644 --- a/assets/themes/postboxes/postboxes.json +++ b/assets/themes/postboxes/postboxes.json @@ -46,7 +46,6 @@ "startLon": 9.9937, "startZoom": 13, "widenFactor": 1.5, - "defaultBackgroundId": "CartoDB.Voyager", "clustering": { "maxZoom": 14, "minNeededElements": 100 diff --git a/assets/themes/rainbow_crossings/rainbow_crossings.json b/assets/themes/rainbow_crossings/rainbow_crossings.json index 689c29df95..043c87e28e 100644 --- a/assets/themes/rainbow_crossings/rainbow_crossings.json +++ b/assets/themes/rainbow_crossings/rainbow_crossings.json @@ -24,7 +24,6 @@ }, "icon": "./assets/themes/rainbow_crossings/logo.svg", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index be57a32c61..3b382ed8ba 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -27,7 +27,7 @@ "startZoom": 12, "widenFactor": 1.2, "socialImage": "./assets/themes/speelplekken/social_image.jpg", - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "id": "shadow", diff --git a/assets/themes/stations/stations.json b/assets/themes/stations/stations.json index f24f1464ac..8b242d1af4 100644 --- a/assets/themes/stations/stations.json +++ b/assets/themes/stations/stations.json @@ -27,7 +27,7 @@ "startLon": 0, "startZoom": 0, "hideFromOverview": true, - "defaultBackgroundId": "CartoDB.Positron", + "defaultBackgroundId": "maptiler.backdrop", "layers": [ { "builtin": "indoors", @@ -412,4 +412,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/themes/surveillance/surveillance.json b/assets/themes/surveillance/surveillance.json index 54d568bf55..4e616eb455 100644 --- a/assets/themes/surveillance/surveillance.json +++ b/assets/themes/surveillance/surveillance.json @@ -54,8 +54,8 @@ "startLon": 0, "startZoom": 1, "widenFactor": 2, - "defaultBackgroundId": "osm", + "defaultBackgroundId": "maptiler.carto", "layers": [ "surveillance_camera" ] -} \ No newline at end of file +} diff --git a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json index 86621c4910..6436b43620 100644 --- a/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json +++ b/assets/themes/toerisme_vlaanderen/toerisme_vlaanderen.json @@ -239,4 +239,4 @@ "hideFromOverview": true, "enableMoreQuests": false, "enableShareScreen": false -} +} \ No newline at end of file diff --git a/assets/themes/vending_machine/vending_machine.json b/assets/themes/vending_machine/vending_machine.json index 7f053f2d2c..5257a28404 100644 --- a/assets/themes/vending_machine/vending_machine.json +++ b/assets/themes/vending_machine/vending_machine.json @@ -29,6 +29,7 @@ "sameAs": "vending_machine" }, "minzoom": 18, + "=presets": [], "source": { "osmTags": { "and": [ diff --git a/assets/themes/walls_and_buildings/walls_and_buildings.json b/assets/themes/walls_and_buildings/walls_and_buildings.json index 9175d774a1..1c221af373 100644 --- a/assets/themes/walls_and_buildings/walls_and_buildings.json +++ b/assets/themes/walls_and_buildings/walls_and_buildings.json @@ -26,7 +26,6 @@ }, "icon": "./assets/layers/walls_and_buildings/walls_and_buildings.png", "startLat": 50.8465573, - "defaultBackgroundId": "CartoDB.Voyager", "startLon": 4.351697, "startZoom": 16, "widenFactor": 2, diff --git a/assets/themes/width/width.json b/assets/themes/width/width.json index 6f16d225e0..6d0bfb1147 100644 --- a/assets/themes/width/width.json +++ b/assets/themes/width/width.json @@ -271,4 +271,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000000..16f8d4541e --- /dev/null +++ b/config.json @@ -0,0 +1,3 @@ +{ + "#": "Settings in this file override the `config`-section of `package.json`" +} diff --git a/index.html b/index.html index 4a29a70269..5d850ae9ef 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + @@ -16,8 +17,6 @@ MapComplete - - Mastodon @@ -48,10 +47,12 @@ + +
- dispatch("click")} options={{ extraClasses: twMerge("flex items-center", clss) }} > - + diff --git a/src/UI/Base/CenterFlexedElement.ts b/src/UI/Base/CenterFlexedElement.ts deleted file mode 100644 index 6e1af34b0b..0000000000 --- a/src/UI/Base/CenterFlexedElement.ts +++ /dev/null @@ -1,32 +0,0 @@ -import BaseUIElement from "../BaseUIElement" - -export class CenterFlexedElement extends BaseUIElement { - private _html: string - - constructor(html: string) { - super() - this._html = html ?? "" - } - - InnerRender(): string { - return this._html - } - - AsMarkdown(): string { - return this._html - } - - protected InnerConstructElement(): HTMLElement { - const e = document.createElement("div") - e.innerHTML = this._html - e.style.display = "flex" - e.style.height = "100%" - e.style.width = "100%" - e.style.flexDirection = "column" - e.style.flexWrap = "nowrap" - e.style.alignContent = "center" - e.style.justifyContent = "center" - e.style.alignItems = "center" - return e - } -} diff --git a/src/UI/Base/FileSelector.svelte b/src/UI/Base/FileSelector.svelte new file mode 100644 index 0000000000..fa4dc2ad69 --- /dev/null +++ b/src/UI/Base/FileSelector.svelte @@ -0,0 +1,40 @@ + + +
+ + { + drawAttention = false; + dispatcher("submit", inputElement.files)}} + + on:dragend={ () => {drawAttention = false}} + on:dragover|preventDefault|stopPropagation={(e) => { + console.log("Dragging over!") + drawAttention = true + e.dataTransfer.drop = "copy" + }} + on:dragstart={ () => {drawAttention = false}} + on:drop|preventDefault|stopPropagation={(e) => { + console.log("Got a 'drop'") + drawAttention = false + dispatcher("submit", e.dataTransfer.files) + }} + type="file" + > +
diff --git a/src/UI/Base/FixedUiElement.ts b/src/UI/Base/FixedUiElement.ts index aa8e41c27e..2fc1e46f22 100644 --- a/src/UI/Base/FixedUiElement.ts +++ b/src/UI/Base/FixedUiElement.ts @@ -1,5 +1,8 @@ import BaseUIElement from "../BaseUIElement" - +import { Utils } from "../../Utils" +/** + * @deprecated + */ export class FixedUiElement extends BaseUIElement { public readonly content: string @@ -8,10 +11,6 @@ export class FixedUiElement extends BaseUIElement { this.content = html ?? "" } - InnerRender(): string { - return this.content - } - AsMarkdown(): string { if (this.HasClass("code")) { if (this.content.indexOf("\n") > 0 || this.HasClass("block")) { @@ -27,7 +26,7 @@ export class FixedUiElement extends BaseUIElement { protected InnerConstructElement(): HTMLElement { const e = document.createElement("span") - e.innerHTML = this.content + e.innerHTML = Utils.purify(this.content) return e } } diff --git a/src/UI/Base/FromHtml.svelte b/src/UI/Base/FromHtml.svelte index b03b2624eb..9178390559 100644 --- a/src/UI/Base/FromHtml.svelte +++ b/src/UI/Base/FromHtml.svelte @@ -2,12 +2,14 @@ /** * Given an HTML string, properly shows this */ + import { Utils } from "../../Utils"; export let src: string + let htmlElem: HTMLElement $: { if (htmlElem) { - htmlElem.innerHTML = src + htmlElem.innerHTML = Utils.purify(src) } } diff --git a/src/UI/Base/Link.ts b/src/UI/Base/Link.ts index 693f26218b..f3474f27fd 100644 --- a/src/UI/Base/Link.ts +++ b/src/UI/Base/Link.ts @@ -53,17 +53,17 @@ export default class Link extends BaseUIElement { } const el = document.createElement("a") if (typeof this._href === "string") { - el.href = this._href + el.setAttribute("href", this._href) } else { this._href.addCallbackAndRun((href) => { - el.href = href + el.setAttribute("href", href) }) } if (this._newTab) { el.target = "_blank" } if (this._download) { - el.download = this._download + el.setAttribute("download", this._download) } el.appendChild(embeddedShow) return el diff --git a/src/UI/Base/Loading.svelte b/src/UI/Base/Loading.svelte index 097bc4472c..ff8a622d7c 100644 --- a/src/UI/Base/Loading.svelte +++ b/src/UI/Base/Loading.svelte @@ -1,9 +1,12 @@ - -
+
diff --git a/src/UI/Base/NextButton.svelte b/src/UI/Base/NextButton.svelte index a546fd8c1c..6b4a64dd83 100644 --- a/src/UI/Base/NextButton.svelte +++ b/src/UI/Base/NextButton.svelte @@ -20,6 +20,6 @@
- +
diff --git a/src/UI/Base/VariableUIElement.ts b/src/UI/Base/VariableUIElement.ts index e4cfc4c4f5..69d8a8330b 100644 --- a/src/UI/Base/VariableUIElement.ts +++ b/src/UI/Base/VariableUIElement.ts @@ -1,7 +1,11 @@ import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" import Combine from "./Combine" +import { Utils } from "../../Utils" +/** + * @deprecated + */ export class VariableUiElement extends BaseUIElement { private readonly _contents?: Store @@ -42,7 +46,7 @@ export class VariableUiElement extends BaseUIElement { return } if (typeof contents === "string") { - el.innerHTML = contents + el.innerHTML = Utils.purify(contents) } else if (contents instanceof Array) { for (const content of contents) { const c = content?.ConstructElement() diff --git a/src/UI/BigComponents/BackgroundSwitcher.svelte b/src/UI/BigComponents/BackgroundSwitcher.svelte index 3ed280b2f9..a92bab52f4 100644 --- a/src/UI/BigComponents/BackgroundSwitcher.svelte +++ b/src/UI/BigComponents/BackgroundSwitcher.svelte @@ -37,7 +37,7 @@ function updatedAltLayer() { const available = availableRasterLayers.data const current = rasterLayer.data - const defaultLayer = AvailableRasterLayers.maplibre + const defaultLayer = AvailableRasterLayers.maptilerDefaultLayer const firstOther = available.find((l) => l !== defaultLayer) const secondOther = available.find((l) => l !== defaultLayer && l !== firstOther) raster0.setData(firstOther === current ? defaultLayer : firstOther) diff --git a/src/UI/BigComponents/PlantNetSpeciesSearch.ts b/src/UI/BigComponents/PlantNetSpeciesSearch.ts deleted file mode 100644 index b7e503f6e0..0000000000 --- a/src/UI/BigComponents/PlantNetSpeciesSearch.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { VariableUiElement } from "../Base/VariableUIElement" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import PlantNet from "../../Logic/Web/PlantNet" -import Loading from "../Base/Loading" -import Wikidata from "../../Logic/Web/Wikidata" -import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox" -import { Button } from "../Base/Button" -import Combine from "../Base/Combine" -import Title from "../Base/Title" -import Translations from "../i18n/Translations" -import List from "../Base/List" -import Svg from "../../Svg" - -export default class PlantNetSpeciesSearch extends VariableUiElement { - /*** - * Given images, queries plantnet to search a species matching those images. - * A list of species will be presented to the user, after which they can confirm an item. - * The wikidata-url is returned in the callback when the user selects one - */ - constructor(images: Store, onConfirm: (wikidataUrl: string) => Promise) { - const t = Translations.t.plantDetection - super( - images - .bind((images) => { - if (images.length === 0) { - return null - } - return UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0, 5))) - }) - .map((result) => { - if (images.data.length === 0) { - return new Combine([ - t.takeImages, - t.howTo.intro, - new List([t.howTo.li0, t.howTo.li1, t.howTo.li2, t.howTo.li3]), - ]).SetClass("flex flex-col") - } - if (result === undefined) { - return new Loading(t.querying.Subs(images.data)) - } - - if (result["error"] !== undefined) { - return t.error.Subs(result).SetClass("alert") - } - console.log(result) - const success = result["success"] - - const selectedSpecies = new UIEventSource(undefined) - const speciesInformation = success.results - .filter((species) => species.score >= 0.005) - .map((species) => { - const wikidata = UIEventSource.FromPromise( - Wikidata.Sparql<{ species }>( - ["?species", "?speciesLabel"], - ['?species wdt:P846 "' + species.gbif.id + '"'] - ) - ) - - const confirmButton = new Button(t.seeInfo, async () => { - await selectedSpecies.setData(wikidata.data[0].species?.value) - }).SetClass("btn") - - const match = t.matchPercentage - .Subs({ match: Math.round(species.score * 100) }) - .SetClass("font-bold") - - const extraItems = new Combine([match, confirmButton]).SetClass( - "flex flex-col" - ) - - return new WikidataPreviewBox( - wikidata.map((wd) => - wd == undefined ? undefined : wd[0]?.species?.value - ), - { - whileLoading: new Loading( - t.loadingWikidata.Subs({ - species: species.species.scientificNameWithoutAuthor, - }) - ), - extraItems: [new Combine([extraItems])], - - imageStyle: "max-width: 8rem; width: unset; height: 8rem", - } - ).SetClass("border-2 border-subtle rounded-xl block mb-2") - }) - const plantOverview = new Combine([ - new Title(t.overviewTitle), - t.overviewIntro, - t.overviewVerify.SetClass("font-bold"), - ...speciesInformation, - ]).SetClass("flex flex-col") - - return new VariableUiElement( - selectedSpecies.map((wikidataSpecies) => { - if (wikidataSpecies === undefined) { - return plantOverview - } - return new Combine([ - new Button( - new Combine([ - Svg.back_svg().SetClass( - "w-6 mr-1 bg-white rounded-full p-1" - ), - t.back, - ]).SetClass("flex"), - () => { - selectedSpecies.setData(undefined) - } - ).SetClass("btn btn-secondary"), - - new Button( - new Combine([ - Svg.confirm_svg().SetClass("w-6 mr-1"), - t.confirm, - ]).SetClass("flex"), - () => { - onConfirm(wikidataSpecies) - } - ).SetClass("btn"), - ]).SetClass("flex justify-between") - }) - ) - }) - ) - } -} diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index 8806d21fee..2e43de9b08 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -1,40 +1,44 @@ @@ -58,12 +62,24 @@
- p === "denied")}> + {#if $currentGPSLocation !== undefined || $geopermission === "prompt"} - + + {:else if $geopermission === "requested"} + + {:else if $geopermission !== "denied"} + + {/if}
diff --git a/src/UI/DownloadFlow/DownloadPdf.svelte b/src/UI/DownloadFlow/DownloadPdf.svelte index 9a807c1ee6..379950b8b0 100644 --- a/src/UI/DownloadFlow/DownloadPdf.svelte +++ b/src/UI/DownloadFlow/DownloadPdf.svelte @@ -29,7 +29,7 @@ const templateUrls = SvgToPdf.templates[templateName].pages const templates: string[] = await Promise.all(templateUrls.map((url) => Utils.download(url))) console.log("Templates are", templates) - const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maplibre + const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maptilerDefaultLayer const creator = new SvgToPdf(title, templates, { state, freeComponentId: "belowmap", diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts deleted file mode 100644 index 5c3f6b5c68..0000000000 --- a/src/UI/Image/ImageUploadFlow.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Translations from "../i18n/Translations" -import Svg from "../../Svg" -import { Tag } from "../../Logic/Tags/Tag" -import BaseUIElement from "../BaseUIElement" -import Toggle from "../Input/Toggle" -import FileSelectorButton from "../Input/FileSelectorButton" -import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader" -import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { FixedUiElement } from "../Base/FixedUiElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import Loading from "../Base/Loading" -import { LoginToggle } from "../Popup/LoginButton" -import Constants from "../../Models/Constants" -import { SpecialVisualizationState } from "../SpecialVisualization" - -export class ImageUploadFlow extends Toggle { - private static readonly uploadCountsPerId = new Map>() - - constructor( - tagsSource: Store, - state: SpecialVisualizationState, - imagePrefix: string = "image", - text: string = undefined - ) { - const perId = ImageUploadFlow.uploadCountsPerId - const id = tagsSource.data.id - if (!perId.has(id)) { - perId.set(id, new UIEventSource(0)) - } - const uploadedCount = perId.get(id) - const uploader = new ImgurUploader(async (url) => { - // A file was uploaded - we add it to the tags of the object - - const tags = tagsSource.data - let key = imagePrefix - if (tags[imagePrefix] !== undefined) { - let freeIndex = 0 - while (tags[imagePrefix + ":" + freeIndex] !== undefined) { - freeIndex++ - } - key = imagePrefix + ":" + freeIndex - } - - await state.changes.applyAction( - new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, { - changeType: "add-image", - theme: state.layout.id, - }) - ) - console.log("Adding image:" + key, url) - uploadedCount.data++ - uploadedCount.ping() - }) - - const t = Translations.t.image - - let labelContent: BaseUIElement - if (text === undefined) { - labelContent = Translations.t.image.addPicture - .Clone() - .SetClass("block align-middle mt-1 ml-3 text-4xl ") - } else { - labelContent = new FixedUiElement(text).SetClass( - "block align-middle mt-1 ml-3 text-2xl " - ) - } - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - labelContent, - ]).SetClass("w-full flex justify-center items-center") - - const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0") - - const fileSelector = new FileSelectorButton(label, { - acceptType: "image/*", - allowMultiple: true, - labelClasses: "rounded-full border-2 border-black font-bold", - }) - /* fileSelector.SetClass( - "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" - ) - .SetStyle(" border-color: var(--foreground-color);")*/ - fileSelector.GetValue().addCallback((filelist) => { - if (filelist === undefined || filelist.length === 0) { - return - } - - for (var i = 0; i < filelist.length; i++) { - const sizeInBytes = filelist[i].size - console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes") - if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) { - alert( - Translations.t.image.toBig.Subs({ - actual_size: Math.floor(sizeInBytes / 1000000) + "MB", - max_size: uploader.maxFileSizeInMegabytes + "MB", - }).txt - ) - return - } - } - - const license = licenseStore?.data ?? "CC0" - - const tags = tagsSource.data - - const layout = state?.layout - let matchingLayer: LayerConfig = undefined - for (const layer of layout?.layers ?? []) { - if (layer.source.osmTags.matchesProperties(tags)) { - matchingLayer = layer - break - } - } - - const title = - matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement() - ?.textContent ?? - tags.name ?? - "https//osm.org/" + tags.id - const description = [ - "author:" + state.osmConnection.userDetails.data.name, - "license:" + license, - "osmid:" + tags.id, - ].join("\n") - - uploader.uploadMany(title, description, filelist) - }) - - const uploadFlow: BaseUIElement = new Combine([ - new VariableUiElement( - uploader.queue - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return new Loading(t.uploadingPicture).SetClass("alert") - } else { - return new Loading( - t.uploadingMultiple.Subs({ count: "" + l }) - ).SetClass("alert") - } - }) - ), - new VariableUiElement( - uploader.failed - .map((q) => q.length) - .map((l) => { - if (l == 0) { - return undefined - } - console.log(l) - return t.uploadFailed.SetClass("block alert") - }) - ), - new VariableUiElement( - uploadedCount.map((l) => { - if (l == 0) { - return undefined - } - if (l == 1) { - return t.uploadDone.Clone().SetClass("thanks block") - } - return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block") - }) - ), - - fileSelector, - new Combine([ - Translations.t.image.respectPrivacy, - new VariableUiElement( - licenseStore.map((license) => - Translations.t.image.currentLicense.Subs({ license }) - ) - ) - .onClick(() => { - console.log("Opening the license settings... ") - state.guistate.openUsersettings("picture-license") - }) - .SetClass("underline"), - ]).SetStyle("font-size:small;"), - ]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none") - - super( - new LoginToggle( - /*We can show the actual upload button!*/ - uploadFlow, - /* User not logged in*/ t.pleaseLogin.Clone(), - state - ), - undefined /* Nothing as the user badge is disabled*/, - state?.featureSwitchUserbadge - ) - } -} diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte new file mode 100644 index 0000000000..23408e778f --- /dev/null +++ b/src/UI/Image/UploadImage.svelte @@ -0,0 +1,77 @@ + + + + + + +
+ + + handleFiles(e.detail)}> +
+ + {#if image !== undefined} + + {:else} + + {/if} + {#if labelText} + {labelText} + {:else} + + {/if} +
+
+ +
+ +
diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte new file mode 100644 index 0000000000..0c1b6f7776 --- /dev/null +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -0,0 +1,67 @@ + + +{#if $uploadStarted == 1} + {#if $uploadFinished == 1 } + + {:else if $failed == 1} +
+ + + +
+ {:else if $retried == 1} + + + + {:else } + + + + {/if} +{:else if $uploadStarted > 1} + {#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0} + + {:else if $uploadFinished == 0} + + + + {:else if $uploadFinished > 0} + + + + {/if} + {#if $failed > 0} +
+ {#if failed === 1} + + {:else} + + + {/if} + + +
+ {/if} +{/if} diff --git a/src/UI/Input/FileSelectorButton.ts b/src/UI/Input/FileSelectorButton.ts deleted file mode 100644 index c3f56d297a..0000000000 --- a/src/UI/Input/FileSelectorButton.ts +++ /dev/null @@ -1,111 +0,0 @@ -import BaseUIElement from "../BaseUIElement" -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class FileSelectorButton extends InputElement { - private static _nextid = 0 - private readonly _value = new UIEventSource(undefined) - private readonly _label: BaseUIElement - private readonly _acceptType: string - private readonly allowMultiple: boolean - private readonly _labelClasses: string - - constructor( - label: BaseUIElement, - options?: { - acceptType: "image/*" | string - allowMultiple: true | boolean - labelClasses?: string - } - ) { - super() - this._label = label - this._acceptType = options?.acceptType ?? "image/*" - this._labelClasses = options?.labelClasses ?? "" - this.SetClass("block cursor-pointer") - label.SetClass("cursor-pointer") - this.allowMultiple = options?.allowMultiple ?? true - } - - GetValue(): UIEventSource { - return this._value - } - - IsValid(t: FileList): boolean { - return true - } - - protected InnerConstructElement(): HTMLElement { - const self = this - const el = document.createElement("form") - const label = document.createElement("label") - label.appendChild(this._label.ConstructElement()) - label.classList.add(...this._labelClasses.split(" ").filter((t) => t !== "")) - el.appendChild(label) - - const actualInputElement = document.createElement("input") - actualInputElement.style.cssText = "display:none" - actualInputElement.type = "file" - actualInputElement.accept = this._acceptType - actualInputElement.name = "picField" - actualInputElement.multiple = this.allowMultiple - actualInputElement.id = "fileselector" + FileSelectorButton._nextid - FileSelectorButton._nextid++ - - label.htmlFor = actualInputElement.id - - actualInputElement.onchange = () => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - } - - el.addEventListener("submit", (e) => { - if (actualInputElement.files !== null) { - self._value.setData(actualInputElement.files) - } - actualInputElement.classList.remove("glowing-shadow") - - e.preventDefault() - }) - - el.appendChild(actualInputElement) - - function setDrawAttention(isOn: boolean) { - if (isOn) { - label.classList.add("glowing-shadow") - } else { - label.classList.remove("glowing-shadow") - } - } - - el.addEventListener("dragover", (event) => { - event.stopPropagation() - event.preventDefault() - setDrawAttention(true) - // Style the drag-and-drop as a "copy file" operation. - event.dataTransfer.dropEffect = "copy" - }) - - window.document.addEventListener("dragenter", () => { - setDrawAttention(true) - }) - - window.document.addEventListener("dragend", () => { - setDrawAttention(false) - }) - - el.addEventListener("drop", (event) => { - event.stopPropagation() - event.preventDefault() - label.classList.remove("glowing-shadow") - const fileList = event.dataTransfer.files - this._value.setData(fileList) - }) - - return el - } -} diff --git a/src/UI/Input/Slider.ts b/src/UI/Input/Slider.ts deleted file mode 100644 index 9fce626a7b..0000000000 --- a/src/UI/Input/Slider.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -/** - * @deprecated - */ -export default class Slider extends InputElement { - private readonly _value: UIEventSource - private readonly min: number - private readonly max: number - private readonly step: number - private readonly vertical: boolean - - /** - * Constructs a slider input element for natural numbers - * @param min: the minimum value that is allowed, inclusive - * @param max: the max value that is allowed, inclusive - * @param options: value: injectable value; step: the step size of the slider - */ - constructor( - min: number, - max: number, - options?: { - value?: UIEventSource - step?: 1 | number - vertical?: false | boolean - } - ) { - super() - this.max = max - this.min = min - this._value = options?.value ?? new UIEventSource(min) - this.step = options?.step ?? 1 - this.vertical = options?.vertical ?? false - } - - GetValue(): UIEventSource { - return this._value - } - - protected InnerConstructElement(): HTMLElement { - const el = document.createElement("input") - el.type = "range" - el.min = "" + this.min - el.max = "" + this.max - el.step = "" + this.step - const valuestore = this._value - el.oninput = () => { - valuestore.setData(Number(el.value)) - } - if (this.vertical) { - el.classList.add("vertical") - el.setAttribute("orient", "vertical") // firefox only workaround... - } - valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data)) - return el - } - - IsValid(t: number): boolean { - return Math.round(t) == t && t >= this.min && t <= this.max - } -} diff --git a/src/UI/InputElement/Validators/FediverseValidator.ts b/src/UI/InputElement/Validators/FediverseValidator.ts index 11a410bc97..aa56528e3f 100644 --- a/src/UI/InputElement/Validators/FediverseValidator.ts +++ b/src/UI/InputElement/Validators/FediverseValidator.ts @@ -40,7 +40,7 @@ export default class FediverseValidator extends Validator { if (match) { const host = match[2] try { - const url = new URL("https://" + host) + new URL("https://" + host) return undefined } catch (e) { return Translations.t.validation.fediverse.invalidHost.Subs({ host }) diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 75f2d54d4a..3843108ceb 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -92,7 +92,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { - self.setBackground() + map.resize() self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) @@ -102,8 +102,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMinzoom(self.minzoom.data) self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) + self.setBackground() this.updateStores(true) }) + map.resize() self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) @@ -113,6 +115,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMinzoom(self.minzoom.data) self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) + self.setBackground() this.updateStores(true) map.on("moveend", () => this.updateStores()) map.on("click", (e) => { @@ -126,7 +129,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { }) }) - this.rasterLayer.addCallback((_) => + this.rasterLayer.addCallbackAndRun((_) => self.setBackground().catch((_) => { console.error("Could not set background") }) @@ -376,12 +379,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { } const background: RasterLayerProperties = this.rasterLayer?.data?.properties if (!background) { - console.error( - "Attempting to 'setBackground', but the background is", - background, - "for", - map.getCanvas() - ) return } if (this._currentRasterLayer === background.id) { @@ -408,7 +405,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { this.removeCurrentLayer(map) } else { // Make sure that the default maptiler style is loaded as it gives an overlay with roads - const maptiler = AvailableRasterLayers.maplibre.properties + const maptiler = AvailableRasterLayers.maptilerDefaultLayer.properties if (!map.getSource(maptiler.id)) { this.removeCurrentLayer(map) map.addSource(maptiler.id, MapLibreAdaptor.prepareWmsSource(maptiler)) @@ -423,7 +420,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { if (!map.getSource(background.id)) { map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) } - map.resize() if (!map.getLayer(background.id)) { map.addLayer( { @@ -436,7 +432,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { ) } await this.awaitStyleIsLoaded() - this.removeCurrentLayer(map) + if(this._currentRasterLayer !== background?.id){ + this.removeCurrentLayer(map) + } this._currentRasterLayer = background?.id } @@ -457,13 +455,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { if (!map) { return } - console.log("Rotation allowed:", allow) if (allow === false) { map.rotateTo(0, { duration: 0 }) map.setPitch(0) map.dragRotate.disable() + map.touchZoomRotate.disableRotation(); } else { map.dragRotate.enable() + map.touchZoomRotate.enableRotation(); } } diff --git a/src/UI/Map/MaplibreMap.svelte b/src/UI/Map/MaplibreMap.svelte index bb03350d28..f882b6eadb 100644 --- a/src/UI/Map/MaplibreMap.svelte +++ b/src/UI/Map/MaplibreMap.svelte @@ -24,7 +24,7 @@ writable({ lng: 0, lat: 0 }) export let zoom: Readable = writable(1) - const styleUrl = AvailableRasterLayers.maplibre.properties.url + const styleUrl = AvailableRasterLayers.maptilerDefaultLayer.properties.url let _map: Map onMount(() => { diff --git a/src/UI/PlantNet/PlantNet.svelte b/src/UI/PlantNet/PlantNet.svelte new file mode 100644 index 0000000000..b828de04b9 --- /dev/null +++ b/src/UI/PlantNet/PlantNet.svelte @@ -0,0 +1,123 @@ + + +
+ + {#if collapsedMode} + + {:else if $error !== undefined} + + {:else if $imageUrls.length === 0} + +
+ {collapsedMode = true}}> + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ {:else if selectedOption === undefined} + speciesSelected(species.detail)}> + {collapsedMode = true}}> + + + {:else if !done} +
+
+ + +
+
+ {selectedOption = undefined}}> + + + { done = true; onConfirm(selectedOption); }} > + + +
+
+ {:else} + + + {done = false; selectedOption = undefined}}> + + + {/if} +
+ + +
+ +
diff --git a/src/UI/PlantNet/PlantNetSpeciesList.svelte b/src/UI/PlantNet/PlantNetSpeciesList.svelte new file mode 100644 index 0000000000..5cb7341561 --- /dev/null +++ b/src/UI/PlantNet/PlantNetSpeciesList.svelte @@ -0,0 +1,37 @@ + + +{#if $options === undefined} + + + +{:else} +
+
+ + +
+

+ +

+ + + {#each $options as species} + + {/each} +
+{/if} diff --git a/src/UI/PlantNet/SpeciesButton.svelte b/src/UI/PlantNet/SpeciesButton.svelte new file mode 100644 index 0000000000..9e3a44b91b --- /dev/null +++ b/src/UI/PlantNet/SpeciesButton.svelte @@ -0,0 +1,54 @@ + + + dispatch("selected", $wikidataId)}> + {#if $wikidata === undefined} + + + + {:else} + new WikidataPreviewBox(wikidataId, + { imageStyle: "max-width: 8rem; width: unset; height: 8rem", + extraItems: [t.matchPercentage + .Subs({ match: Math.round(species.score * 100) }) + .SetClass("thanks w-fit self-center")] + }).SetClass("w-full")}> + {/if} + diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index b95fbb5a57..d89e94402e 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -3,109 +3,109 @@ * This component ties together all the steps that are needed to create a new point. * There are many subcomponents which help with that */ - import type { SpecialVisualizationState } from "../../SpecialVisualization" - import PresetList from "./PresetList.svelte" - import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig" - import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" - import Tr from "../../Base/Tr.svelte" - import SubtleButton from "../../Base/SubtleButton.svelte" - import FromHtml from "../../Base/FromHtml.svelte" - import Translations from "../../i18n/Translations.js" - import TagHint from "../TagHint.svelte" - import { And } from "../../../Logic/Tags/And.js" - import LoginToggle from "../../Base/LoginToggle.svelte" - import Constants from "../../../Models/Constants.js" - import FilteredLayer from "../../../Models/FilteredLayer" - import { Store, UIEventSource } from "../../../Logic/UIEventSource" - import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" - import LoginButton from "../../Base/LoginButton.svelte" - import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte" - import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" - import { OsmWay } from "../../../Logic/Osm/OsmObject" - import { Tag } from "../../../Logic/Tags/Tag" - import type { WayId } from "../../../Models/OsmFeature" - import Loading from "../../Base/Loading.svelte" - import type { GlobalFilter } from "../../../Models/GlobalFilter" - import { onDestroy } from "svelte" - import NextButton from "../../Base/NextButton.svelte" - import BackButton from "../../Base/BackButton.svelte" - import ToSvelte from "../../Base/ToSvelte.svelte" - import Svg from "../../../Svg" - import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" - import { twJoin } from "tailwind-merge" + import type { SpecialVisualizationState } from "../../SpecialVisualization"; + import PresetList from "./PresetList.svelte"; + import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"; + import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; + import Tr from "../../Base/Tr.svelte"; + import SubtleButton from "../../Base/SubtleButton.svelte"; + import FromHtml from "../../Base/FromHtml.svelte"; + import Translations from "../../i18n/Translations.js"; + import TagHint from "../TagHint.svelte"; + import { And } from "../../../Logic/Tags/And.js"; + import LoginToggle from "../../Base/LoginToggle.svelte"; + import Constants from "../../../Models/Constants.js"; + import FilteredLayer from "../../../Models/FilteredLayer"; + import { Store, UIEventSource } from "../../../Logic/UIEventSource"; + import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"; + import LoginButton from "../../Base/LoginButton.svelte"; + import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; + import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; + import { OsmWay } from "../../../Logic/Osm/OsmObject"; + import { Tag } from "../../../Logic/Tags/Tag"; + import type { WayId } from "../../../Models/OsmFeature"; + import Loading from "../../Base/Loading.svelte"; + import type { GlobalFilter } from "../../../Models/GlobalFilter"; + import { onDestroy } from "svelte"; + import NextButton from "../../Base/NextButton.svelte"; + import BackButton from "../../Base/BackButton.svelte"; + import ToSvelte from "../../Base/ToSvelte.svelte"; + import Svg from "../../../Svg"; + import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"; + import { twJoin } from "tailwind-merge"; - export let coordinate: { lon: number; lat: number } - export let state: SpecialVisualizationState + export let coordinate: { lon: number; lat: number }; + export let state: SpecialVisualizationState; let selectedPreset: { preset: PresetConfig layer: LayerConfig icon: string tags: Record - } = undefined - let checkedOfGlobalFilters: number = 0 - let confirmedCategory = false + } = undefined; + let checkedOfGlobalFilters: number = 0; + let confirmedCategory = false; $: if (selectedPreset === undefined) { - confirmedCategory = false - creating = false - checkedOfGlobalFilters = 0 + confirmedCategory = false; + creating = false; + checkedOfGlobalFilters = 0; } - let flayer: FilteredLayer = undefined - let layerIsDisplayed: UIEventSource | undefined = undefined - let layerHasFilters: Store | undefined = undefined - let globalFilter: UIEventSource = state.layerState.globalFilters - let _globalFilter: GlobalFilter[] = [] + let flayer: FilteredLayer = undefined; + let layerIsDisplayed: UIEventSource | undefined = undefined; + let layerHasFilters: Store | undefined = undefined; + let globalFilter: UIEventSource = state.layerState.globalFilters; + let _globalFilter: GlobalFilter[] = []; onDestroy( globalFilter.addCallbackAndRun((globalFilter) => { - console.log("Global filters are", globalFilter) - _globalFilter = globalFilter ?? [] + console.log("Global filters are", globalFilter); + _globalFilter = globalFilter ?? []; }) - ) + ); $: { - flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id) - layerIsDisplayed = flayer?.isDisplayed - layerHasFilters = flayer?.hasFilter + flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); + layerIsDisplayed = flayer?.isDisplayed; + layerHasFilters = flayer?.hasFilter; } - const t = Translations.t.general.add + const t = Translations.t.general.add; - const zoom = state.mapProperties.zoom + const zoom = state.mapProperties.zoom; - const isLoading = state.dataIsLoading - let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined) - let snappedToObject: UIEventSource = new UIEventSource(undefined) + const isLoading = state.dataIsLoading; + let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined); + let snappedToObject: UIEventSource = new UIEventSource(undefined); // Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map - let preciseInputIsTapped = false + let preciseInputIsTapped = false; - let creating = false + let creating = false; /** * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters. * Will delete the lastclick-location */ function abort() { - state.selectedElement.setData(undefined) + state.selectedElement.setData(undefined); // When aborted, we force the contributors to place the pin _again_ // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map - state.lastClickObject.features.setData([]) - preciseInputIsTapped = false + state.lastClickObject.features.setData([]); + preciseInputIsTapped = false; } async function confirm() { - creating = true - const location: { lon: number; lat: number } = preciseCoordinate.data - const snapTo: WayId | undefined = snappedToObject.data + creating = true; + const location: { lon: number; lat: number } = preciseCoordinate.data; + const snapTo: WayId | undefined = snappedToObject.data; const tags: Tag[] = selectedPreset.preset.tags.concat( ..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) - ) - console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags) + ); + console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); - let snapToWay: undefined | OsmWay = undefined + let snapToWay: undefined | OsmWay = undefined; if (snapTo !== undefined) { - const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0) + const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); if (downloaded !== "deleted") { - snapToWay = downloaded + snapToWay = downloaded; } } @@ -113,33 +113,42 @@ theme: state.layout?.id ?? "unkown", changeType: "create", snapOnto: snapToWay, - }) - await state.changes.applyAction(newElementAction) - state.newFeatures.features.ping() + reusePointWithinMeters: 1 + }); + await state.changes.applyAction(newElementAction); + state.newFeatures.features.ping(); // The 'changes' should have created a new point, which added this into the 'featureProperties' - const newId = newElementAction.newElementId - console.log("Applied pending changes, fetching store for", newId) - const tagsStore = state.featureProperties.getStore(newId) + const newId = newElementAction.newElementId; + console.log("Applied pending changes, fetching store for", newId); + const tagsStore = state.featureProperties.getStore(newId); + if (!tagsStore) { + console.error("Bug: no tagsStore found for", newId); + } { // Set some metainfo - const properties = tagsStore.data + const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this - delete properties["_referencing_ways"] - properties["_referencing_ways"] = `["${snapTo}"]` + delete properties["_referencing_ways"]; + properties["_referencing_ways"] = `["${snapTo}"]`; } - properties["_backend"] = state.osmConnection.Backend() - properties["_last_edit:timestamp"] = new Date().toISOString() - const userdetails = state.osmConnection.userDetails.data - properties["_last_edit:contributor"] = userdetails.name - properties["_last_edit:uid"] = "" + userdetails.uid - tagsStore.ping() + properties["_backend"] = state.osmConnection.Backend(); + properties["_last_edit:timestamp"] = new Date().toISOString(); + const userdetails = state.osmConnection.userDetails.data; + properties["_last_edit:contributor"] = userdetails.name; + properties["_last_edit:uid"] = "" + userdetails.uid; + tagsStore.ping(); } - const feature = state.indexedFeatures.featuresById.data.get(newId) - abort() - state.selectedLayer.setData(selectedPreset.layer) - state.selectedElement.setData(feature) - tagsStore.ping() + const feature = state.indexedFeatures.featuresById.data.get(newId); + console.log("Selecting feature", feature, "and opening their popup"); + abort(); + state.selectedLayer.setData(selectedPreset.layer); + state.selectedElement.setData(feature); + tagsStore.ping(); + } + + function confirmSync() { + confirm().then(_ => console.debug("New point successfully handled")).catch(e => console.error("Handling the new point went wrong due to", e)); } @@ -328,7 +337,7 @@ "absolute top-0 flex w-full justify-center p-12" )} > - +
diff --git a/src/UI/Popup/CreateNewNote.svelte b/src/UI/Popup/CreateNewNote.svelte index 4bdb92b23c..10eda741a2 100644 --- a/src/UI/Popup/CreateNewNote.svelte +++ b/src/UI/Popup/CreateNewNote.svelte @@ -62,8 +62,13 @@ state.newFeatures.features.data.push(feature) state.newFeatures.features.ping() state.selectedElement?.setData(feature) + if(state.featureProperties.trackFeature){ + state.featureProperties.trackFeature(feature) + } comment.setData("") created = true + state.selectedElement.setData(feature) + state.selectedLayer.setData(state.layerState.filteredLayers.get("note")) } diff --git a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte index 8eada0a7fb..2cbfb0850a 100644 --- a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte +++ b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte @@ -38,7 +38,6 @@ const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined let currentState: "start" | "confirm" | "applying" | "deleted" = "start" $: { - console.log("Current state is", currentState, $canBeDeleted, canBeDeletedReason) deleteAbility.CheckDeleteability(true) } @@ -55,7 +54,6 @@ let actionToTake: OsmChangeAction const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {})) const deleteReason = changedProperties[DeleteConfig.deleteReasonKey] - console.log("Deleting! Hard?:", canBeDeleted.data, deleteReason) if (deleteReason) { // This is a proper, hard deletion actionToTake = new DeleteAction( diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte index eeb3c19c7b..2aa280f986 100644 --- a/src/UI/Popup/LinkableImage.svelte +++ b/src/UI/Popup/LinkableImage.svelte @@ -6,7 +6,7 @@ import ToSvelte from "../Base/ToSvelte.svelte" import { AttributedImage } from "../Image/AttributedImage" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" - import LinkPicture from "../../Logic/Osm/Actions/LinkPicture" + import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" import { GeoOperations } from "../../Logic/GeoOperations" @@ -23,25 +23,24 @@ export let feature: Feature export let layer: LayerConfig - export let linkable = true - let isLinked = false + export let linkable = true; + let isLinked = Object.values(tags.data).some(v => image.pictureUrl === v); - const t = Translations.t.image.nearby - const c = [lon, lat] + const t = Translations.t.image.nearby; + const c = [lon, lat]; let attributedImage = new AttributedImage({ url: image.thumbUrl ?? image.pictureUrl, provider: AllImageProviders.byName(image.provider), - date: new Date(image.date), - }) - let distance = Math.round( - GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c) - ) + date: new Date(image.date) + }); + let distance = Math.round(GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)); + $: { const currentTags = tags.data const key = Object.keys(image.osmTags)[0] const url = image.osmTags[key] if (isLinked) { - const action = new LinkPicture(currentTags.id, key, url, currentTags, { + const action = new LinkImageAction(currentTags.id, key, url, currentTags, { theme: state.layout.id, changeType: "link-image", }) diff --git a/src/UI/Popup/NoteCommentElement.ts b/src/UI/Popup/NoteCommentElement.ts index c72244924c..4d4e59c7eb 100644 --- a/src/UI/Popup/NoteCommentElement.ts +++ b/src/UI/Popup/NoteCommentElement.ts @@ -41,7 +41,7 @@ export default class NoteCommentElement extends Combine { let userinfo = Stores.FromPromise( Utils.downloadJsonCached( - "https://www.openstreetmap.org/api/0.6/user/" + comment.uid, + "https://api.openstreetmap.org/api/0.6/user/" + comment.uid, 24 * 60 * 60 * 1000 ) ) @@ -56,7 +56,7 @@ export default class NoteCommentElement extends Combine { ) const htmlElement = document.createElement("div") - htmlElement.innerHTML = comment.html + htmlElement.innerHTML = Utils.purify(comment.html) const images = Array.from(htmlElement.getElementsByTagName("a")) .map((link) => link.href) .filter((link) => { diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index 198fc7a938..ed5a7acc4f 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -1,18 +1,13 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Toggle from "../Input/Toggle" -import Lazy from "../Base/Lazy" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" -import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch" import Wikidata from "../../Logic/Web/Wikidata" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" -import { SubtleButton } from "../Base/SubtleButton" -import Combine from "../Base/Combine" -import Svg from "../../Svg" -import Translations from "../i18n/Translations" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement" +import PlantNet from "../PlantNet/PlantNet.svelte" export class PlantNetDetectionViz implements SpecialVisualization { funcName = "plantnet_detection" @@ -37,45 +32,29 @@ export class PlantNetDetectionViz implements SpecialVisualization { imagePrefixes = [].concat(...args.map((a) => a.split(","))) } - const detect = new UIEventSource(false) - const toggle = new Toggle( - new Lazy(() => { - const allProvidedImages: Store = AllImageProviders.LoadImagesFor( - tags, - imagePrefixes - ) - const allImages: Store = allProvidedImages.map((pi) => - pi.map((pi) => pi.url) - ) - return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => { - selectedWikidata = Wikidata.ExtractKey(selectedWikidata) - const change = new ChangeTagAction( - tags.data.id, - new And([ - new Tag("species:wikidata", selectedWikidata), - new Tag("source:species:wikidata", "PlantNet.org AI"), - ]), - tags.data, - { - theme: state.layout.id, - changeType: "plantnet-ai-detection", - } - ) - await state.changes.applyAction(change) - }) - }), - new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() => - detect.setData(true) - ), - detect + const allProvidedImages: Store = AllImageProviders.LoadImagesFor( + tags, + imagePrefixes ) + const imageUrls: Store = allProvidedImages.map((pi) => pi.map((pi) => pi.url)) - return new Combine([ - toggle, - new Combine([ - Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"), - Translations.t.plantDetection.poweredByPlantnet, - ]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"), - ]).SetClass("flex flex-col") + async function applySpecies(selectedWikidata) { + selectedWikidata = Wikidata.ExtractKey(selectedWikidata) + const change = new ChangeTagAction( + tags.data.id, + new And([ + new Tag("species:wikidata", selectedWikidata), + new Tag("source:species:wikidata", "PlantNet.org AI"), + ]), + tags.data, + { + theme: state.layout.id, + changeType: "plantnet-ai-detection", + } + ) + await state.changes.applyAction(change) + } + + return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies }) } } diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 9e031f169b..d50d0efcdd 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -88,7 +88,6 @@ checkedMappings.push(unseenFreeformValues.length > 0) } } - console.log("Inited 'checkMappings' to", checkedMappings) if (confg.freeform?.key) { if (!confg.multiAnswer) { // Somehow, setting multi-answer freeform values is broken if this is not set diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 4cb3aeb02b..a4e00100b8 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -1,113 +1,118 @@ -import { Store, UIEventSource } from "../Logic/UIEventSource" -import BaseUIElement from "./BaseUIElement" -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { Changes } from "../Logic/Osm/Changes" -import { ExportableMap, MapProperties } from "../Models/MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Geometry, Point } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { MangroveIdentity } from "../Logic/Web/MangroveReviews" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { MenuState } from "../Models/MenuState" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" -import { RasterLayerPolygon } from "../Models/RasterLayers" +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import BaseUIElement from "./BaseUIElement"; +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { Changes } from "../Logic/Osm/Changes"; +import { ExportableMap, MapProperties } from "../Models/MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Geometry, Point } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { MenuState } from "../Models/MenuState"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import { RasterLayerPolygon } from "../Models/RasterLayers"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { OsmTags } from "../Models/OsmFeature"; /** * The state needed to render a special Visualisation. */ export interface SpecialVisualizationState { - readonly guistate: MenuState - readonly layout: LayoutConfig - readonly featureSwitches: FeatureSwitchState + readonly guistate: MenuState; + readonly layout: LayoutConfig; + readonly featureSwitches: FeatureSwitchState; - readonly layerState: LayerState - readonly featureProperties: { getStore(id: string): UIEventSource> } + readonly layerState: LayerState; + readonly featureProperties: { getStore(id: string): UIEventSource>, trackFeature?(feature: { properties: OsmTags }) }; - readonly indexedFeatures: IndexedFeatureSource + readonly indexedFeatures: IndexedFeatureSource; - /** - * Some features will create a new element that should be displayed. - * These can be injected by appending them to this featuresource (and pinging it) - */ - readonly newFeatures: WritableFeatureSource + /** + * Some features will create a new element that should be displayed. + * These can be injected by appending them to this featuresource (and pinging it) + */ + readonly newFeatures: WritableFeatureSource; - readonly historicalUserLocations: WritableFeatureSource> + readonly historicalUserLocations: WritableFeatureSource>; - readonly osmConnection: OsmConnection - readonly featureSwitchUserbadge: Store - readonly featureSwitchIsTesting: Store - readonly changes: Changes - readonly osmObjectDownloader: OsmObjectDownloader - /** - * State of the main map - */ - readonly mapProperties: MapProperties & ExportableMap + readonly osmConnection: OsmConnection; + readonly featureSwitchUserbadge: Store; + readonly featureSwitchIsTesting: Store; + readonly changes: Changes; + readonly osmObjectDownloader: OsmObjectDownloader; + /** + * State of the main map + */ + readonly mapProperties: MapProperties & ExportableMap; - readonly selectedElement: UIEventSource - /** - * Works together with 'selectedElement' to indicate what properties should be displayed - */ - readonly selectedLayer: UIEventSource - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> + readonly selectedElement: UIEventSource; + /** + * Works together with 'selectedElement' to indicate what properties should be displayed + */ + readonly selectedLayer: UIEventSource; + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; - /** - * If data is currently being fetched from external sources - */ - readonly dataIsLoading: Store - /** - * Only needed for 'ReplaceGeometryAction' - */ - readonly fullNodeDatabase?: FullNodeDatabaseSource + /** + * If data is currently being fetched from external sources + */ + readonly dataIsLoading: Store; + /** + * Only needed for 'ReplaceGeometryAction' + */ + readonly fullNodeDatabase?: FullNodeDatabaseSource; - readonly perLayer: ReadonlyMap - readonly userRelatedState: { - readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> - readonly mangroveIdentity: MangroveIdentity - readonly showAllQuestionsAtOnce: UIEventSource - readonly preferencesAsTags: Store> - readonly language: UIEventSource - } - readonly lastClickObject: WritableFeatureSource + readonly perLayer: ReadonlyMap; + readonly userRelatedState: { + readonly imageLicense: UIEventSource; + readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> + readonly mangroveIdentity: MangroveIdentity + readonly showAllQuestionsAtOnce: UIEventSource + readonly preferencesAsTags: Store> + readonly language: UIEventSource + }; + readonly lastClickObject: WritableFeatureSource; - readonly availableLayers: Store + readonly availableLayers: Store; + + readonly imageUploadManager: ImageUploadManager; } export interface SpecialVisualization { - readonly funcName: string - readonly docs: string | BaseUIElement - readonly example?: string + readonly funcName: string; + readonly docs: string | BaseUIElement; + readonly example?: string; - /** - * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included - */ - readonly needsNodeDatabase?: boolean - readonly args: { - name: string - defaultValue?: string - doc: string - required?: false | boolean - }[] - readonly getLayerDependencies?: (argument: string[]) => string[] + /** + * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included + */ + readonly needsNodeDatabase?: boolean; + readonly args: { + name: string + defaultValue?: string + doc: string + required?: false | boolean + }[]; + readonly getLayerDependencies?: (argument: string[]) => string[]; - structuredExamples?(): { feature: Feature>; args: string[] }[] + structuredExamples?(): { feature: Feature>; args: string[] }[]; - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement; } export type RenderingSpecification = - | string - | { - func: SpecialVisualization - args: string[] - style: string - } + | string + | { + func: SpecialVisualization + args: string[] + style: string +} diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index a1803e5390..23d1b57c8b 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,79 +1,71 @@ -import Combine from "./Base/Combine" -import { FixedUiElement } from "./Base/FixedUiElement" -import BaseUIElement from "./BaseUIElement" -import Title from "./Base/Title" -import Table from "./Base/Table" -import { - RenderingSpecification, - SpecialVisualization, - SpecialVisualizationState, -} from "./SpecialVisualization" -import { HistogramViz } from "./Popup/HistogramViz" -import { MinimapViz } from "./Popup/MinimapViz" -import { ShareLinkViz } from "./Popup/ShareLinkViz" -import { UploadToOsmViz } from "./Popup/UploadToOsmViz" -import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" -import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" -import TagApplyButton from "./Popup/TagApplyButton" -import { CloseNoteButton } from "./Popup/CloseNoteButton" -import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" -import AllTagsPanel from "./Popup/AllTagsPanel.svelte" -import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" -import { ImageCarousel } from "./Image/ImageCarousel" -import { ImageUploadFlow } from "./Image/ImageUploadFlow" -import { VariableUiElement } from "./Base/VariableUIElement" -import { Utils } from "../Utils" -import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" -import { Translation } from "./i18n/Translation" -import Translations from "./i18n/Translations" -import ReviewForm from "./Reviews/ReviewForm" -import ReviewElement from "./Reviews/ReviewElement" -import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" -import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" -import { SubtleButton } from "./Base/SubtleButton" -import Svg from "../Svg" -import NoteCommentElement from "./Popup/NoteCommentElement" -import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" -import FileSelectorButton from "./Input/FileSelectorButton" -import { LoginToggle } from "./Popup/LoginButton" -import Toggle from "./Input/Toggle" -import { SubstitutedTranslation } from "./SubstitutedTranslation" -import List from "./Base/List" -import StatisticsPanel from "./BigComponents/StatisticsPanel" -import AutoApplyButton from "./Popup/AutoApplyButton" -import { LanguageElement } from "./Popup/LanguageElement" -import FeatureReviews from "../Logic/Web/MangroveReviews" -import Maproulette from "../Logic/Maproulette" -import SvelteUIElement from "./Base/SvelteUIElement" -import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" -import QuestionViz from "./Popup/QuestionViz" -import { Feature, Point } from "geojson" -import { GeoOperations } from "../Logic/GeoOperations" -import CreateNewNote from "./Popup/CreateNewNote.svelte" -import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" -import UserProfile from "./BigComponents/UserProfile.svelte" -import LanguagePicker from "./LanguagePicker" -import Link from "./Base/Link" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import { OsmTags, WayId } from "../Models/OsmFeature" -import MoveWizard from "./Popup/MoveWizard" -import SplitRoadWizard from "./Popup/SplitRoadWizard" -import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" -import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" -import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" -import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" -import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" -import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" -import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" -import { OpenJosm } from "./BigComponents/OpenJosm" -import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" -import FediverseValidator from "./InputElement/Validators/FediverseValidator" -import SendEmail from "./Popup/SendEmail.svelte" -import NearbyImages from "./Popup/NearbyImages.svelte" -import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" +import Combine from "./Base/Combine"; +import { FixedUiElement } from "./Base/FixedUiElement"; +import BaseUIElement from "./BaseUIElement"; +import Title from "./Base/Title"; +import Table from "./Base/Table"; +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; +import { HistogramViz } from "./Popup/HistogramViz"; +import { MinimapViz } from "./Popup/MinimapViz"; +import { ShareLinkViz } from "./Popup/ShareLinkViz"; +import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; +import { MultiApplyViz } from "./Popup/MultiApplyViz"; +import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; +import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; +import TagApplyButton from "./Popup/TagApplyButton"; +import { CloseNoteButton } from "./Popup/CloseNoteButton"; +import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; +import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; +import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; +import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; +import { ImageCarousel } from "./Image/ImageCarousel"; +import { VariableUiElement } from "./Base/VariableUIElement"; +import { Utils } from "../Utils"; +import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; +import { Translation } from "./i18n/Translation"; +import Translations from "./i18n/Translations"; +import ReviewForm from "./Reviews/ReviewForm"; +import ReviewElement from "./Reviews/ReviewElement"; +import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; +import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; +import { SubtleButton } from "./Base/SubtleButton"; +import Svg from "../Svg"; +import NoteCommentElement from "./Popup/NoteCommentElement"; +import { SubstitutedTranslation } from "./SubstitutedTranslation"; +import List from "./Base/List"; +import StatisticsPanel from "./BigComponents/StatisticsPanel"; +import AutoApplyButton from "./Popup/AutoApplyButton"; +import { LanguageElement } from "./Popup/LanguageElement"; +import FeatureReviews from "../Logic/Web/MangroveReviews"; +import Maproulette from "../Logic/Maproulette"; +import SvelteUIElement from "./Base/SvelteUIElement"; +import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; +import QuestionViz from "./Popup/QuestionViz"; +import { Feature, Point } from "geojson"; +import { GeoOperations } from "../Logic/GeoOperations"; +import CreateNewNote from "./Popup/CreateNewNote.svelte"; +import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; +import UserProfile from "./BigComponents/UserProfile.svelte"; +import LanguagePicker from "./LanguagePicker"; +import Link from "./Base/Link"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; +import { OsmTags, WayId } from "../Models/OsmFeature"; +import MoveWizard from "./Popup/MoveWizard"; +import SplitRoadWizard from "./Popup/SplitRoadWizard"; +import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; +import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; +import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; +import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; +import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; +import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; +import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; +import { OpenJosm } from "./BigComponents/OpenJosm"; +import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; +import FediverseValidator from "./InputElement/Validators/FediverseValidator"; +import SendEmail from "./Popup/SendEmail.svelte"; +import NearbyImages from "./Popup/NearbyImages.svelte"; +import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; +import UploadImage from "./Image/UploadImage.svelte"; class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -272,6 +264,7 @@ export default class SpecialVisualizations { SpecialVisualizations.specialVisualizations .map((sp) => sp.funcName + "()") .join(", ") + } } @@ -538,7 +531,7 @@ export default class SpecialVisualizations { const keys = args[0].split(";").map((k) => k.trim()) const wikiIds: Store = tagsSource.map((tags) => { const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") - return tags[key]?.split(";")?.map((id) => id.trim()) + return tags[key]?.split(";")?.map((id) => id.trim()) ?? [] }) return new SvelteUIElement(WikipediaPanel, { wikiIds, @@ -616,16 +609,18 @@ export default class SpecialVisualizations { { name: "image-key", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", - defaultValue: "image", + required: false }, { name: "label", doc: "The text to show on the button", - defaultValue: "Add image", + required: false }, ], constr: (state, tags, args) => { - return new ImageUploadFlow(tags, state, args[0], args[1]) + return new SvelteUIElement(UploadImage, { + state,tags, labelText: args[1], image: args[0] + }) }, }, { @@ -864,43 +859,11 @@ export default class SpecialVisualizations { }, ], constr: (state, tags, args) => { - const isUploading = new UIEventSource(false) - const t = Translations.t.notes const id = tags.data[args[0] ?? "id"] - - const uploader = new ImgurUploader(async (url) => { - isUploading.setData(false) - await state.osmConnection.addCommentToNote(id, url) - NoteCommentElement.addCommentTo(url, tags, state) - }) - - const label = new Combine([ - Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "), - Translations.t.image.addPicture, - ]).SetClass( - "p-2 border-4 border-black rounded-full font-bold h-full align-center w-full flex justify-center" - ) - - const fileSelector = new FileSelectorButton(label) - fileSelector.GetValue().addCallback((filelist) => { - isUploading.setData(true) - uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist) - }) - const ti = Translations.t.image - const uploadPanel = new Combine([ - fileSelector, - ti.respectPrivacy.SetClass("text-sm"), - ]).SetClass("flex flex-col") - return new LoginToggle( - new Toggle( - Translations.t.image.uploadingPicture.SetClass("alert"), - uploadPanel, - isUploading - ), - t.loginToAddPicture, - state - ) - }, + tags = state.featureProperties.getStore(id) + console.log("Id is", id) + return new SvelteUIElement(UploadImage, {state, tags}) + } }, { funcName: "title", @@ -1171,7 +1134,7 @@ export default class SpecialVisualizations { new Link( Utils.SubstituteKeys(text, tags), Utils.SubstituteKeys(href, tags), - download === undefined, + download === undefined && !href.startsWith("#"), Utils.SubstituteKeys(download, tags) ).SetClass(classnames) ) diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte index cd44cf3289..452ebb378d 100644 --- a/src/UI/StylesheetTestGui.svelte +++ b/src/UI/StylesheetTestGui.svelte @@ -29,6 +29,10 @@ areas, where some buttons might appear.

+
+ Highly interactive area (mostly: active question) +
+
-
Below
+
Below