Compare commits

..

12 commits

Author SHA1 Message Date
db9c5b233f Fix: fix studio 2025-08-15 20:17:50 +02:00
a65f365485 Merge upstream 2025-08-15 20:11:35 +02:00
e80b232dd5 chore(release): 0.55.2 2025-08-15 20:10:42 +02:00
0452c84520 Fix: fix #2495 2025-08-15 20:10:36 +02:00
b8f6cfb8ea Merge branch 'master' of source.mapcomplete.org:mapcomplete/mapcomplete 2025-08-15 16:00:37 +02:00
23e5849b61 App: add google play badge on download page, update text 2025-08-15 15:59:29 +02:00
Weblate
3c8ec10139 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/
2025-08-15 10:36:04 +00:00
Supaplex
b102c690d1 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 7.9% (367 of 4639 strings)

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/zh_Hant/
2025-08-15 10:36:01 +00:00
Lukáš Jelínek
1cbf9a71ac Translated using Weblate (Czech)
Currently translated at 100.0% (4639 of 4639 strings)

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/cs/
2025-08-15 10:36:01 +00:00
d9815cae46 chore(release): 0.55.2 2025-08-15 02:31:10 +02:00
e2fc678ec9 Fix: fix crash in collection times picker 2025-08-15 02:30:12 +02:00
b61bc6de7c Fix: fix statistics 2025-08-14 15:14:47 +02:00
71 changed files with 868 additions and 760 deletions

View file

@ -2,6 +2,15 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.55.2](https://source.mapcomplete.org/MapComplete/MapComplete/compare/v0.55.1...v0.55.2) (2025-08-15)
### Bug Fixes
* fix [#2495](https://source.mapcomplete.org/MapComplete/MapComplete/issues/2495) ([0452c84](https://source.mapcomplete.org/MapComplete/MapComplete/commits/0452c84520e564a18164726faee0e5447ec7b602))
* fix crash in collection times picker ([e2fc678](https://source.mapcomplete.org/MapComplete/MapComplete/commits/e2fc678ec9e694578927f0c7aeab86a0298041d4))
* fix statistics ([b61bc6d](https://source.mapcomplete.org/MapComplete/MapComplete/commits/b61bc6de7cb66fa5fdadb21067f80fb29cd5ecc9))
### [0.55.1](https://source.mapcomplete.org/MapComplete/MapComplete/compare/v0.54.7...v0.55.1) (2025-08-13) ### [0.55.1](https://source.mapcomplete.org/MapComplete/MapComplete/compare/v0.54.7...v0.55.1) (2025-08-13)

View file

@ -9,8 +9,9 @@
const t = Translations.t.app const t = Translations.t.app
const lng = Locale.language const lng = Locale.language
let fdroid = t.downloadOnFDroid.current let fdroid = t.downloadOnFDroid.current
let googleplay = t.getOnGoogle.current
let supportedVersions: { version: number; codename: string } = [ let supportedVersions: { version: number; codename: string }[] = [
{ version: 9, codename: "pie" }, { version: 9, codename: "pie" },
{ version: 10, codename: "quince-tart" }, { version: 10, codename: "quince-tart" },
{ version: 11, codename: "red-velvet-cake" }, { version: 11, codename: "red-velvet-cake" },
@ -80,6 +81,15 @@
/> />
</a> </a>
<a href="https://play.google.com/store/apps/details?id=org.mapcomplete">
<img
src={`./googleplay.svg`}
alt={$googleplay}
class="m-2"
style="width: 17rem"
/>
</a>
<a <a
rel="noopener" rel="noopener"
href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://source.mapcomplete.org/MapComplete/android-wrapper/releases" href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://source.mapcomplete.org/MapComplete/android-wrapper/releases"

View file

@ -8,6 +8,8 @@ npm run build:vite:app-landing
mkdir to_upload mkdir to_upload
mv dist/app/* to_upload/ mv dist/app/* to_upload/
cp *.png to_upload/ cp *.png to_upload/
cp *.svg to_upload/
cp -r .well-known/ to_upload/ cp -r .well-known/ to_upload/
mkdir -p to_upload/assets/fonts mkdir -p to_upload/assets/fonts

193
app/googleplay.svg Normal file
View file

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="800"
height="237.03703"
viewBox="0 -47.5 135 39.999999"
id="Layer_1"
version="1.1"
sodipodi:docname="google-play-badge-logo-svgrepo-com.svg"
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs23" />
<sodipodi:namedview
id="namedview23"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.0062124"
inkscape:cx="133.58506"
inkscape:cy="137.8219"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<style
id="style1">.st0{fill:#a6a6a6}.st1{stroke:#ffffff;stroke-width:.2;stroke-miterlimit:10}.st1,.st2{fill:#fff}.st3{fill:url(#SVGID_1_)}.st4{fill:url(#SVGID_2_)}.st5{fill:url(#SVGID_3_)}.st6{fill:url(#SVGID_4_)}.st7,.st8,.st9{opacity:.2;enable-background:new}.st8,.st9{opacity:.12}.st9{opacity:.25;fill:#fff}</style>
<path
d="M 130,-7.5 H 5 c -2.8,0 -5,-2.2 -5,-5 v -30 c 0,-2.8 2.2,-5 5,-5 h 125 c 2.8,0 5,2.2 5,5 v 30 c 0,2.8 -2.2,5 -5,5 z"
id="path1" />
<path
class="st0"
d="m 130,-46.7 c 2.3,0 4.2,1.9 4.2,4.2 v 30 c 0,2.3 -1.9,4.2 -4.2,4.2 H 5 c -2.3,0 -4.2,-1.9 -4.2,-4.2 v -30 c 0,-2.3 1.9,-4.2 4.2,-4.2 h 125 m 0,-0.8 H 5 c -2.8,0 -5,2.3 -5,5 v 30 c 0,2.8 2.2,5 5,5 h 125 c 2.8,0 5,-2.2 5,-5 v -30 c 0,-2.7 -2.2,-5 -5,-5 z"
id="path2" />
<path
class="st1"
d="m 47.4,-37.3 c 0,0.8 -0.2,1.5 -0.7,2 -0.6,0.6 -1.3,0.9 -2.2,0.9 -0.9,0 -1.6,-0.3 -2.2,-0.9 -0.6,-0.6 -0.9,-1.3 -0.9,-2.2 0,-0.9 0.3,-1.6 0.9,-2.2 0.6,-0.6 1.3,-0.9 2.2,-0.9 0.4,0 0.8,0.1 1.2,0.3 0.4,0.2 0.7,0.4 0.9,0.7 l -0.5,0.5 c -0.4,-0.5 -0.9,-0.7 -1.6,-0.7 -0.6,0 -1.2,0.2 -1.6,0.7 -0.5,0.4 -0.7,1 -0.7,1.7 0,0.7 0.2,1.3 0.7,1.7 0.5,0.4 1,0.7 1.6,0.7 0.7,0 1.2,-0.2 1.7,-0.7 0.3,-0.3 0.5,-0.7 0.5,-1.2 h -2.2 v -0.8 h 2.9 z m 4.6,-2.5 h -2.7 v 1.9 h 2.5 v 0.7 h -2.5 v 1.9 H 52 v 0.8 h -3.5 v -6 H 52 Z m 3.3,5.3 h -0.8 v -5.3 h -1.7 v -0.7 H 57 v 0.7 h -1.7 z m 4.6,0 v -6 h 0.8 v 6 z m 4.2,0 h -0.8 v -5.3 h -1.7 v -0.7 h 4.1 v 0.7 H 64 v 5.3 z m 9.5,-0.8 c -0.6,0.6 -1.3,0.9 -2.2,0.9 -0.9,0 -1.6,-0.3 -2.2,-0.9 -0.6,-0.6 -0.9,-1.3 -0.9,-2.2 0,-0.9 0.3,-1.6 0.9,-2.2 0.6,-0.6 1.3,-0.9 2.2,-0.9 0.9,0 1.6,0.3 2.2,0.9 0.6,0.6 0.9,1.3 0.9,2.2 0,0.9 -0.3,1.6 -0.9,2.2 z m -3.8,-0.5 c 0.4,0.4 1,0.7 1.6,0.7 0.6,0 1.2,-0.2 1.6,-0.7 0.4,-0.4 0.7,-1 0.7,-1.7 0,-0.7 -0.2,-1.3 -0.7,-1.7 -0.4,-0.4 -1,-0.7 -1.6,-0.7 -0.6,0 -1.2,0.2 -1.6,0.7 -0.4,0.4 -0.7,1 -0.7,1.7 0,0.7 0.2,1.3 0.7,1.7 z m 5.8,1.3 v -6 h 0.9 l 2.9,4.7 v -4.7 h 0.8 v 6 h -0.8 l -3.1,-4.9 v 4.9 z"
id="path3" />
<path
class="st2"
d="m 68.1,-25.7 c -2.4,0 -4.3,1.8 -4.3,4.3 0,2.4 1.9,4.3 4.3,4.3 2.4,0 4.3,-1.8 4.3,-4.3 0,-2.6 -1.9,-4.3 -4.3,-4.3 z m 0,6.8 c -1.3,0 -2.4,-1.1 -2.4,-2.6 0,-1.5 1.1,-2.6 2.4,-2.6 1.3,0 2.4,1 2.4,2.6 0,1.5 -1.1,2.6 -2.4,2.6 z m -9.3,-6.8 c -2.4,0 -4.3,1.8 -4.3,4.3 0,2.4 1.9,4.3 4.3,4.3 2.4,0 4.3,-1.8 4.3,-4.3 0,-2.6 -1.9,-4.3 -4.3,-4.3 z m 0,6.8 c -1.3,0 -2.4,-1.1 -2.4,-2.6 0,-1.5 1.1,-2.6 2.4,-2.6 1.3,0 2.4,1 2.4,2.6 0,1.5 -1.1,2.6 -2.4,2.6 z m -11.1,-5.5 v 1.8 H 52 c -0.1,1 -0.5,1.8 -1,2.3 -0.6,0.6 -1.6,1.3 -3.3,1.3 -2.7,0 -4.7,-2.1 -4.7,-4.8 0,-2.7 2.1,-4.8 4.7,-4.8 1.4,0 2.5,0.6 3.3,1.3 l 1.3,-1.3 c -1.1,-1 -2.5,-1.8 -4.5,-1.8 -3.6,0 -6.7,3 -6.7,6.6 0,3.6 3.1,6.6 6.7,6.6 2,0 3.4,-0.6 4.6,-1.9 1.2,-1.2 1.6,-2.9 1.6,-4.2 0,-0.4 0,-0.8 -0.1,-1.1 z m 45.4,1.4 c -0.4,-1 -1.4,-2.7 -3.6,-2.7 -2.2,0 -4,1.7 -4,4.3 0,2.4 1.8,4.3 4.2,4.3 1.9,0 3.1,-1.2 3.5,-1.9 l -1.4,-1 c -0.5,0.7 -1.1,1.2 -2.1,1.2 -1,0 -1.6,-0.4 -2.1,-1.3 l 5.7,-2.4 z m -5.8,1.4 c 0,-1.6 1.3,-2.5 2.2,-2.5 0.7,0 1.4,0.4 1.6,0.9 z m -4.7,4.1 h 1.9 V -30 h -1.9 z m -3,-7.3 c -0.5,-0.5 -1.3,-1 -2.3,-1 -2.1,0 -4.1,1.9 -4.1,4.3 0,2.4 1.9,4.2 4.1,4.2 1,0 1.8,-0.5 2.2,-1 h 0.1 v 0.6 c 0,1.6 -0.9,2.5 -2.3,2.5 -1.1,0 -1.9,-0.8 -2.1,-1.5 l -1.6,0.7 c 0.5,1.1 1.7,2.5 3.8,2.5 2.2,0 4,-1.3 4,-4.4 v -7.6 h -1.8 z m -2.2,5.9 c -1.3,0 -2.4,-1.1 -2.4,-2.6 0,-1.5 1.1,-2.6 2.4,-2.6 1.3,0 2.3,1.1 2.3,2.6 0,1.5 -1,2.6 -2.3,2.6 z M 101.8,-30 h -4.5 v 12.5 h 1.9 v -4.7 h 2.6 c 2.1,0 4.1,-1.5 4.1,-3.9 0,-2.4 -2,-3.9 -4.1,-3.9 z m 0.1,6 h -2.7 v -4.3 h 2.7 c 1.4,0 2.2,1.2 2.2,2.1 -0.1,1.1 -0.9,2.2 -2.2,2.2 z m 11.5,-1.8 c -1.4,0 -2.8,0.6 -3.3,1.9 l 1.7,0.7 c 0.4,-0.7 1,-0.9 1.7,-0.9 1,0 1.9,0.6 2,1.6 v 0.1 c -0.3,-0.2 -1.1,-0.5 -1.9,-0.5 -1.8,0 -3.6,1 -3.6,2.8 0,1.7 1.5,2.8 3.1,2.8 1.3,0 1.9,-0.6 2.4,-1.2 h 0.1 v 1 h 1.8 v -4.8 c -0.2,-2.2 -1.9,-3.5 -4,-3.5 z m -0.2,6.9 c -0.6,0 -1.5,-0.3 -1.5,-1.1 0,-1 1.1,-1.3 2,-1.3 0.8,0 1.2,0.2 1.7,0.4 -0.2,1.2 -1.2,2 -2.2,2 z m 10.5,-6.6 -2.1,5.4 h -0.1 l -2.2,-5.4 h -2 l 3.3,7.6 -1.9,4.2 h 1.9 l 5.1,-11.8 z m -16.8,8 h 1.9 V -30 h -1.9 z"
id="path4" />
<g
id="g23"
transform="translate(0,-47.5)">
<linearGradient
id="SVGID_1_"
gradientUnits="userSpaceOnUse"
x1="21.799999"
y1="33.290001"
x2="5.0170002"
y2="16.507999"
gradientTransform="matrix(1,0,0,-1,0,42)">
<stop
offset="0"
stop-color="#00a0ff"
id="stop4" />
<stop
offset=".007"
stop-color="#00a1ff"
id="stop5" />
<stop
offset=".26"
stop-color="#00beff"
id="stop6" />
<stop
offset=".512"
stop-color="#00d2ff"
id="stop7" />
<stop
offset=".76"
stop-color="#00dfff"
id="stop8" />
<stop
offset="1"
stop-color="#00e3ff"
id="stop9" />
</linearGradient>
<path
class="st3"
d="M 10.4,7.5 C 10.1,7.8 10,8.3 10,8.9 V 31 c 0,0.6 0.2,1.1 0.5,1.4 L 10.6,32.5 23,20.1 v -0.2 z"
id="path9"
style="fill:url(#SVGID_1_)" />
<linearGradient
id="SVGID_2_"
gradientUnits="userSpaceOnUse"
x1="33.834"
y1="21.999001"
x2="9.6370001"
y2="21.999001"
gradientTransform="matrix(1,0,0,-1,0,42)">
<stop
offset="0"
stop-color="#ffe000"
id="stop10" />
<stop
offset=".409"
stop-color="#ffbd00"
id="stop11" />
<stop
offset=".775"
stop-color="orange"
id="stop12" />
<stop
offset="1"
stop-color="#ff9c00"
id="stop13" />
</linearGradient>
<path
class="st4"
d="m 27,24.3 -4.1,-4.1 v -0.3 l 4.1,-4.1 0.1,0.1 4.9,2.8 c 1.4,0.8 1.4,2.1 0,2.9 z"
id="path13"
style="fill:url(#SVGID_2_)" />
<linearGradient
id="SVGID_3_"
gradientUnits="userSpaceOnUse"
x1="24.827"
y1="19.704"
x2="2.069"
y2="-3.0539999"
gradientTransform="matrix(1,0,0,-1,0,42)">
<stop
offset="0"
stop-color="#ff3a44"
id="stop14" />
<stop
offset="1"
stop-color="#c31162"
id="stop15" />
</linearGradient>
<path
class="st5"
d="M 27.1,24.2 22.9,20 10.4,32.5 c 0.5,0.5 1.2,0.5 2.1,0.1 l 14.6,-8.4"
id="path15"
style="fill:url(#SVGID_3_)" />
<linearGradient
id="SVGID_4_"
gradientUnits="userSpaceOnUse"
x1="7.2969999"
y1="41.824001"
x2="17.459999"
y2="31.660999"
gradientTransform="matrix(1,0,0,-1,0,42)">
<stop
offset="0"
stop-color="#32a071"
id="stop16" />
<stop
offset=".069"
stop-color="#2da771"
id="stop17" />
<stop
offset=".476"
stop-color="#15cf74"
id="stop18" />
<stop
offset=".801"
stop-color="#06e775"
id="stop19" />
<stop
offset="1"
stop-color="#00f076"
id="stop20" />
</linearGradient>
<path
class="st6"
d="M 27.1,15.8 12.5,7.5 C 11.6,7 10.9,7.1 10.4,7.6 L 22.9,20 Z"
id="path20"
style="fill:url(#SVGID_4_)" />
<path
class="st7"
d="m 27,24.1 -14.5,8.2 c -0.8,0.5 -1.5,0.4 -2,0 l -0.1,0.1 0.1,0.1 c 0.5,0.4 1.2,0.5 2,0 z"
id="path21" />
<path
class="st8"
d="M 10.4,32.3 C 10.1,32 10,31.5 10,30.9 V 31 c 0,0.6 0.2,1.1 0.5,1.4 v -0.1 z m 21.6,-11 -5,2.8 0.1,0.1 4.9,-2.8 c 0.7,-0.4 1,-0.9 1,-1.4 0,0.5 -0.4,0.9 -1,1.3 z"
id="path22" />
<path
class="st9"
d="M 12.5,7.6 32,18.7 c 0.6,0.4 1,0.8 1,1.3 0,-0.5 -0.3,-1 -1,-1.4 L 12.5,7.5 C 11.1,6.7 10,7.3 10,8.9 V 9 c 0,-1.5 1.1,-2.2 2.5,-1.4 z"
id="path23" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -12,16 +12,9 @@
"hidden" "hidden"
], ],
"render": { "render": {
"special": { "en": "Dietary options",
"type": "show_icons", "cs": "Dietní možnosti",
"labels": "diets_content", "nl": "Dieetopties"
"class": "inline-flex float-right"
},
"before": {
"en": "Dietary options",
"cs": "Dietní možnosti",
"nl": "Dieetopties"
}
} }
}, },
{ {

View file

@ -131,8 +131,7 @@
}, },
"tags": [ "tags": [
"historic=locomotive" "historic=locomotive"
], ]
"snapToLayer": ["railway"]
}, },
{ {
"title": { "title": {
@ -145,9 +144,7 @@
}, },
"tags": [ "tags": [
"historic=railway_car" "historic=railway_car"
], ]
"snapToLayer": ["railway"]
}, },
{ {
"title": { "title": {
@ -160,9 +157,7 @@
}, },
"tags": [ "tags": [
"historic=minecart" "historic=minecart"
], ]
"snapToLayer": ["railway"]
} }
], ],
"tagRenderings": [ "tagRenderings": [

View file

@ -430,10 +430,10 @@
} }
}, },
{ {
"condition": "_favourite=yes", "id": "favourite_icon",
"description": "Only for rendering", "description": "Only for rendering",
"icon": "circle:white;heart:red", "icon": "circle:white;heart:red",
"id": "favourite_icon", "condition": "_favourite=yes",
"metacondition": "__showTimeSensitiveIcons!=no" "metacondition": "__showTimeSensitiveIcons!=no"
}, },
{ {

View file

@ -217,8 +217,8 @@
}, },
{ {
"id": "debug", "id": "debug",
"metacondition": "__featureSwitchIsDebugging=true", "render": "{all_tags()}",
"render": "{all_tags()}" "metacondition": "__featureSwitchIsDebugging=true"
} }
], ],
"filter": [ "filter": [

View file

@ -114,9 +114,9 @@
"lineRendering": [], "lineRendering": [],
"tagRenderings": [ "tagRenderings": [
{ {
"classes": "p-0",
"id": "conversation", "id": "conversation",
"render": "{visualize_note_comments()}" "render": "{visualize_note_comments()}",
"classes": "p-0"
}, },
{ {
"id": "add_image", "id": "add_image",

View file

@ -66,16 +66,16 @@
], ],
"tagRenderings": [ "tagRenderings": [
{ {
"condition": "level=country",
"description": "The name of the country",
"id": "country_name", "id": "country_name",
"render": "{nameEn} {emojiFlag}" "description": "The name of the country",
"render": "{nameEn} {emojiFlag}",
"condition": "level=country"
}, },
{ {
"condition": "_community_links~*",
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"id": "community_links", "id": "community_links",
"render": "{_community_links}" "description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"render": "{_community_links}",
"condition": "_community_links~*"
} }
], ],
"filter": [ "filter": [

View file

@ -1,66 +0,0 @@
{
"id": "railway",
"name": {
"en": "Railway"
},
"description": {
"en": "Railways and disused railways"
},
"source": {
"osmTags": {
"or": [
"railway=rail",
"railway=tram",
"railway=subway",
"railway=light_rail",
"railway=disused",
"disused:railway=rail",
"disused:railway=tram",
"disused:railway=subway",
"disused:railway=light_rail",
"abandoned:railway=rail",
"abandoned:railway=tram",
"abandoned:railway=subway",
"abandoned:railway=light_rail"
]
}
},
"minzoom": 14,
"title": {
"render": {
"en": "Railway"
},
"icon": "./assets/svg/train.svg"
},
"pointRendering": [
{
"location": [
"point"
],
"marker": [
{
"icon": "circle"
}
]
}
],
"lineRendering": [
{
"width": 1,
"color": "black"
}
],
"tagRenderings": [
"images"
],
"allowMove": false,
"#": "In first instance to snap historic rolling stock",
"snapName": {
"en": "railway track"
},
"credits": "mnalis ALTernative",
"credits:uid": 172435
}

View file

@ -43,24 +43,11 @@
"id~relation/.*" "id~relation/.*"
] ]
}, },
{ "building~*"
"or": [
"building~*",
"building:part~*"
]
}
] ]
} }
}, },
"title": { "title": "OSM-gebouw",
"render": "OSM-building",
"mappings": [{
"if": "building:part~*",
"then": {
"en": "Building part"
}
}]
},
"tagRenderings": [ "tagRenderings": [
{ {
"id": "building type", "id": "building type",
@ -119,12 +106,10 @@
"if": "building=yes", "if": "building=yes",
"then": "A building - no specification" "then": "A building - no specification"
} }
], ]
"condition": "building:part="
}, },
{ {
"id": "grb-housenumber", "id": "grb-housenumber",
"condition": "building:part=",
"render": { "render": {
"nl": "Het huisnummer is <b>{addr:housenumber}</b>" "nl": "Het huisnummer is <b>{addr:housenumber}</b>"
}, },
@ -149,7 +134,6 @@
] ]
}, },
{ {
"condition": "building:part=",
"id": "grb-unit", "id": "grb-unit",
"question": "Wat is de wooneenheid-aanduiding?", "question": "Wat is de wooneenheid-aanduiding?",
"render": { "render": {
@ -166,7 +150,6 @@
] ]
}, },
{ {
"condition": "building:part=",
"id": "grb-street", "id": "grb-street",
"render": { "render": {
"nl": "De straat is <b>{addr:street}</b>" "nl": "De straat is <b>{addr:street}</b>"
@ -259,49 +242,7 @@
] ]
} }
], ],
"pointRendering": [ "pointRendering": null,
{
"marker": [{
"icon": "circle",
"color": {
"render": "#00c",
"mappings": [
{
"if": "fixme~*",
"then": "#ff00ff"
},
{
"if": "building=house",
"then": "#a00"
},
{
"if": "building=shed",
"then": "#563e02"
},
{
"if": {
"or": [
"building=garage",
"building=garages"
]
},
"then": "#f9bfbb"
},
{
"if": "building=yes",
"then": "#0774f2"
},
{
"if": "building:part~*",
"then": "#f8fc25"
}
]
}
}],
"location": ["centroid"],
"iconSize": "10,10"
}
],
"lineRendering": [ "lineRendering": [
{ {
"width": { "width": {
@ -340,10 +281,6 @@
{ {
"if": "building=yes", "if": "building=yes",
"then": "#0774f2" "then": "#0774f2"
},
{
"if": "building:part~*",
"then": "#f8fc25"
} }
] ]
} }

View file

@ -18,14 +18,6 @@
}, },
"icon": "./assets/layers/historic_rolling_stock/steam_locomotive.svg", "icon": "./assets/layers/historic_rolling_stock/steam_locomotive.svg",
"layers": [ "layers": [
"historic_rolling_stock", "historic_rolling_stock"
{
"builtin": "railway",
"override": {
"name": null,
"presets": null,
"shownByDefault": false
}
}
] ]
} }

View file

@ -58,14 +58,6 @@
"drinking_water", "drinking_water",
"birdhide", "birdhide",
"nature_reserve", "nature_reserve",
{
"builtin": [
"shelter"
],
"override": {
"minzoom": 11
}
},
{ {
"builtin": [ "builtin": [
"map", "map",
@ -74,6 +66,7 @@
"picnic_table", "picnic_table",
"toilet", "toilet",
"guidepost", "guidepost",
"shelter",
"bbq", "bbq",
"firepit", "firepit",
"insect_hotel", "insect_hotel",

View file

@ -100,48 +100,6 @@
"name": null "name": null
} }
}, },
{
"builtin": "cafe_pub",
"override": {
"id": "cafe_pub_dog_friendly",
"name": {
"en": "Dog friendly drinking places",
"da": "Hundevenlige værtshuse"
},
"pointRendering": [
{
"iconBadges+": [
"icons.dogicon"
]
}
],
"=presets": [],
"source": {
"osmTags": {
"and+": [
{
"or": [
"dog=unleashed",
"dog=leashed",
"dog=yes"
]
}
]
}
}
}
},
{
"builtin": "cafe_pub",
"override": {
"minzoom": 18,
"isCounted": false,
"filter": {
"sameAs": "cafe_pub_dog_friendly"
},
"name": null
}
},
{ {
"builtin": "shops", "builtin": "shops",
"override": { "override": {

View file

@ -7,7 +7,6 @@
"download": "Stáhnout aplikaci", "download": "Stáhnout aplikaci",
"downloadOnFDroid": "Stáhnout MapComplete na F-Droidu", "downloadOnFDroid": "Stáhnout MapComplete na F-Droidu",
"getOnObtanium": "Získat na Obtainiu", "getOnObtanium": "Získat na Obtainiu",
"intro": "MapComplete je k dispozici jako aplikace pro Android k přímému stažení. Pracujeme na to, aby byla zveřejněna i v repozitáři F-Droid.",
"legacyExplanation": "Kvůli restrikcím společnosti Google nelze zkompilovat identický balíček APK pro novější i starší verze systému Android.\n\n Pokud instalace z F-Droidu, Google Play, Obtainia nebo instalace výše odkazované nejnovější verze ('latest') selže,\n zkuste balíčky pro starší verze Androidu, které jsou k dispozici níže.", "legacyExplanation": "Kvůli restrikcím společnosti Google nelze zkompilovat identický balíček APK pro novější i starší verze systému Android.\n\n Pokud instalace z F-Droidu, Google Play, Obtainia nebo instalace výše odkazované nejnovější verze ('latest') selže,\n zkuste balíčky pro starší verze Androidu, které jsou k dispozici níže.",
"legacyHeader": "Sestavení pro starší telefony se systémem Android", "legacyHeader": "Sestavení pro starší telefony se systémem Android",
"noPlayServices": "Aplikace funguje bez Google Play Services", "noPlayServices": "Aplikace funguje bez Google Play Services",

View file

@ -7,7 +7,6 @@
"download": "Download appen", "download": "Download appen",
"downloadOnFDroid": "Download MapComplete på F-Droid", "downloadOnFDroid": "Download MapComplete på F-Droid",
"getOnObtanium": "Hent på Obtainium", "getOnObtanium": "Hent på Obtainium",
"intro": "MapComplete er tilgængelig som Android-app som direkte download. Vi arbejder på også at udgive den på FDroid.",
"noPlayServices": "Appen fungerer uden Google Play Services", "noPlayServices": "Appen fungerer uden Google Play Services",
"older": "Se ældre builds", "older": "Se ældre builds",
"title": "MapComplete Android-app" "title": "MapComplete Android-app"

View file

@ -5,7 +5,6 @@
"app": { "app": {
"back": "Zurück zu MapComplete", "back": "Zurück zu MapComplete",
"download": "Lade die App runter", "download": "Lade die App runter",
"intro": "MapComplete gibt's als Android-App zum direkten Download. Wir arbeiten daran, die App auch auf FDroid zu veröffentlichen.",
"noPlayServices": "Die App funktioniert ohne Google Play Services", "noPlayServices": "Die App funktioniert ohne Google Play Services",
"older": "Ältere Versionen ansehen", "older": "Ältere Versionen ansehen",
"title": "MapComplete Android-App" "title": "MapComplete Android-App"

View file

@ -6,8 +6,9 @@
"back": "Go back to MapComplete", "back": "Go back to MapComplete",
"download": "Download the app", "download": "Download the app",
"downloadOnFDroid": "Download MapComplete on F-Droid", "downloadOnFDroid": "Download MapComplete on F-Droid",
"getOnGoogle": "Download MapComplete on Google Playstore",
"getOnObtanium": "Get on Obtainium", "getOnObtanium": "Get on Obtainium",
"intro": "MapComplete is available as Android App as direct download. We are working on publishing this in on FDroid too.", "intro": "MapComplete is available as Android App on various app stores. Installing the app version makes it easier to access MapComplete and uses less internet. However, the web version has the same features.",
"legacyExplanation": "Due to restrictions by Google, it is not possible to compile an identical APK for newer and older Android versions.\nIf installing via F-Droid, Google Play, Obtainium or installing 'latest' above failed, try a versions for older versions of android are available below.", "legacyExplanation": "Due to restrictions by Google, it is not possible to compile an identical APK for newer and older Android versions.\nIf installing via F-Droid, Google Play, Obtainium or installing 'latest' above failed, try a versions for older versions of android are available below.",
"legacyHeader": "Builds for older Android phones", "legacyHeader": "Builds for older Android phones",
"noPlayServices": "The app works without Google Play Services", "noPlayServices": "The app works without Google Play Services",
@ -22,6 +23,9 @@
"retrying": "Loading data failed. Trying again in {count} seconds…", "retrying": "Loading data failed. Trying again in {count} seconds…",
"zoomIn": "Zoom in to view or edit the data" "zoomIn": "Zoom in to view or edit the data"
}, },
"collectionTimes": {
"addTime": "Add moment"
},
"communityIndex": { "communityIndex": {
"available": "This community speaks {native}", "available": "This community speaks {native}",
"intro": "Get in touch with other people to get to know them, learn from them, …", "intro": "Get in touch with other people to get to know them, learn from them, …",

View file

@ -7,7 +7,6 @@
"download": "Télécharger l'application", "download": "Télécharger l'application",
"downloadOnFDroid": "Télécharger MapComplete sur F-Droid", "downloadOnFDroid": "Télécharger MapComplete sur F-Droid",
"getOnObtanium": "Obtenir sur Obtainium", "getOnObtanium": "Obtenir sur Obtainium",
"intro": "MapComplete est disponible en tant qu'application Android en téléchargement direct. Nous nous efforçons de publier également cette application sur FDroid.",
"legacyExplanation": "En raison des restrictions imposées par Google, il n'est pas possible de compiler un APK identique pour les versions Android récentes et anciennes.\nSi l'installation via F-Droid, Google Play, Obtainium ou l'installation de la « dernière version » ci-dessus a échoué, essayez une des versions pour les anciennes versions d'Android disponibles ci-dessous.", "legacyExplanation": "En raison des restrictions imposées par Google, il n'est pas possible de compiler un APK identique pour les versions Android récentes et anciennes.\nSi l'installation via F-Droid, Google Play, Obtainium ou l'installation de la « dernière version » ci-dessus a échoué, essayez une des versions pour les anciennes versions d'Android disponibles ci-dessous.",
"legacyHeader": "Versions pour les anciens téléphones Android", "legacyHeader": "Versions pour les anciens téléphones Android",
"noPlayServices": "L'application fonctionne sans Google Play Services", "noPlayServices": "L'application fonctionne sans Google Play Services",

View file

@ -5,7 +5,6 @@
"app": { "app": {
"back": "Ritorna a MapComplete", "back": "Ritorna a MapComplete",
"download": "Scarica l'app", "download": "Scarica l'app",
"intro": "MapComplete è disponibile come App Android in download diretto. Stiamo lavorando per pubblicarla anche su FDroid.",
"noPlayServices": "L'app funziona senza Google Play Services", "noPlayServices": "L'app funziona senza Google Play Services",
"older": "Vedi le vecchie versioni", "older": "Vedi le vecchie versioni",
"title": "MapComplete App per Android" "title": "MapComplete App per Android"

View file

@ -4751,9 +4751,7 @@
"diets": { "diets": {
"tagRenderings": { "tagRenderings": {
"diets_title": { "diets_title": {
"render": { "render": "Dietní možnosti"
"before": "Dietní možnosti"
}
}, },
"gluten_free": { "gluten_free": {
"mappings": { "mappings": {
@ -7467,7 +7465,7 @@
} }
}, },
"lighthouse": { "lighthouse": {
"name": "maják", "name": "Majáky",
"presets": { "presets": {
"0": { "0": {
"title": "maják" "title": "maják"
@ -7485,7 +7483,7 @@
"then": "{name}" "then": "{name}"
} }
}, },
"render": "maják" "render": "Maják"
} }
}, },
"love_hotel": { "love_hotel": {

View file

@ -4768,9 +4768,7 @@
"diets": { "diets": {
"tagRenderings": { "tagRenderings": {
"diets_title": { "diets_title": {
"render": { "render": "Dietary options"
"before": "Dietary options"
}
}, },
"gluten_free": { "gluten_free": {
"mappings": { "mappings": {
@ -10094,14 +10092,6 @@
} }
} }
}, },
"railway": {
"description": "Railways and disused railways",
"name": "Railway",
"snapName": "railway track",
"title": {
"render": "Railway"
}
},
"railway_platforms": { "railway_platforms": {
"description": "Find every platform in the station, and the train routes that use them.", "description": "Find every platform in the station, and the train routes that use them.",
"name": "Railway Platforms", "name": "Railway Platforms",

View file

@ -2189,9 +2189,6 @@
}, },
"title": { "title": {
"mappings": { "mappings": {
"0": {
"then": "{name}"
},
"1": { "1": {
"then": "Vogelkijkhut {name}" "then": "Vogelkijkhut {name}"
}, },
@ -4561,9 +4558,7 @@
"diets": { "diets": {
"tagRenderings": { "tagRenderings": {
"diets_title": { "diets_title": {
"render": { "render": "Dieetopties"
"before": "Dieetopties"
}
}, },
"gluten_free": { "gluten_free": {
"mappings": { "mappings": {
@ -7267,11 +7262,6 @@
} }
}, },
"title": { "title": {
"mappings": {
"0": {
"then": "{name}"
}
},
"render": "Natuurgebied" "render": "Natuurgebied"
} }
}, },
@ -7805,21 +7795,6 @@
"render": "Picknicktafel" "render": "Picknicktafel"
} }
}, },
"play_forest": {
"description": "Een speelbos is een vrij toegankelijke zone in een bos",
"name": "Speelbossen",
"title": {
"mappings": {
"0": {
"then": "{name}"
},
"1": {
"then": "Speelbos {name}"
}
},
"render": "Speelbos"
}
},
"playground": { "playground": {
"deletion": { "deletion": {
"nonDeleteMappings": { "nonDeleteMappings": {
@ -9620,9 +9595,6 @@
}, },
"title": { "title": {
"mappings": { "mappings": {
"0": {
"then": "{name}"
},
"1": { "1": {
"then": "Voetpad" "then": "Voetpad"
}, },
@ -11881,16 +11853,7 @@
} }
}, },
"village_green": { "village_green": {
"description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)", "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)"
"name": "Speelweide",
"title": {
"mappings": {
"0": {
"then": "{name}"
}
},
"render": "Speelweide"
}
}, },
"visitor_information_centre": { "visitor_information_centre": {
"description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.", "description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.",

View file

@ -1072,7 +1072,8 @@
}, },
"question": "這家商店提供戀物癖裝備嗎?" "question": "這家商店提供戀物癖裝備嗎?"
} }
} },
"name": "商家"
}, },
"sports_centre": { "sports_centre": {
"tagRenderings": { "tagRenderings": {
@ -1275,5 +1276,18 @@
}, },
"render": "風機" "render": "風機"
} }
},
"speed_camera": {
"title": {
"render": "測速照相"
},
"tagRenderings": {
"maxspeed": {
"question": "這個測速照相允許的最高速限?"
}
}
},
"police_call_box": {
"name": "警察詢問室"
} }
} }

View file

@ -6,7 +6,6 @@
"back": "Ga terug naar MapComplete", "back": "Ga terug naar MapComplete",
"download": "Download de laatste versie", "download": "Download de laatste versie",
"downloadOnFDroid": "Download MapComplete op F-Droid", "downloadOnFDroid": "Download MapComplete op F-Droid",
"intro": "MapComplete is beschikbaar als Android App. Deze is binnenkort ook in F-Droid beschikbaar",
"older": "Bekijk oudere versies", "older": "Bekijk oudere versies",
"title": "MapComplete Anrdoid App" "title": "MapComplete Anrdoid App"
}, },

View file

@ -646,13 +646,6 @@
"grb-reference": { "grb-reference": {
"render": "Has been imported from GRB, reference number is {source:geometry:ref}" "render": "Has been imported from GRB, reference number is {source:geometry:ref}"
} }
},
"title": {
"mappings": {
"0": {
"then": "Building part"
}
}
} }
}, },
"1": { "1": {

View file

@ -7,7 +7,6 @@
"download": "下載應用程式", "download": "下載應用程式",
"downloadOnFDroid": "在 F-Droid 下載 MapComplete", "downloadOnFDroid": "在 F-Droid 下載 MapComplete",
"getOnObtanium": "從 Obtainium 取得", "getOnObtanium": "從 Obtainium 取得",
"intro": "MapComplete可以在Android App直接下載。我們也計畫在Fdroid發佈。",
"legacyExplanation": "由於Google的限制並無法為更新與更舊的Android編譯相同的APK\n 版本。\n\n 如果你透過F-Droid、Google Play、Obtainium或是安裝「最新」的版本失敗\n 請為其他較舊Android版本使用其他版本。", "legacyExplanation": "由於Google的限制並無法為更新與更舊的Android編譯相同的APK\n 版本。\n\n 如果你透過F-Droid、Google Play、Obtainium或是安裝「最新」的版本失敗\n 請為其他較舊Android版本使用其他版本。",
"legacyHeader": "為較舊Android手機建構", "legacyHeader": "為較舊Android手機建構",
"noPlayServices": "本應用無需 Google Play 服務即可運行", "noPlayServices": "本應用無需 Google Play 服務即可運行",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.55.1", "version": "0.55.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.55.1", "version": "0.55.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.55.1", "version": "0.55.2",
"repository": "https://source.mapcomplete.org/MapComplete/MapComplete", "repository": "https://source.mapcomplete.org/MapComplete/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "hhttps://source.mapcomplete.org/MapComplete/MapComplete/issues", "bugs": "hhttps://source.mapcomplete.org/MapComplete/MapComplete/issues",

View file

@ -37,8 +37,15 @@ import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages"
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
import {
LayerConfigDependencyGraph,
LevelInfo,
} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph"
import { Lists } from "../src/Utils/Lists" import { Lists } from "../src/Utils/Lists"
import { LayerConfigDependencyGraph, LevelInfo } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" import {
LayerConfigDependencyGraph,
LevelInfo,
} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph"
import { AddContextToTranslations } from "../src/Models/ThemeConfig/Conversion/AddContextToTranslations" import { AddContextToTranslations } from "../src/Models/ThemeConfig/Conversion/AddContextToTranslations"
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files. // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
@ -114,7 +121,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye
static singleton = new AddIconSummary() static singleton = new AddIconSummary()
constructor() { constructor() {
super("AddIconSummary", "Adds an icon summary ('_layerIcon') for quick reference. This previews how the layer should be shown in e.g. the filter menu") super("AddIconSummary", "Adds an icon summary for quick reference")
} }
convert(json: { raw: LayerConfigJson; parsed: LayerConfig }) { convert(json: { raw: LayerConfigJson; parsed: LayerConfig }) {
@ -699,8 +706,8 @@ class LayerOverviewUtils extends Script {
) )
const path = "assets/layers/questions/questions.json" const path = "assets/layers/questions/questions.json"
const sharedQuestionsRaw: LayerConfigJson = this.parseLayer(doesImageExist, prepareLayer, path).raw const sharedQuestionsRaw = this.parseLayer(doesImageExist, prepareLayer, path).raw
const sharedQuestions: LayerConfigJson = new AddContextToTranslations<LayerConfigJson>("").convertStrict( const sharedQuestions = new AddContextToTranslations("").convertStrict(
sharedQuestionsRaw, sharedQuestionsRaw,
ConversionContext.construct(["layers:questions"], []) ConversionContext.construct(["layers:questions"], [])
) )

View file

@ -90,18 +90,6 @@ export class MenuState {
if (!visitedBefore.data && shouldShowWelcomeMessage) { if (!visitedBefore.data && shouldShowWelcomeMessage) {
this.pageStates.about_theme.set(true) this.pageStates.about_theme.set(true)
visitedBefore.set(true) visitedBefore.set(true)
this._selectedElement.addCallbackD(() => {
if(this.pageStates.about_theme.data){
this.pageStates.about_theme.set(false)
this._selectedElement.addCallbackAndRun(selected => {
if(!selected){
this.pageStates.about_theme.set(true)
return true
}
})
return true
}
})
} }
} }

View file

@ -299,7 +299,7 @@ export interface LayerConfigJson {
/** /**
* Advanced option - might be set by the theme compiler * Advanced option - might be set by the theme compiler
* *
* If true, this data will _always_ be loaded, even if the layer is disabled by a filter or hidden. * If true, this data will _always_ be loaded, even if the theme is disabled by a filter or hidden.
* The opposite of `doNotDownload` * The opposite of `doNotDownload`
* *
* question: Should this layer be forcibly loaded? * question: Should this layer be forcibly loaded?

View file

@ -523,9 +523,7 @@ export default class TagRenderingConfig {
/** /**
* Gets all the render values. Will return multiple render values if 'multianswer' is enabled. * Gets all the render values. Will return multiple render values if 'multianswer' is enabled.
* The result will equal [GetRenderValue] if not 'multiAnswer'. * The result will equal [GetRenderValue] if not 'multiAnswer'
*
* If an iconsource is given, will return an icon too (but not necessarly an iconSize)
* @param tags * @param tags
* @constructor * @constructor
*/ */

View file

@ -21,7 +21,6 @@ import SelectedElementTagsUpdater from "../../Logic/Actors/SelectedElementTagsUp
import NoElementsInViewDetector, { import NoElementsInViewDetector, {
FeatureViewState, FeatureViewState,
} from "../../Logic/Actors/NoElementsInViewDetector" } from "../../Logic/Actors/NoElementsInViewDetector"
import { features } from "monaco-editor/esm/metadata"
export class WithChangesState extends WithLayoutSourceState { export class WithChangesState extends WithLayoutSourceState {
readonly changes: Changes readonly changes: Changes
@ -227,10 +226,15 @@ export class WithChangesState extends WithLayoutSourceState {
metaTags: this.userRelatedState.preferencesAsTags, metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement, selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id), fetchStore: (id) => this.featureProperties.getStore(id),
onClick: feature => {
this.setSelectedElement(feature)
}
}) })
/*new ShowDataLayer(map, {
layer: fs.layer.layerDef,
features: filtered,
doShowLayer,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id),
})*/
}) })
return filteringFeatureSource return filteringFeatureSource
} }

View file

@ -130,8 +130,7 @@ export class WithLayoutSourceState extends WithSelectedElementState {
} }
protected setSelectedElement(feature: Feature) { protected setSelectedElement(feature: Feature) {
// The given feature might be a partial one from the cache or might be a centroid point instead of the way // The given feature might be a partial one from the cache
// We lookup a version by IDP
if (feature !== undefined) { if (feature !== undefined) {
feature = feature =
this.indexedFeatures.featuresById.data?.get(feature?.properties?.id) ?? feature this.indexedFeatures.featuresById.data?.get(feature?.properties?.id) ?? feature

View file

@ -4,7 +4,7 @@ import FavouritesFeatureSource from "../../Logic/FeatureSource/Sources/Favourite
import Constants from "../Constants" import Constants from "../Constants"
import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { Feature, Geometry } from "geojson" import { Feature } from "geojson"
import { BBox } from "../../Logic/BBox" import { BBox } from "../../Logic/BBox"
import ShowDataLayer from "../../UI/Map/ShowDataLayer" import ShowDataLayer from "../../UI/Map/ShowDataLayer"
import MetaTagging from "../../Logic/MetaTagging" import MetaTagging from "../../Logic/MetaTagging"
@ -22,7 +22,6 @@ import {
} from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions"
import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource" import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource"
import { OsmTags } from "../OsmFeature"
export class WithSpecialLayers extends WithChangesState { export class WithSpecialLayers extends WithChangesState {
readonly favourites: FavouritesFeatureSource readonly favourites: FavouritesFeatureSource
@ -181,7 +180,7 @@ export class WithSpecialLayers extends WithChangesState {
}) })
) )
// show last click = new point/note marker // show last click = new point/note marker
const features: StaticFeatureSource<Feature<Geometry, Record<string, string> & {id: string}>> = new StaticFeatureSource(lastClickFiltered) const features = new StaticFeatureSource(lastClickFiltered)
this.featureProperties.trackFeatureSource(features) this.featureProperties.trackFeatureSource(features)
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features, features,

View file

@ -8,6 +8,7 @@
import { OH } from "../../../OpeningHours/OpeningHours" import { OH } from "../../../OpeningHours/OpeningHours"
import { Lists } from "../../../../Utils/Lists" import { Lists } from "../../../../Utils/Lists"
import { Translation } from "../../../i18n/Translation" import { Translation } from "../../../i18n/Translation"
import PlusCircle from "@babeard/svelte-heroicons/mini/PlusCircle"
export let value: UIEventSource<string> export let value: UIEventSource<string>
@ -114,7 +115,7 @@
}} }}
> >
<PlusCircle class="h-6 w-6" /> <PlusCircle class="h-6 w-6" />
Add time <Tr t={Translations.t.collectionTimes.addTime} />
</button> </button>
</div> </div>
<div class="flex w-fit flex-wrap"> <div class="flex w-fit flex-wrap">

View file

@ -5,7 +5,6 @@
import type { Feature } from "geojson" import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { Validator } from "./Validator"
import DistanceInput from "./Helpers/DistanceInput.svelte" import DistanceInput from "./Helpers/DistanceInput.svelte"
export let type: ValidatorType export let type: ValidatorType
@ -14,11 +13,13 @@
export let args: (string | number | boolean)[] | any = undefined export let args: (string | number | boolean)[] | any = undefined
export let state: SpecialVisualizationState = undefined export let state: SpecialVisualizationState = undefined
let validator = Validators.get(type) let validator = Validators.get(type)
let validatorHelper: Validator = validator.inputHelper let validatorHelper = validator.inputHelper
</script> </script>
{#if type === "distance"} {#if type === "distance"}
<DistanceInput {value} {feature} {state} {args} /> <DistanceInput {value} {feature} {state} {args} />
{:else if validatorHelper !== undefined} {:else if validatorHelper !== undefined}
<svelte:component this={validatorHelper} {value} {feature} {state} {args} on:submit /> <svelte:component this={validatorHelper} {value} {feature} {state} {args} on:submit />
{:else}
<slot name="fallback"/> <!-- Used in the FilterWithView to inject the helper -->
{/if} {/if}

View file

@ -459,6 +459,11 @@ export default class ShowDataLayer {
preprocessPoints, preprocessPoints,
} = this._options } = this._options
let onClick = this._options.onClick let onClick = this._options.onClick
if (!onClick && selectedElement && layer.title !== undefined) {
onClick = (feature: Feature) => {
selectedElement?.setData(feature)
}
}
if (drawLines !== false) { if (drawLines !== false) {
for (let i = 0; i < layer.lineRendering.length; i++) { for (let i = 0; i < layer.lineRendering.length; i++) {
const lineRenderingConfig = layer.lineRendering[i] const lineRenderingConfig = layer.lineRendering[i]

View file

@ -2,9 +2,8 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../Logic/Osm/Changes" import { Changes } from "../../Logic/Osm/Changes"
import { import {
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
} from "../SpecialVisualization" } from "../SpecialVisualization"
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
@ -32,7 +31,12 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
public readonly funcName: string = "auto_apply" public readonly funcName: string = "auto_apply"
public readonly needsUrls = [] public readonly needsUrls = []
public readonly group = "data_import" public readonly group = "data_import"
public readonly args: SpecialVisualisationArg[] = [ public readonly args: {
name: string
defaultValue?: string
doc: string
required?: boolean
}[] = [
{ {
name: "target_layer", name: "target_layer",
doc: "The layer that the target features will reside in", doc: "The layer that the target features will reside in",
@ -50,7 +54,6 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
}, },
{ {
name: "text", name: "text",
type:"translation",
doc: "The text to show on the button", doc: "The text to show on the button",
required: true, required: true,
}, },
@ -83,11 +86,15 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
4. At last, add this component` 4. At last, add this component`
} }
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
const target_layer_id = args[0] state: SpecialVisualizationState,
const targetTagRendering = args[2] tagSource: UIEventSource<Record<string, string>>,
const text = args[3] argument: string[]
const icon = args[4] ): SvelteUIElement {
const target_layer_id = argument[0]
const targetTagRendering = argument[2]
const text = argument[3]
const icon = argument[4]
const options = { const options = {
target_layer_id, target_layer_id,
targetTagRendering, targetTagRendering,
@ -98,7 +105,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined) const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined)
Stores.chronic(500, () => to_parse.data === undefined) Stores.chronic(500, () => to_parse.data === undefined)
.map(() => { .map(() => {
const applicable = <string | string[]>tags.data[args[1]] const applicable = <string | string[]>tagSource.data[argument[1]]
if (typeof applicable === "string") { if (typeof applicable === "string") {
return <string[]>JSON.parse(applicable) return <string[]>JSON.parse(applicable)
} else { } else {

View file

@ -1,5 +1,11 @@
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature, LineString } from "geojson" import { Feature, LineString } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import ExportFeatureButton from "./ExportFeatureButton.svelte" import ExportFeatureButton from "./ExportFeatureButton.svelte"
@ -11,7 +17,13 @@ class ExportAsGpxVis extends SpecialVisualizationSvelte {
args = [] args = []
needsUrls = [] needsUrls = []
constr({ tags, feature, layer }: SpecialVisualisationParams) { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
) {
if (feature.geometry.type !== "LineString") { if (feature.geometry.type !== "LineString") {
return undefined return undefined
} }
@ -36,7 +48,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
docs = "Exports the selected feature as GeoJson-file" docs = "Exports the selected feature as GeoJson-file"
args = [] args = []
constr({ tags, feature, layer }: SpecialVisualisationParams) { constr(state, tags, args, feature, layer) {
const t = Translations.t.general.download const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, { return new SvelteUIElement(ExportFeatureButton, {
tags, tags,
@ -52,7 +64,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
} }
export class DataExportVisualisations { export class DataExportVisualisations {
public static initList(): SpecialVisualizationSvelte[] { public static initList(): SpecialVisualization[] {
return [new ExportAsGpxVis(), new ExportAsGeojsonVis()] return [new ExportAsGpxVis(), new ExportAsGeojsonVis()]
} }
} }

View file

@ -1,11 +1,11 @@
import { import {
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
} from "../SpecialVisualization" } from "../SpecialVisualization"
import { HistogramViz } from "./HistogramViz" import { HistogramViz } from "./HistogramViz"
import { Store } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import DirectionIndicator from "../Base/DirectionIndicator.svelte" import DirectionIndicator from "../Base/DirectionIndicator.svelte"
@ -20,18 +20,15 @@ import NextChangeViz from "../OpeningHours/NextChangeViz.svelte"
import { Unit } from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte" import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte"
import { LanguageElement } from "./LanguageElement/LanguageElement" import { LanguageElement } from "./LanguageElement/LanguageElement"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte" import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte" import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte" import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
import Tr from "../Base/Tr.svelte"
import Combine from "../Base/Combine"
import Marker from "../Map/Marker.svelte"
import { twJoin } from "tailwind-merge"
class DirectionIndicatorVis extends SpecialVisualizationSvelte { class DirectionIndicatorVis extends SpecialVisualization {
funcName = "direction_indicator" funcName = "direction_indicator"
args = [] args = []
@ -39,8 +36,13 @@ class DirectionIndicatorVis extends SpecialVisualizationSvelte {
"Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object" "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object"
group = "data" group = "data"
constr(params: SpecialVisualisationParams): SvelteUIElement { constr(
return new SvelteUIElement(DirectionIndicator, params) state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
): BaseUIElement {
return new SvelteUIElement(DirectionIndicator, { state, feature })
} }
} }
@ -63,15 +65,17 @@ class DirectionAbsolute extends SpecialVisualization {
] ]
group = "data" group = "data"
constr({ constr(
tags, state: SpecialVisualizationState,
args, tagSource: UIEventSource<Record<string, string>>,
}: SpecialVisualisationParams): BaseUIElement { args: string[]
): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0] const key = args[0] === "" ? "_direction:centerpoint" : args[0]
const offset = args[1] === "" ? 0 : Number(args[1]) const offset = args[1] === "" ? 0 : Number(args[1])
return new VariableUiElement( return new VariableUiElement(
tags.map((tags) => { tagSource
.map((tags) => {
console.log("Direction value", tags[key], key) console.log("Direction value", tags[key], key)
return tags[key] return tags[key]
}) })
@ -113,12 +117,14 @@ class OpeningHoursTableVis extends SpecialVisualizationSvelte {
example = example =
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`" "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`"
constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(state, tagSource: UIEventSource<any>, args) {
const [key, prefix, postfix] = args const [key, prefix, postfix] = args
const openingHoursStore: Store<opening_hours | "error" | undefined> = const openingHoursStore: Store<opening_hours | "error" | undefined> =
OH.CreateOhObjectStore(tags, key, prefix, postfix) OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
return new SvelteUIElement(OpeningHoursWithError, { return new SvelteUIElement(OpeningHoursWithError, {
tags, key, opening_hours_obj: openingHoursStore, tags: tagSource,
key,
opening_hours_obj: openingHoursStore,
}) })
} }
} }
@ -146,7 +152,11 @@ class OpeningHoursState extends SpecialVisualizationSvelte {
}, },
] ]
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
const keyToUse = args[0] const keyToUse = args[0]
const prefix = args[1] const prefix = args[1]
const postfix = args[2] const postfix = args[2]
@ -177,10 +187,10 @@ class Canonical extends SpecialVisualization {
}, },
] ]
constr({ state, tags, args }: SpecialVisualisationParams) { constr(state, tagSource, args) {
const key = args[0] const key = args[0]
return new VariableUiElement( return new VariableUiElement(
tags tagSource
.map((tags) => tags[key]) .map((tags) => tags[key])
.map((value) => { .map((value) => {
if (value === undefined) { if (value === undefined) {
@ -193,7 +203,7 @@ class Canonical extends SpecialVisualization {
if (unit === undefined) { if (unit === undefined) {
return value return value
} }
const getCountry = () => tags.data._country const getCountry = () => tagSource.data._country
return unit.asHumanLongValue(value, getCountry) return unit.asHumanLongValue(value, getCountry)
}) })
) )
@ -207,24 +217,26 @@ class StatisticsVis extends SpecialVisualizationSvelte {
"Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer" "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer"
args = [] args = []
constr(params: SpecialVisualisationParams) { constr(state) {
return new SvelteUIElement(AllFeaturesStatistics, params) return new SvelteUIElement(AllFeaturesStatistics, { state })
} }
} }
class PresetDescription extends SpecialVisualizationSvelte { class PresetDescription extends SpecialVisualization {
funcName = "preset_description" funcName = "preset_description"
docs = docs =
"Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty" "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty"
args = [] args = []
group = "UI"
constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { constr(
const translation = tags.map((tags) => { state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
): BaseUIElement {
const translation = tagSource.map((tags) => {
const layer = state.theme.getMatchingLayer(tags) const layer = state.theme.getMatchingLayer(tags)
return layer?.getMostMatchingPreset(tags)?.description return layer?.getMostMatchingPreset(tags)?.description
}) })
return new SvelteUIElement(Tr, { t: translation }) return new VariableUiElement(translation)
} }
} }
@ -232,9 +244,14 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
funcName = "preset_type_select" funcName = "preset_type_select"
docs = "An editable tag rendering which allows to change the type" docs = "An editable tag rendering which allows to change the type"
args = [] args = []
group = "ui"
constr({ state, tags, feature, layer }: SpecialVisualisationParams,): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
selectedElement: Feature,
layer: LayerConfig
): SvelteUIElement {
const t = Translations.t.preset_type const t = Translations.t.preset_type
if (layer._basedOn !== layer.id) { if (layer._basedOn !== layer.id) {
console.warn("Trying to use the _original_ layer") console.warn("Trying to use the _original_ layer")
@ -260,7 +277,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
return new SvelteUIElement(TagRenderingEditable, { return new SvelteUIElement(TagRenderingEditable, {
config, config,
tags, tags,
selectedElement: feature, selectedElement,
state, state,
layer, layer,
}) })
@ -273,8 +290,8 @@ class AllTagsVis extends SpecialVisualizationSvelte {
args = [] args = []
group = "data" group = "data"
constr(params: SpecialVisualisationParams) { constr(state, tags: UIEventSource<Record<string, string>>, _, __, layer: LayerConfig) {
return new SvelteUIElement(AllTagsPanel, params) return new SvelteUIElement(AllTagsPanel, { tags, layer })
} }
} }
@ -285,18 +302,23 @@ class PointsInTimeVis extends SpecialVisualization {
args = [ args = [
{ {
name: "key", name: "key",
type:"key",
required: true, required: true,
doc: "The key out of which the points_in_time will be parsed", doc: "The key out of which the points_in_time will be parsed",
}, },
] ]
constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = args[0] const key = args[0]
const points_in_time = tags.map((tags) => tags[key]) const points_in_time = tagSource.map((tags) => tags[key])
const times = points_in_time.map( const times = points_in_time.map(
(times) => OH.createOhObject(<any>tags.data, times, tags.data["_country"], 1), (times) => OH.createOhObject(<any>tagSource.data, times, tagSource.data["_country"], 1),
[tags] [tagSource]
) )
return new VariableUiElement( return new VariableUiElement(
times.map((times) => new SvelteUIElement(CollectionTimes, { times })) times.map((times) => new SvelteUIElement(CollectionTimes, { times }))
@ -304,60 +326,6 @@ class PointsInTimeVis extends SpecialVisualization {
} }
} }
class KnownIcons extends SpecialVisualization {
docs = "Displays all icons from the specified tagRenderings (if they are known and have an icon) together, e.g. to give a summary of the dietary options"
needsUrls = []
group = "UI"
funcName = "show_icons"
args: SpecialVisualisationArg[] = [{
name: "labels",
doc: "A ';'-separated list of labels and/or ids of tagRenderings",
type: "key",
required: true,
}, {
name: "class",
doc: "CSS-classes of the container, space-separated",
type: "css",
required: false,
defaultValue: "inline-flex mx-4",
}]
private static readonly emojiHeights = {
small: "2rem",
medium: "3rem",
large: "5rem",
}
constr(options: SpecialVisualisationParams): BaseUIElement {
const labels = new Set(options.args[0].split(";").map(s => s.trim()))
const matchingTrs = options.layer.tagRenderings.filter(
tr => labels.has(tr.id) || tr.labels.some(l => labels.has(l)),
)
return new VariableUiElement(options.tags.map(tags =>
new Combine(matchingTrs.map(tr => {
const mapping = tr.GetRenderValueWithImage(tags)
if (!mapping?.icon) {
return undefined
}
return new SvelteUIElement(Marker, {
emojiHeight: KnownIcons.emojiHeights[mapping.iconClass] ?? "2rem",
clss: `mapping-icon-${mapping.iconClass ?? "small"}`,
icons: mapping.icon,
size: twJoin(
"shrink-0",
`mapping-icon-${mapping.iconClass ?? "small"}-height mapping-icon-${
mapping.iconClass ?? "small"
}-width`),
})
})
).SetClass(options.args[1] ?? "inline-flex mx-4")
))
}
}
export class DataVisualisations { export class DataVisualisations {
public static initList(): SpecialVisualization[] { public static initList(): SpecialVisualization[] {
return [ return [
@ -373,7 +341,6 @@ export class DataVisualisations {
new PresetDescription(), new PresetDescription(),
new PresetTypeSelect(), new PresetTypeSelect(),
new AllTagsVis(), new AllTagsVis(),
new KnownIcons(),
] ]
} }
} }

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson" import { Feature } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import Histogram from "../BigComponents/Histogram.svelte" import Histogram from "../BigComponents/Histogram.svelte"
@ -14,7 +14,6 @@ export class HistogramViz extends SpecialVisualization {
args = [ args = [
{ {
name: "key", name: "key",
type:"key",
doc: "The key to be read and to generate a histogram from", doc: "The key to be read and to generate a histogram from",
required: true, required: true,
}, },
@ -36,8 +35,12 @@ export class HistogramViz extends SpecialVisualization {
] ]
} }
constr( {tags, args}: SpecialVisualisationParams): SvelteUIElement { constr(
const values: Store<string[]> = tags.map((tags) => { state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
) {
const values: Store<string[]> = tagSource.map((tags) => {
const value = tags[args[0]] const value = tags[args[0]]
try { try {
if (value === "" || value === undefined) { if (value === "" || value === undefined) {

View file

@ -1,11 +1,7 @@
import { import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualizationSvelte,
SpecialVisualizationUtils,
} from "../../SpecialVisualization"
import { UIEventSource } from "../../../Logic/UIEventSource" import { UIEventSource } from "../../../Logic/UIEventSource"
import { Feature, Geometry, LineString, Polygon } from "geojson" import { Feature, Geometry, LineString, Polygon } from "geojson"
import BaseUIElement from "../../BaseUIElement"
import { ImportFlowArguments, ImportFlowUtils } from "./ImportFlow" import { ImportFlowArguments, ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
@ -18,7 +14,6 @@ import { Changes } from "../../../Logic/Osm/Changes"
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { OsmConnection } from "../../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
import { OsmTags } from "../../../Models/OsmFeature" import { OsmTags } from "../../../Models/OsmFeature"
import Tr from "../../Base/Tr.svelte"
export interface ConflateFlowArguments extends ImportFlowArguments { export interface ConflateFlowArguments extends ImportFlowArguments {
way_to_conflate: string way_to_conflate: string
@ -27,17 +22,21 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
snap_onto_layers?: string snap_onto_layers?: string
} }
export default class ConflateImportButtonViz extends SpecialVisualizationSvelte implements AutoAction { export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction {
supportsAutoAction: boolean = true supportsAutoAction: boolean = true
needsUrls = [] needsUrls = []
group = "data_import" group = "data_import"
public readonly funcName: string = "conflate_button" public readonly funcName: string = "conflate_button"
public readonly args: SpecialVisualisationArg[] = [ public readonly args: {
name: string
defaultValue?: string
doc: string
required?: boolean
}[] = [
...ImportFlowUtils.generalArguments, ...ImportFlowUtils.generalArguments,
{ {
name: "way_to_conflate", name: "way_to_conflate",
type:"key",
doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag", doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag",
}, },
] ]
@ -85,25 +84,32 @@ export default class ConflateImportButtonViz extends SpecialVisualizationSvelte
await state.changes.applyAction(action) await state.changes.applyAction(action)
} }
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {
const canBeImported = const canBeImported =
feature.geometry.type === "LineString" || feature.geometry.type === "LineString" ||
(feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1) (feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1)
if (!canBeImported) { if (!canBeImported) {
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongTypeToConflate, cls: "alert" }) return Translations.t.general.add.import.wrongTypeToConflate.SetClass("alert")
} }
const argsParsed: ConflateFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args) const args: ConflateFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, argsParsed) const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
const idOfWayToReplaceGeometry = tags.data[argsParsed.way_to_conflate] const idOfWayToReplaceGeometry = tagSource.data[args.way_to_conflate]
const importFlow = new ConflateImportFlowState( const importFlow = new ConflateImportFlowState(
state, state,
<Feature<LineString | Polygon>>feature, <Feature<LineString | Polygon>>feature,
argsParsed, args,
tagsToApply, tagsToApply,
tags, tagSource,
idOfWayToReplaceGeometry idOfWayToReplaceGeometry
) )
return new SvelteUIElement(WayImportFlow, { importFlow }) return new SvelteUIElement(WayImportFlow, {
importFlow,
})
} }
getLayerDependencies = (args: string[]) => getLayerDependencies = (args: string[]) =>

View file

@ -66,14 +66,13 @@ ${Utils.special_visualizations_importRequirementDocs}
* Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point, * Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point,
*/ */
public static getTagsToApply( public static getTagsToApply(
originalFeatureTags: Store<OsmTags>, originalFeatureTags: UIEventSource<OsmTags>,
args: { tags: string } args: { tags: string }
): Store<Tag[]> { ): Store<Tag[]> {
if (originalFeatureTags === undefined) { if (originalFeatureTags === undefined) {
return undefined return undefined
} }
let newTags: Store<Tag[]> let newTags: Store<Tag[]>
// Listing of the keys that should be transferred
const tags = args.tags const tags = args.tags
if ( if (
tags.indexOf(" ") < 0 && tags.indexOf(" ") < 0 &&
@ -82,6 +81,12 @@ ${Utils.special_visualizations_importRequirementDocs}
) { ) {
// This is a property to expand... // This is a property to expand...
const items: string = originalFeatureTags.data[tags] const items: string = originalFeatureTags.data[tags]
console.debug(
"The import button is using tags from properties[" +
tags +
"] of this object, namely ",
items
)
if (items.startsWith("{")) { if (items.startsWith("{")) {
// This is probably a JSON // This is probably a JSON

View file

@ -1,5 +1,7 @@
import { Feature, Point } from "geojson" import { Feature, Point } from "geojson"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization" import { UIEventSource } from "../../../Logic/UIEventSource"
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import BaseUIElement from "../../BaseUIElement"
import SvelteUIElement from "../../Base/SvelteUIElement" import SvelteUIElement from "../../Base/SvelteUIElement"
import PointImportFlow from "./PointImportFlow.svelte" import PointImportFlow from "./PointImportFlow.svelte"
import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState" import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState"
@ -7,14 +9,12 @@ import { Utils } from "../../../Utils"
import { ImportFlowUtils } from "./ImportFlow" import { ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import { GeoOperations } from "../../../Logic/GeoOperations" import { GeoOperations } from "../../../Logic/GeoOperations"
import Tr from "../../Base/Tr.svelte"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { OsmTags } from "../../../Models/OsmFeature" import { OsmTags } from "../../../Models/OsmFeature"
/** /**
* The wrapper to make the special visualisation for the PointImportFlow * The wrapper to make the special visualisation for the PointImportFlow
*/ */
export class PointImportButtonViz extends SpecialVisualizationSvelte { export class PointImportButtonViz extends SpecialVisualization {
public readonly funcName = "import_button" public readonly funcName = "import_button"
public readonly docs: string = public readonly docs: string =
"This button will copy the point from an external dataset into OpenStreetMap" + "This button will copy the point from an external dataset into OpenStreetMap" +
@ -47,24 +47,29 @@ export class PointImportButtonViz extends SpecialVisualizationSvelte {
public needsUrls = [] public needsUrls = []
group = "data_import" group = "data_import"
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {
const to_point_index = this.args.findIndex((arg) => arg.name === "to_point") const to_point_index = this.args.findIndex((arg) => arg.name === "to_point")
const summarizePointArg = args[to_point_index].toLowerCase() const summarizePointArg = argument[to_point_index].toLowerCase()
if (feature.geometry.type !== "Point") { if (feature.geometry.type !== "Point") {
if (summarizePointArg !== "no" && summarizePointArg !== "false") { if (summarizePointArg !== "no" && summarizePointArg !== "false") {
feature = GeoOperations.centerpoint(feature) feature = GeoOperations.centerpoint(feature)
} else { } else {
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongType.SetClass("alert") }) return Translations.t.general.add.import.wrongType.SetClass("alert")
} }
} }
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, args) const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, baseArgs) const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs)
const importFlow = new PointImportFlowState( const importFlow = new PointImportFlowState(
state, state,
<Feature<Point>>feature, <Feature<Point>>feature,
baseArgs, baseArgs,
tagsToApply, tagsToApply,
tags tagSource
) )
return new SvelteUIElement(PointImportFlow, { return new SvelteUIElement(PointImportFlow, {

View file

@ -1,12 +1,10 @@
import { import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
SpecialVisualisationParams,
SpecialVisualizationSvelte,
SpecialVisualizationUtils,
} from "../../SpecialVisualization"
import { AutoAction } from "../AutoApplyButtonVis" import { AutoAction } from "../AutoApplyButtonVis"
import { Feature, LineString, Polygon } from "geojson" import { Feature, LineString, Polygon } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource" import { UIEventSource } from "../../../Logic/UIEventSource"
import BaseUIElement from "../../BaseUIElement"
import { ImportFlowUtils } from "./ImportFlow" import { ImportFlowUtils } from "./ImportFlow"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import SvelteUIElement from "../../Base/SvelteUIElement" import SvelteUIElement from "../../Base/SvelteUIElement"
import WayImportFlow from "./WayImportFlow.svelte" import WayImportFlow from "./WayImportFlow.svelte"
import WayImportFlowState, { WayImportFlowArguments } from "./WayImportFlowState" import WayImportFlowState, { WayImportFlowArguments } from "./WayImportFlowState"
@ -15,11 +13,12 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../../Logic/Osm/Changes" import { Changes } from "../../../Logic/Osm/Changes"
import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource" import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource"
import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { OsmTags } from "../../../Models/OsmFeature"
/** /**
* Wrapper around 'WayImportFlow' to make it a special visualisation * Wrapper around 'WayImportFlow' to make it a special visualisation
*/ */
export default class WayImportButtonViz extends SpecialVisualizationSvelte implements AutoAction { export default class WayImportButtonViz extends SpecialVisualization implements AutoAction {
public readonly funcName: string = "import_way_button" public readonly funcName: string = "import_way_button"
needsUrls = [] needsUrls = []
group = "data_import" group = "data_import"
@ -61,20 +60,25 @@ export default class WayImportButtonViz extends SpecialVisualizationSvelte imple
public readonly supportsAutoAction = true public readonly supportsAutoAction = true
public readonly needsNodeDatabase = true public readonly needsNodeDatabase = true
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature,
_: LayerConfig
): BaseUIElement {
const geometry = feature.geometry const geometry = feature.geometry
if (!(geometry.type == "LineString" || geometry.type === "Polygon")) { if (!(geometry.type == "LineString" || geometry.type === "Polygon")) {
throw "Invalid type to import, expected linestring of polygon but got " + geometry.type throw "Invalid type to import " + geometry.type
} }
const parsedArgs: WayImportFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args) const args: WayImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
console.log("Parsed args are", parsedArgs) const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
const tagsToApply = ImportFlowUtils.getTagsToApply(tags, parsedArgs)
const importFlow = new WayImportFlowState( const importFlow = new WayImportFlowState(
state, state,
<Feature<LineString | Polygon>>feature, <Feature<LineString | Polygon>>feature,
parsedArgs, args,
tagsToApply, tagsToApply,
tags, tagSource
) )
return new SvelteUIElement(WayImportFlow, { return new SvelteUIElement(WayImportFlow, {
importFlow, importFlow,

View file

@ -1,49 +1,46 @@
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization" import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import BaseUIElement from "../../BaseUIElement"
import { UIEventSource } from "../../../Logic/UIEventSource"
import SvelteUIElement from "../../Base/SvelteUIElement" import SvelteUIElement from "../../Base/SvelteUIElement"
import { Feature } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { default as LanguageElementSvelte } from "./LanguageElement.svelte" import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
export class LanguageElement extends SpecialVisualizationSvelte { export class LanguageElement extends SpecialVisualization {
funcName: string = "language_chooser" funcName: string = "language_chooser"
needsUrls = [] needsUrls = []
docs: string = docs: string | BaseUIElement =
"The language element allows to show and pick all known (modern) languages. The key can be set" "The language element allows to show and pick all known (modern) languages. The key can be set"
args: { name: string; defaultValue?: string; doc: string; required?: boolean; type?: string }[] = [ args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
{ {
name: "key", name: "key",
required: true, required: true,
type:"key",
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked ", doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked ",
}, },
{ {
name: "question", name: "question",
required: true, required: true,
type: "translation",
doc: "What to ask if no questions are known", doc: "What to ask if no questions are known",
}, },
{ {
name: "render_list_item", name: "render_list_item",
type: "translation",
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).", doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
defaultValue: "{language()}", defaultValue: "{language()}",
}, },
{ {
name: "render_single_language", name: "render_single_language",
type: "translation",
doc: "What will be shown if the feature only supports a single language", doc: "What will be shown if the feature only supports a single language",
required: true, required: true,
}, },
{ {
type: "translation",
name: "render_all", name: "render_all",
doc: "The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single", doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}", defaultValue: "{list()}",
}, },
{ {
name: "no_known_languages", name: "no_known_languages",
type: "translation",
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead", doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
}, },
] ]
@ -62,16 +59,14 @@ export class LanguageElement extends SpecialVisualizationSvelte {
` `
constr( constr(
{ state: SpecialVisualizationState,
state, tagSource: UIEventSource<Record<string, string>>,
tags, argument: string[],
args, feature: Feature,
feature, layer: LayerConfig
layer, ): BaseUIElement {
}: SpecialVisualisationParams,
): SvelteUIElement {
let [key, question, item_render, single_render, all_render, on_no_known_languages] = let [key, question, item_render, single_render, all_render, on_no_known_languages] =
args argument
if (item_render === undefined || item_render.trim() === "") { if (item_render === undefined || item_render.trim() === "") {
item_render = "{language()}" item_render = "{language()}"
} }
@ -99,7 +94,7 @@ export class LanguageElement extends SpecialVisualizationSvelte {
return new SvelteUIElement(LanguageElementSvelte, { return new SvelteUIElement(LanguageElementSvelte, {
key, key,
tags, tags: tagSource,
state, state,
feature, feature,
layer, layer,

View file

@ -1,6 +1,7 @@
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import { ImmutableStore } from "../../Logic/UIEventSource" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { Feature } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import MapillaryLink from "../BigComponents/MapillaryLink.svelte" import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
@ -19,7 +20,12 @@ export class MapillaryLinkVis extends SpecialVisualizationSvelte {
}, },
] ]
public constr({ args, feature }: SpecialVisualisationParams): SvelteUIElement { public constr(
state: SpecialVisualizationState,
tagsSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
let zoom = Number(args[0]) let zoom = Number(args[0])
if (isNaN(zoom)) { if (isNaN(zoom)) {

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature, Geometry } from "geojson" import type { Feature } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
@ -8,23 +8,22 @@
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import MaplibreMap from "../Map/MaplibreMap.svelte" import MaplibreMap from "../Map/MaplibreMap.svelte"
import DelayedComponent from "../Base/DelayedComponent.svelte" import DelayedComponent from "../Base/DelayedComponent.svelte"
import type { OsmTags } from "../../Models/OsmFeature"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>> export let tagSource: UIEventSource<Record<string, string>>
export let defaultzoom: number export let defaultzoom: number
export let idkeys: string[] export let idkeys: string[]
export let feature: Feature export let feature: Feature
export let clss: string = "h-40 rounded" export let clss: string = "h-40 rounded"
const keys = idkeys const keys = idkeys
let featuresToShow: Store<Feature<Geometry, OsmTags>[]> = state.indexedFeatures.featuresById.map( let featuresToShow: Store<Feature[]> = state.indexedFeatures.featuresById.map(
(featuresById) => { (featuresById) => {
if (featuresById === undefined) { if (featuresById === undefined) {
return [] return []
} }
const properties = tags.data const properties = tagSource.data
const features: Feature<Geometry, OsmTags>[] = [] const features: Feature[] = []
for (const key of keys) { for (const key of keys) {
const value = properties[key] const value = properties[key]
if (value === undefined || value === null) { if (value === undefined || value === null) {
@ -45,13 +44,13 @@
console.warn("No feature found for id ", id) console.warn("No feature found for id ", id)
continue continue
} }
features.push(<Feature<Geometry, OsmTags>> feature) features.push(feature)
} }
} }
return features return features
}, },
[tags], [tagSource],
onDestroy, onDestroy
) )
let mlmap = new UIEventSource(undefined) let mlmap = new UIEventSource(undefined)
@ -71,7 +70,7 @@
mlmap, mlmap,
new StaticFeatureSource(featuresToShow), new StaticFeatureSource(featuresToShow),
state.theme.layers, state.theme.layers,
{ zoomToFeatures: true }, { zoomToFeatures: true }
) )
</script> </script>

View file

@ -1,6 +1,6 @@
import { Store } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { MultiApplyParams } from "./MultiApply" import { MultiApplyParams } from "./MultiApply"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import MultiApplyButton from "./MultiApplyButton.svelte" import MultiApplyButton from "./MultiApplyButton.svelte"
@ -19,9 +19,7 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features.", doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features.",
required: true, required: true,
}, },
{ name: "text", { name: "text", doc: "The text to show on the button" },
type: "translation",
doc: "The text to show on the button" },
{ {
name: "autoapply", name: "autoapply",
doc: "A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown", doc: "A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown",
@ -36,13 +34,17 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
example = example =
"{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}" "{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}"
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagsSource: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
const featureIdsKey = args[0] const featureIdsKey = args[0]
const keysToApply = args[1].split(";") const keysToApply = args[1].split(";")
const text = args[2] const text = args[2]
const autoapply = args[3]?.toLowerCase() === "true" const autoapply = args[3]?.toLowerCase() === "true"
const overwrite = args[4]?.toLowerCase() === "true" const overwrite = args[4]?.toLowerCase() === "true"
const featureIds: Store<string[]> = tags.map((tags) => { const featureIds: Store<string[]> = tagsSource.map((tags) => {
const ids = tags[featureIdsKey] const ids = tags[featureIdsKey]
try { try {
if (ids === undefined) { if (ids === undefined) {
@ -67,7 +69,7 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
text, text,
autoapply, autoapply,
overwrite, overwrite,
tagsSource: tags, tagsSource,
state, state,
} }
return new SvelteUIElement(MultiApplyButton, { params }) return new SvelteUIElement(MultiApplyButton, { params })

View file

@ -1,11 +1,11 @@
import { Store } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import Wikidata from "../../Logic/Web/Wikidata" import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import PlantNet from "../PlantNet/PlantNet.svelte" import PlantNet from "../PlantNet/PlantNet.svelte"
import { default as PlantNetCode } from "../../Logic/Web/PlantNet" import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
@ -31,13 +31,16 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte {
args = [ args = [
{ {
name: "image_key", name: "image_key",
type:"key",
defaultValue: AllImageProviders.defaultKeys.join(","), defaultValue: AllImageProviders.defaultKeys.join(","),
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ", doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
}, },
] ]
public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
let imagePrefixes: string[] = undefined let imagePrefixes: string[] = undefined
if (args.length > 0) { if (args.length > 0) {
imagePrefixes = [].concat(...args.map((a) => a.split(","))) imagePrefixes = [].concat(...args.map((a) => a.split(",")))

View file

@ -1,5 +1,6 @@
import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import ShareButton from "../Base/ShareButton.svelte" import ShareButton from "../Base/ShareButton.svelte"
@ -16,25 +17,25 @@ export class ShareLinkViz extends SpecialVisualizationSvelte {
}, },
{ {
name: "text", name: "text",
type:"translation",
doc: "The text to show on the button. If none is given, will act as a titleIcon", doc: "The text to show on the button. If none is given, will act as a titleIcon",
}, },
] ]
needsUrls = [] needsUrls = []
public constr({ public constr(
state, state: SpecialVisualizationState,
tags, tagSource: UIEventSource<Record<string, string>>,
args}:SpecialVisualisationParams args: string[]
) { ) {
const text = args[1] const text = args[1]
const generateShareData = () => { const generateShareData = () => {
const title = state?.theme?.title?.txt ?? "MapComplete" const title = state?.theme?.title?.txt ?? "MapComplete"
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tags?.data)
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tagSource?.data)
let name = let name =
matchingLayer?.title?.GetRenderValue(tags.data)?.Subs(tags.data)?.txt ?? matchingLayer?.title?.GetRenderValue(tagSource.data)?.Subs(tagSource.data)?.txt ??
tags.data?.name ?? tagSource.data?.name ??
"POI" "POI"
if (name) { if (name) {
name = `${name} (${title})` name = `${name} (${title})`

View file

@ -38,7 +38,7 @@
let key = "cached_special_spec_" + $language let key = "cached_special_spec_" + $language
specs = t[key] specs = t[key]
if (specs === undefined) { if (specs === undefined) {
specs = SpecialVisualizations.constructSpecification(txt) specs = SpecialVisualizations.constructSpecification(txt) ?? []
t[key] = specs t[key] = specs
} }
} }
@ -51,7 +51,7 @@
{ {
try { try {
return specpart.func return specpart.func
.constr({state, tags, args : specpart.args, feature, layer}) .constr(state, tags, specpart.args, feature, layer)
?.SetClass(specpart.style) ?.SetClass(specpart.style)
} catch (e) { } catch (e) {
console.error( console.error(
@ -69,8 +69,9 @@
} }
} }
</script> </script>
{#if specs === undefined}
{#if lang === "*"} <!-- Empty -->
{:else if lang === "*"}
{#each specs as specpart} {#each specs as specpart}
{#if typeof specpart === "string"} {#if typeof specpart === "string"}
<span class={clss}> <span class={clss}>

View file

@ -1,9 +1,4 @@
import { import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
@ -14,14 +9,18 @@ import { ServerSourceInfo } from "../../Models/SourceOverview"
/** /**
* Wrapper around 'UploadTraceToOsmUI' * Wrapper around 'UploadTraceToOsmUI'
*/ */
export class UploadToOsmViz extends SpecialVisualizationSvelte { export class UploadToOsmViz extends SpecialVisualization {
funcName = "upload_to_osm" funcName = "upload_to_osm"
docs = docs =
"Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."
args = [] args = []
needsUrls: ServerSourceInfo[] = [Constants.osmAuthConfig] needsUrls: ServerSourceInfo[] = [Constants.osmAuthConfig]
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
_: UIEventSource<Record<string, string>>,
__: string[]
) {
const locations = state.historicalUserLocations.features.data const locations = state.historicalUserLocations.features.data
return new SvelteUIElement(UploadTraceToOsmUI, { return new SvelteUIElement(UploadTraceToOsmUI, {
state, state,

View file

@ -1,5 +1,4 @@
import { import {
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState, SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
@ -48,7 +47,6 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
args = [ args = [
{ {
name: "message", name: "message",
type: "translation",
doc: "A message to show to the user", doc: "A message to show to the user",
}, },
{ {
@ -58,7 +56,6 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
}, },
{ {
name: "message_confirm", name: "message_confirm",
type: "translation",
doc: "What to show when the task is closed, either by the user or was already closed.", doc: "What to show when the task is closed, either by the user or was already closed.",
}, },
{ {
@ -68,19 +65,17 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
}, },
{ {
name: "maproulette_id", name: "maproulette_id",
type:"key",
doc: "The property name containing the maproulette id", doc: "The property name containing the maproulette id",
defaultValue: "mr_taskId", defaultValue: "mr_taskId",
}, },
{ {
name: "ask_feedback", name: "ask_feedback",
type: "translation",
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added", doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
defaultValue: "", defaultValue: "",
}, },
] ]
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(state, tagsSource, args) {
let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args
if (image === "") { if (image === "") {
image = "confirm" image = "confirm"
@ -91,7 +86,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
statusToSet = statusToSet ?? "1" statusToSet = statusToSet ?? "1"
return new SvelteUIElement(MaprouletteSetStatus, { return new SvelteUIElement(MaprouletteSetStatus, {
state, state,
tags, tags: tagsSource,
message, message,
image, image,
message_closed, message_closed,
@ -111,7 +106,6 @@ class LinkedDataFromWebsite extends SpecialVisualization {
{ {
name: "key", name: "key",
defaultValue: "website", defaultValue: "website",
type:"key",
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>", doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
}, },
{ {
@ -150,15 +144,21 @@ class LinkedDataFromWebsite extends SpecialVisualization {
}, },
] ]
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
if (state.theme.enableMorePrivacy) { if (state.theme.enableMorePrivacy) {
return undefined return undefined
} }
const key = args[0] ?? "website" const key = argument[0] ?? "website"
const useProxy = args[1] !== "no" const useProxy = argument[1] !== "no"
const readonly = args[3] === "readonly" const readonly = argument[3] === "readonly"
const isClosed = (args[4] ?? "yes") === "yes" const isClosed = (argument[4] ?? "yes") === "yes"
const downloadInformation = new UIEventSource(false) const downloadInformation = new UIEventSource(false)
const countryStore: Store<string | undefined> = tags.mapD((tags) => tags._country) const countryStore: Store<string | undefined> = tags.mapD((tags) => tags._country)
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => { const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
@ -240,7 +240,7 @@ class LinkedDataFromWebsite extends SpecialVisualization {
} }
} }
class CompareData extends SpecialVisualizationSvelte { class CompareData extends SpecialVisualization {
funcName = "compare_data" funcName = "compare_data"
group = "data_import" group = "data_import"
needsUrls = (args) => args[1].split(";") needsUrls = (args) => args[1].split(";")
@ -248,7 +248,6 @@ class CompareData extends SpecialVisualizationSvelte {
{ {
name: "url", name: "url",
required: true, required: true,
type:"key",
doc: "The attribute containing the url where to fetch more data", doc: "The attribute containing the url where to fetch more data",
}, },
{ {
@ -265,14 +264,20 @@ class CompareData extends SpecialVisualizationSvelte {
docs = docs =
"Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM" "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM"
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const url = args[0] const url = args[0]
const readonly = args[3] === "yes" const readonly = args[3] === "yes"
const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url)) const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url))
return new SvelteUIElement(ComparisonTool, { return new SvelteUIElement(ComparisonTool, {
url, url,
state, state,
tags, tags: tagSource,
layer, layer,
feature, feature,
readonly, readonly,
@ -281,7 +286,7 @@ class CompareData extends SpecialVisualizationSvelte {
} }
} }
export class DataImportSpecialVisualisations { export class DataImportSpecialVisualisations {
public static initList(): SpecialVisualizationSvelte[] { public static initList(): (SpecialVisualization & { group })[] {
return [ return [
new TagApplyViz(), new TagApplyViz(),
new PointImportButtonViz(), new PointImportButtonViz(),

View file

@ -1,4 +1,7 @@
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte" import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte"
import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte" import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte"
@ -11,8 +14,19 @@ class FavouriteStatus extends SpecialVisualizationSvelte {
args = [] args = []
group = "favourites" group = "favourites"
constr(params: SpecialVisualisationParams): SvelteUIElement { constr(
return new SvelteUIElement(MarkAsFavourite, params) state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource,
state,
layer,
feature,
})
} }
} }
@ -23,8 +37,19 @@ class FavouriteIcon extends SpecialVisualizationSvelte {
"A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon" "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon"
args = [] args = []
constr(params: SpecialVisualisationParams): SvelteUIElement { constr(
return new SvelteUIElement(MarkAsFavouriteMini, params) state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavouriteMini, {
tags: tagSource,
state,
layer,
feature,
})
} }
} }

View file

@ -1,9 +1,12 @@
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import ImageCarousel from "../Image/ImageCarousel.svelte" import ImageCarousel from "../Image/ImageCarousel.svelte"
import UploadImage from "../Image/UploadImage.svelte" import UploadImage from "../Image/UploadImage.svelte"
import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch" import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import NearbyImages from "../Image/NearbyImages.svelte" import NearbyImages from "../Image/NearbyImages.svelte"
import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte" import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte"
@ -29,7 +32,13 @@ class NearbyImageVis extends SpecialVisualizationSvelte {
funcName = "nearby_images" funcName = "nearby_images"
needsUrls = CombinedFetcher.apiUrls needsUrls = CombinedFetcher.apiUrls
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const isOpen = args[0] === "open" const isOpen = args[0] === "open"
const readonly = args[1] === "readonly" || args[1] === "yes" const readonly = args[1] === "readonly" || args[1] === "yes"
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
@ -60,7 +69,7 @@ class ImageCarouselVis extends SpecialVisualizationSvelte {
] ]
needsUrls = AllImageProviders.apiUrls needsUrls = AllImageProviders.apiUrls
constr({state, tags, args, feature}: SpecialVisualisationParams) { constr(state, tags, args, feature) {
let imagePrefixes: string[] = undefined let imagePrefixes: string[] = undefined
if (args.length > 0) { if (args.length > 0) {
imagePrefixes = [].concat(...args.map((a) => a.split(";"))) imagePrefixes = [].concat(...args.map((a) => a.split(";")))
@ -86,8 +95,8 @@ class ImageUpload extends SpecialVisualizationSvelte {
needsUrls = [Constants.panoramax, Constants.osmAuthConfig] needsUrls = [Constants.panoramax, Constants.osmAuthConfig]
args = [ args = [
{ {
name: "image_key",
type: "key", type: "key",
name: "image_key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
defaultValue: "panoramax", defaultValue: "panoramax",
required: false, required: false,
@ -96,7 +105,6 @@ class ImageUpload extends SpecialVisualizationSvelte {
name: "label", name: "label",
doc: "The text to show on the button", doc: "The text to show on the button",
required: false, required: false,
type: "translation"
}, },
{ {
name: "disable_blur", name: "disable_blur",
@ -105,7 +113,7 @@ class ImageUpload extends SpecialVisualizationSvelte {
}, },
] ]
constr({state, tags, args, feature}: SpecialVisualisationParams) { constr(state, tags, args, feature) {
const targetKey = args[0] === "" ? undefined : args[0] const targetKey = args[0] === "" ? undefined : args[0]
const noBlur = args[3]?.toLowerCase()?.trim() const noBlur = args[3]?.toLowerCase()?.trim()
return new SvelteUIElement(UploadImage, { return new SvelteUIElement(UploadImage, {

View file

@ -1,4 +1,8 @@
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson" import { Feature } from "geojson"
@ -24,7 +28,6 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
{ {
name: "text", name: "text",
doc: "Text to show on this button", doc: "Text to show on this button",
type: "translation",
required: true, required: true,
}, },
{ {
@ -36,7 +39,6 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
name: "idkey", name: "idkey",
doc: "The property name where the ID of the note to close can be found", doc: "The property name where the ID of the note to close can be found",
defaultValue: "id", defaultValue: "id",
type:"key"
}, },
{ {
name: "comment", name: "comment",
@ -53,7 +55,11 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
] ]
public readonly group = "notes" public readonly group = "notes"
public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs(
this.args, this.args,
args args
@ -81,12 +87,14 @@ class AddNoteCommentViz extends SpecialVisualizationSvelte {
name: "Id-key", name: "Id-key",
doc: "The property name where the ID of the note to close can be found", doc: "The property name where the ID of the note to close can be found",
defaultValue: "id", defaultValue: "id",
type:"key"
}, },
] ]
public readonly group = "notes" public readonly group = "notes"
public constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>
): SvelteUIElement {
return new SvelteUIElement(AddNoteComment, { state, tags }) return new SvelteUIElement(AddNoteComment, { state, tags })
} }
} }
@ -99,8 +107,13 @@ class OpenNote extends SpecialVisualizationSvelte {
docs = docs =
"Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled" "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled"
constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { constr(
const [lon, lat] = GeoOperations.centerpointCoordinates(<Feature>feature) state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, { return new SvelteUIElement(CreateNewNote, {
state, state,
coordinate: new UIEventSource({ lon, lat }), coordinate: new UIEventSource({ lon, lat }),
@ -116,13 +129,12 @@ class AddImageToNote extends SpecialVisualizationSvelte {
name: "Id-key", name: "Id-key",
doc: "The property name where the ID of the note to close can be found", doc: "The property name where the ID of the note to close can be found",
defaultValue: "id", defaultValue: "id",
type:"key"
}, },
] ]
group = "notes" group = "notes"
needsUrls = [] needsUrls = []
constr({ state, tags, args, feature }: SpecialVisualisationParams) { constr(state, tags, args, feature) {
const id = tags.data[args[0] ?? "id"] const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id) tags = state.featureProperties.getStore(id)
return new SvelteUIElement(UploadImage, { state, tags, feature }) return new SvelteUIElement(UploadImage, { state, tags, feature })
@ -138,7 +150,6 @@ class VisualiseNoteComment extends SpecialVisualization {
name: "commentsKey", name: "commentsKey",
doc: "The property name of the comments, which should be stringified json", doc: "The property name of the comments, which should be stringified json",
defaultValue: "comments", defaultValue: "comments",
type:"key"
}, },
{ {
name: "start", name: "start",
@ -148,7 +159,7 @@ class VisualiseNoteComment extends SpecialVisualization {
] ]
needsUrls = [Constants.osmAuthConfig] needsUrls = [Constants.osmAuthConfig]
constr({ state, tags, args }: SpecialVisualisationParams) { constr(state, tags, args) {
return new VariableUiElement( return new VariableUiElement(
tags tags
.map((tags) => tags[args[0]]) .map((tags) => tags[args[0]])
@ -175,7 +186,7 @@ class VisualiseNoteComment extends SpecialVisualization {
} }
export class NoteVisualisations { export class NoteVisualisations {
public static initList(): SpecialVisualization[] { public static initList(): (SpecialVisualization & { group })[] {
return [ return [
new AddNoteCommentViz(), new AddNoteCommentViz(),
new CloseNoteViz(), new CloseNoteViz(),

View file

@ -1,5 +1,4 @@
import { import {
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState, SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
@ -17,7 +16,6 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import { ServerSourceInfo } from "../../Models/SourceOverview" import { ServerSourceInfo } from "../../Models/SourceOverview"
import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState"
class CreateReview extends SpecialVisualizationSvelte { class CreateReview extends SpecialVisualizationSvelte {
public static MangroveReviewInfo: ServerSourceInfo = { public static MangroveReviewInfo: ServerSourceInfo = {
@ -45,7 +43,6 @@ class CreateReview extends SpecialVisualizationSvelte {
{ {
name: "subjectKey", name: "subjectKey",
defaultValue: "name", defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>", doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
}, },
{ {
@ -54,13 +51,11 @@ class CreateReview extends SpecialVisualizationSvelte {
}, },
{ {
name: "question", name: "question",
type: "translation",
doc: "The question to ask during the review", doc: "The question to ask during the review",
}, },
] ]
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { constr(state, tags, args, feature, layer) {
const nameKey = args[0] ?? "name" const nameKey = args[0] ?? "name"
const fallbackName = args[1] const fallbackName = args[1]
const question = args[2] const question = args[2]
@ -72,7 +67,7 @@ class CreateReview extends SpecialVisualizationSvelte {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
}, },
<SpecialVisualizationState & WithUserRelatedState>state, state
) )
return new SvelteUIElement(ReviewForm, { return new SvelteUIElement(ReviewForm, {
reviews, reviews,
@ -96,7 +91,6 @@ class ListReview extends SpecialVisualizationSvelte {
{ {
name: "subjectKey", name: "subjectKey",
defaultValue: "name", defaultValue: "name",
type: "key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>", doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
}, },
{ {
@ -105,7 +99,7 @@ class ListReview extends SpecialVisualizationSvelte {
}, },
] ]
constr({ state, tags, args, feature }: SpecialVisualisationParams) { constr(state, tags, args, feature) {
const nameKey = args[0] ?? "name" const nameKey = args[0] ?? "name"
const fallbackName = args[1] const fallbackName = args[1]
const reviews = FeatureReviews.construct( const reviews = FeatureReviews.construct(
@ -131,7 +125,6 @@ class Rating extends SpecialVisualizationSvelte {
{ {
name: "subjectKey", name: "subjectKey",
defaultValue: "name", defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews.", doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews.",
}, },
{ {
@ -140,7 +133,7 @@ class Rating extends SpecialVisualizationSvelte {
}, },
] ]
constr({ state, tags, args, feature }: SpecialVisualisationParams) { constr(state, tags, args, feature) {
const nameKey = args[0] ?? "name" const nameKey = args[0] ?? "name"
const fallbackName = args[1] const fallbackName = args[1]
const reviews = FeatureReviews.construct( const reviews = FeatureReviews.construct(
@ -168,13 +161,16 @@ class ImportMangroveKey extends SpecialVisualizationSvelte {
args = [ args = [
{ {
name: "text", name: "text",
type: "translation",
doc: "The text that is shown on the button", doc: "The text that is shown on the button",
}, },
] ]
constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { constr(
const [text] = args state: SpecialVisualizationState,
_: UIEventSource<Record<string, string>>,
argument: string[]
): SvelteUIElement {
const [text] = argument
return new SvelteUIElement(ImportReviewIdentity, { state, text }) return new SvelteUIElement(ImportReviewIdentity, { state, text })
} }
} }
@ -190,7 +186,6 @@ class Reviews extends SpecialVisualization {
{ {
name: "subjectKey", name: "subjectKey",
defaultValue: "name", defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>", doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
}, },
{ {
@ -199,22 +194,27 @@ class Reviews extends SpecialVisualization {
}, },
{ {
name: "question", name: "question",
type: "translation",
doc: "The question to ask in the review form. Optional", doc: "The question to ask in the review form. Optional",
}, },
] ]
needsUrls = [CreateReview.MangroveReviewInfo] needsUrls = [CreateReview.MangroveReviewInfo]
constr(params: SpecialVisualisationParams): BaseUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
return new Combine([ return new Combine([
new CreateReview().constr(params), new CreateReview().constr(state, tagSource, args, feature, layer),
new ListReview().constr(params), new ListReview().constr(state, tagSource, args, feature),
]) ])
} }
} }
export class ReviewSpecialVisualisations { export class ReviewSpecialVisualisations {
public static initList(): SpecialVisualization[] { public static initList(): (SpecialVisualization & { group })[] {
return [ return [
new Rating(), new Rating(),
new CreateReview(), new CreateReview(),

View file

@ -1,8 +1,4 @@
import { import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
SpecialVisualisationParams,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import LogoutButton from "../Base/LogoutButton.svelte" import LogoutButton from "../Base/LogoutButton.svelte"
@ -29,7 +25,7 @@ class LanguagePickerVis extends SpecialVisualizationSvelte {
group = "settings" group = "settings"
docs = "A component to set the language of the user interface" docs = "A component to set the language of the user interface"
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState): SvelteUIElement {
const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) => const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) =>
showTranslations showTranslations
? LanguageUtils.usedLanguagesSorted ? LanguageUtils.usedLanguagesSorted
@ -72,7 +68,7 @@ class GpsAllTags extends SpecialVisualizationSvelte {
docs = "Shows the current tags of the GPS-representing object, used for debugging" docs = "Shows the current tags of the GPS-representing object, used for debugging"
args = [] args = []
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState): SvelteUIElement {
const tags = (<ThemeViewState>state).geolocation.currentUserLocation.features.map( const tags = (<ThemeViewState>state).geolocation.currentUserLocation.features.map(
(features) => features[0]?.properties (features) => features[0]?.properties
) )
@ -89,7 +85,7 @@ class StorageAllTags extends SpecialVisualizationSvelte {
docs = "Shows the current state of storage" docs = "Shows the current state of storage"
args = [] args = []
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState): SvelteUIElement {
const data = {} const data = {}
for (const key in localStorage) { for (const key in localStorage) {
data[key] = localStorage[key] data[key] = localStorage[key]
@ -115,15 +111,18 @@ export class ClearCachesVis extends SpecialVisualizationSvelte {
{ {
name: "text", name: "text",
required: true, required: true,
type: "translation",
doc: "The text to show on the button", doc: "The text to show on the button",
}, },
] ]
group = "settings" group = "settings"
constr({ args }: SpecialVisualisationParams): SvelteUIElement { constr(
_: SpecialVisualizationState,
__: UIEventSource<Record<string, string>>,
argument: string[]
): SvelteUIElement {
return new SvelteUIElement(ClearCaches, { return new SvelteUIElement(ClearCaches, {
msg: args[0] ?? "Clear local caches", msg: argument[0] ?? "Clear local caches",
}) })
} }
} }
@ -137,14 +136,13 @@ class LoginButtonVis extends SpecialVisualizationSvelte {
}, },
{ {
name: "message", name: "message",
type: "translation",
doc: "Message to display on the button", doc: "Message to display on the button",
}, },
] ]
docs = "Show a login button" docs = "Show a login button"
group = "settings" group = "settings"
constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState, _, args): SvelteUIElement {
const force = args[0].toLowerCase() const force = args[0].toLowerCase()
let msg = args[1] let msg = args[1]
if (msg === "") { if (msg === "") {
@ -162,7 +160,6 @@ class QrLogin extends SpecialVisualizationSvelte {
funcName = "qr_login" funcName = "qr_login"
args = [ args = [
{ {
type: "translation",
name: "text", name: "text",
doc: "Extra text on the side of the QR-code", doc: "Extra text on the side of the QR-code",
}, },
@ -175,10 +172,16 @@ class QrLogin extends SpecialVisualizationSvelte {
"A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out" "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out"
group = "settings" group = "settings"
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const shared_oauth_cookie = state.osmConnection.getToken() const shared_oauth_cookie = state.osmConnection.getToken()
const sideText = args[0] const sideText = argument[0]
const sideTextClass = args[1] ?? "" const sideTextClass = argument[1] ?? ""
return new SvelteUIElement(QrCode, { return new SvelteUIElement(QrCode, {
state, state,
tags, tags,
@ -196,8 +199,8 @@ class Logout extends SpecialVisualizationSvelte {
docs = "Shows a button where the user can log out" docs = "Shows a button where the user can log out"
group = "settings" group = "settings"
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(LogoutButton,state) return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
} }
} }
@ -207,7 +210,7 @@ class PendingChanges extends SpecialVisualizationSvelte {
group = "settings" group = "settings"
args = [] args = []
constr({ state }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false }) return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
} }
} }
@ -218,8 +221,8 @@ class ClearLocationHistoryVis extends SpecialVisualizationSvelte {
docs = "A button to remove the travelled track information from the device" docs = "A button to remove the travelled track information from the device"
args = [] args = []
constr(params: SpecialVisualisationParams) { constr(state) {
return new SvelteUIElement(ClearGPSHistory, params) return new SvelteUIElement(ClearGPSHistory, { state })
} }
} }
@ -227,6 +230,7 @@ export class SettingsVisualisations {
public static initList(): SpecialVisualizationSvelte[] { public static initList(): SpecialVisualizationSvelte[] {
return [ return [
new LanguagePickerVis(), new LanguagePickerVis(),
new DisabledQuestionsVis(), new DisabledQuestionsVis(),
new GyroscopeAllTags(), new GyroscopeAllTags(),
new GpsAllTags(), new GpsAllTags(),

View file

@ -1,5 +1,5 @@
import { AutoAction } from "../Popup/AutoApplyButtonVis" import { AutoAction } from "../Popup/AutoApplyButtonVis"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
@ -26,7 +26,6 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
}, },
{ {
name: "message", name: "message",
type:"translation",
doc: "The text to show to the contributor", doc: "The text to show to the contributor",
}, },
{ {
@ -174,7 +173,12 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
} }
} }
public constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
const tagsToApply: Store<Tag[]> = TagApplyViz.generateTagsToApply(args[0], tags) const tagsToApply: Store<Tag[]> = TagApplyViz.generateTagsToApply(args[0], tags)
const msg = args[1] const msg = args[1]
let image = args[2]?.trim() let image = args[2]?.trim()

View file

@ -1,5 +1,4 @@
import { import {
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState, SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
@ -28,7 +27,6 @@ class StealViz extends SpecialVisualization {
args = [ args = [
{ {
name: "featureId", name: "featureId",
type:"key",
doc: "The key of the attribute which contains the id of the feature from which to use the tags", doc: "The key of the attribute which contains the id of the feature from which to use the tags",
required: true, required: true,
}, },
@ -40,7 +38,7 @@ class StealViz extends SpecialVisualization {
] ]
needsUrls = [] needsUrls = []
constr({ state, tags, args }: SpecialVisualisationParams) { constr(state: SpecialVisualizationState, featureTags, args) {
const [featureIdKey, layerAndtagRenderingIds] = args const [featureIdKey, layerAndtagRenderingIds] = args
const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
@ -53,7 +51,7 @@ class StealViz extends SpecialVisualization {
throw "Could not create stolen tagrenddering: tagRenderings not found" throw "Could not create stolen tagrenddering: tagRenderings not found"
} }
return new VariableUiElement( return new VariableUiElement(
tags.map( featureTags.map(
(tags) => { (tags) => {
const featureId = tags[featureIdKey] const featureId = tags[featureIdKey]
if (featureId === undefined) { if (featureId === undefined) {
@ -119,7 +117,6 @@ class Multi extends SpecialVisualization {
args = [ args = [
{ {
name: "key", name: "key",
type:"key",
doc: "The property to read and to interpret as a list of properties", doc: "The property to read and to interpret as a list of properties",
required: true, required: true,
}, },
@ -134,12 +131,18 @@ class Multi extends SpecialVisualization {
}, },
] ]
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { constr(
state: SpecialVisualizationState,
featureTags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
) {
const [key, tr, classesRaw] = args const [key, tr, classesRaw] = args
const classes = classesRaw ?? "" const classes = classesRaw ?? ""
const translation = new Translation({ "*": tr }) const translation = new Translation({ "*": tr })
return new VariableUiElement( return new VariableUiElement(
tags.map((tags) => { featureTags.map((tags) => {
let properties: object[] let properties: object[]
if (typeof tags[key] === "string") { if (typeof tags[key] === "string") {
properties = JSON.parse(tags[key]) properties = JSON.parse(tags[key])
@ -193,14 +196,20 @@ class Group extends SpecialVisualizationSvelte {
}, },
] ]
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
const [header, labelsStr, blacklistStr] = args state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
selectedElement: Feature,
layer: LayerConfig
): SvelteUIElement {
const [header, labelsStr, blacklistStr] = argument
const labels = labelsStr.split(";").map((x) => x.trim()) const labels = labelsStr.split(";").map((x) => x.trim())
const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? [] const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? []
return new SvelteUIElement(GroupedView, { return new SvelteUIElement(GroupedView, {
state, state,
tags, tags,
selectedElement: feature, selectedElement,
layer, layer,
header, header,
labels, labels,
@ -213,19 +222,19 @@ class OpenInId extends SpecialVisualizationSvelte {
funcName = "open_in_iD" funcName = "open_in_iD"
docs = "Opens the current view in the iD-editor" docs = "Opens the current view in the iD-editor"
args = [] args = []
group = "web_and_communication" group = "tagrendering_manipulation"
constr({state, feature}: SpecialVisualisationParams): SvelteUIElement { constr(state, feature): SvelteUIElement {
return new SvelteUIElement(OpenIdEditor, { return new SvelteUIElement(OpenIdEditor, {
mapProperties: state.mapProperties, mapProperties: state.mapProperties,
objectId: feature.properties.id, objectId: feature.data.id,
}) })
} }
} }
class OpenInJosm extends SpecialVisualizationSvelte { class OpenInJosm extends SpecialVisualizationSvelte {
funcName = "open_in_josm" funcName = "open_in_josm"
group = "web_and_communication" group = "tagrendering_manipulation"
docs = "Opens the current view in the JOSM-editor" docs = "Opens the current view in the JOSM-editor"
args = [] args = []
needsUrls = [ needsUrls = [
@ -243,12 +252,12 @@ class OpenInJosm extends SpecialVisualizationSvelte {
}, },
] ]
constr(params: SpecialVisualisationParams): SvelteUIElement { constr(state): SvelteUIElement {
return new SvelteUIElement(OpenJosm, params) return new SvelteUIElement(OpenJosm, { state })
} }
} }
export default class TagrenderingManipulationSpecialVisualisations { export default class TagrenderingManipulationSpecialVisualisations {
public static initList(): SpecialVisualization[] { public static initList(): (SpecialVisualization & { group })[] {
return [new StealViz(), new Multi(), new Group(), new OpenInId(), new OpenInJosm()] return [new StealViz(), new Multi(), new Group(), new OpenInId(), new OpenInJosm()]
} }
} }

View file

@ -1,5 +1,4 @@
import { import {
SpecialVisualisationParams,
SpecialVisualization, SpecialVisualization,
SpecialVisualizationState, SpecialVisualizationState,
SpecialVisualizationSvelte, SpecialVisualizationSvelte,
@ -50,7 +49,13 @@ class QuestionViz extends SpecialVisualizationSvelte {
] ]
group = "default" group = "default"
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const labels = args[0] const labels = args[0]
?.split(";") ?.split(";")
?.map((s) => s.trim()) ?.map((s) => s.trim())
@ -94,7 +99,6 @@ class Minimap extends SpecialVisualizationSvelte {
doc: "The key of one or more properties of the feature, semi-colon separated. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. ", doc: "The key of one or more properties of the feature, semi-colon separated. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. ",
name: "idKey", name: "idKey",
defaultValue: "id", defaultValue: "id",
type:"key"
}, },
{ {
name: "class", name: "class",
@ -105,7 +109,12 @@ class Minimap extends SpecialVisualizationSvelte {
example = example =
"`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`" "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
const minzoom = Number(args[0] ?? 18) const minzoom = Number(args[0] ?? 18)
const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"] const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"]
const clss = args[2] const clss = args[2]
@ -115,7 +124,7 @@ class Minimap extends SpecialVisualizationSvelte {
idkeys: ids, idkeys: ids,
clss, clss,
feature, feature,
tags, tagSource,
}) })
} }
} }
@ -126,9 +135,12 @@ class SplitButton extends SpecialVisualizationSvelte {
args = [] args = []
group = "default" group = "default"
constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
): SvelteUIElement {
return new SvelteUIElement(SplitRoadWizard, { return new SvelteUIElement(SplitRoadWizard, {
id: tags.map((pr) => pr.id), id: tagSource.map((pr) => pr.id),
state, state,
}) })
} }
@ -141,7 +153,13 @@ class MoveButton extends SpecialVisualizationSvelte {
args = [] args = []
group = "default" group = "default"
constr({ state, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
if (feature.geometry.type !== "Point") { if (feature.geometry.type !== "Point") {
return undefined return undefined
} }
@ -161,12 +179,18 @@ class DeleteButton extends SpecialVisualizationSvelte {
args = [] args = []
group = "default" group = "default"
constr({ state, tags, feature, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
if (!layer.deletion) { if (!layer.deletion) {
return undefined return undefined
} }
return new SvelteUIElement(DeleteWizard, { return new SvelteUIElement(DeleteWizard, {
tags, tags: tagSource,
deleteConfig: layer.deletion, deleteConfig: layer.deletion,
state, state,
feature, feature,
@ -179,7 +203,7 @@ class QrCodeVis extends SpecialVisualizationSvelte {
funcName = "qr_code" funcName = "qr_code"
args = [ args = [
{ {
name: "text",type:"translation", name: "text",
doc: "Extra text on the side of the QR-code", doc: "Extra text on the side of the QR-code",
}, },
{ {
@ -190,9 +214,14 @@ class QrCodeVis extends SpecialVisualizationSvelte {
group = "default" group = "default"
docs = "Generates a QR-code to share the selected object" docs = "Generates a QR-code to share the selected object"
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { constr(
const sideText = args[0] state: SpecialVisualizationState,
const sideTextClass = args[1] ?? "" tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
): SvelteUIElement {
const sideText = argument[0]
const sideTextClass = argument[1] ?? ""
return new SvelteUIElement(QrCode, { return new SvelteUIElement(QrCode, {
state, state,
tags, tags,
@ -209,7 +238,6 @@ class IfNothingKnown extends SpecialVisualizationSvelte {
{ {
name: "text", name: "text",
doc: "Text to show", doc: "Text to show",
type:"translation",
required: true, required: true,
}, },
{ name: "cssClasses", doc: "Classes to apply onto the text" }, { name: "cssClasses", doc: "Classes to apply onto the text" },
@ -218,12 +246,18 @@ class IfNothingKnown extends SpecialVisualizationSvelte {
docs = docs =
"Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question" "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question"
constr({ state, tags, args, layer }: SpecialVisualisationParams): SvelteUIElement { constr(
const text = args[0] state: SpecialVisualizationState,
const cssClasses = args[1] tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const text = argument[0]
const cssClasses = argument[1]
return new SvelteUIElement(NothingKnown, { return new SvelteUIElement(NothingKnown, {
state, state,
tags, tags: tagSource,
layer, layer,
text, text,
cssClasses, cssClasses,
@ -238,7 +272,7 @@ class AddNewPointVis extends SpecialVisualizationSvelte {
args = [] args = []
group = "default" group = "default"
constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { constr(state: SpecialVisualizationState, _, __, feature: GeoJSON): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(AddNewPoint, { return new SvelteUIElement(AddNewPoint, {
state, state,
@ -261,10 +295,14 @@ class Translated extends SpecialVisualization {
}, },
] ]
constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[]
): BaseUIElement {
return new VariableUiElement( return new VariableUiElement(
tags.map((tags) => { tagSource.map((tags) => {
const v = tags[args[0] ?? "value"] const v = tags[argument[0] ?? "value"]
try { try {
const tr = typeof v === "string" ? JSON.parse(v) : v const tr = typeof v === "string" ? JSON.parse(v) : v
return new Translation(tr).SetClass("font-bold") return new Translation(tr).SetClass("font-bold")
@ -287,8 +325,14 @@ class TitleVis extends SpecialVisualizationSvelte {
example = example =
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`." "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`."
constr(params: SpecialVisualisationParams): SvelteUIElement { constr(
return new SvelteUIElement(FeatureTitle, params) state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
_: string[],
feature: Feature,
layer: LayerConfig
) {
return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer })
} }
} }
@ -302,11 +346,16 @@ class BracedVis extends SpecialVisualization {
name: "text", name: "text",
required: true, required: true,
doc: "The value to show", doc: "The value to show",
type:"translation"
}, },
] ]
constr({ args }: SpecialVisualisationParams): BaseUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
return new FixedUiElement("{" + args[0] + "}") return new FixedUiElement("{" + args[0] + "}")
} }
} }
@ -316,9 +365,19 @@ class CreateCopyVis extends SpecialVisualizationSvelte {
funcName = "create_copy" funcName = "create_copy"
docs = "Allow to create a copy of the current element" docs = "Allow to create a copy of the current element"
args = [] args = []
constr(
constr(params: SpecialVisualisationParams): SvelteUIElement { state: SpecialVisualizationState,
return new SvelteUIElement(CreateCopy, params) tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
try {
console.log(">>> create_copy invoked")
return new SvelteUIElement(CreateCopy, { state, tags, argument, feature, layer })
} catch (e) {
console.error(">>> failed", e)
}
} }
} }

View file

@ -1,5 +1,10 @@
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import {
import { ImmutableStore, Store } from "../../Logic/UIEventSource" SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import SvelteUIElement from "../Base/SvelteUIElement" import SvelteUIElement from "../Base/SvelteUIElement"
import FediverseLink from "../Popup/FediverseLink.svelte" import FediverseLink from "../Popup/FediverseLink.svelte"
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata" import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
@ -13,33 +18,35 @@ import SendEmail from "../Popup/SendEmail.svelte"
import DynLink from "../Base/DynLink.svelte" import DynLink from "../Base/DynLink.svelte"
import { Lists } from "../../Utils/Lists" import { Lists } from "../../Utils/Lists"
class FediverseLinkVis extends SpecialVisualizationSvelte { class FediverseLinkVis extends SpecialVisualization {
funcName = "fediverse_link" funcName = "fediverse_link"
group = "web_and_communication" group = "web_and_communication"
docs = "Converts a fediverse username or link into a clickable link" docs = "Converts a fediverse username or link into a clickable link"
args = [ args = [
{ {
name: "key", name: "key",
type:"key",
doc: "The attribute-name containing the link", doc: "The attribute-name containing the link",
required: true, required: true,
}, },
] ]
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
const key = args[0] state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[]
): BaseUIElement {
const key = argument[0]
return new SvelteUIElement(FediverseLink, { key, tags, state }) return new SvelteUIElement(FediverseLink, { key, tags, state })
} }
} }
class WikipediaVis extends SpecialVisualizationSvelte { class WikipediaVis extends SpecialVisualization {
funcName = "wikipedia" funcName = "wikipedia"
group = "web_and_communication" group = "web_and_communication"
docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag." docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag."
args = [ args = [
{ {
name: "keyToShowWikipediaFor", name: "keyToShowWikipediaFor",
type:"key",
doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used", doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used",
defaultValue: "wikidata;wikipedia", defaultValue: "wikidata;wikipedia",
}, },
@ -49,9 +56,9 @@ class WikipediaVis extends SpecialVisualizationSvelte {
example = example =
"`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height" "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height"
constr({ tags, args }: SpecialVisualisationParams) { constr(_, tagsSource, args) {
const keys = args[0].split(";").map((k) => k.trim()) const keys = args[0].split(";").map((k) => k.trim())
const wikiIds: Store<string[]> = tags.map((tags) => { const wikiIds: Store<string[]> = tagsSource.map((tags) => {
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") 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()) ?? []
}) })
@ -69,7 +76,6 @@ class WikidatalabelVis extends SpecialVisualization {
args = [ args = [
{ {
name: "keyToShowWikidataFor", name: "keyToShowWikidataFor",
type:"key",
doc: "Use the wikidata entry from this key to show the label", doc: "Use the wikidata entry from this key to show the label",
defaultValue: "wikidata", defaultValue: "wikidata",
}, },
@ -79,8 +85,8 @@ class WikidatalabelVis extends SpecialVisualization {
example = example =
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself" "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself"
constr({ tags, args }: SpecialVisualisationParams) { constr(_, tagsSource, args) {
const id = tags const id = tagsSource
.map((tags) => tags[args[0]]) .map((tags) => tags[args[0]])
.map((wikidata) => { .map((wikidata) => {
const wikidataIds = Lists.noEmpty( const wikidataIds = Lists.noEmpty(
@ -111,32 +117,28 @@ class SendEmailVis extends SpecialVisualizationSvelte {
{ {
name: "to", name: "to",
doc: "Who to send the email to?", doc: "Who to send the email to?",
type:"key",
required: true, required: true,
}, },
{ {
name: "subject", name: "subject",
type: "translation",
doc: "The subject of the email", doc: "The subject of the email",
required: true, required: true,
}, },
{ {
name: "body", name: "body",
type: "translation",
doc: "The text in the email", doc: "The text in the email",
required: true, required: true,
}, },
{ {
name: "button_text", name: "button_text",
type: "translation",
doc: "The text shown on the button in the UI", doc: "The text shown on the button in the UI",
required: true, required: true,
}, },
] ]
constr(params: SpecialVisualisationParams) { constr(__, tags, args) {
return new SvelteUIElement(SendEmail, params) return new SvelteUIElement(SendEmail, { args, tags })
} }
} }
@ -149,12 +151,11 @@ class LinkVis extends SpecialVisualizationSvelte {
{ {
name: "text", name: "text",
doc: "Text to be shown", doc: "Text to be shown",
type: "translation",
required: true, required: true,
}, },
{ {
name: "href", name: "href",
doc: "The URL to link to. Note that this will be URI-encoded before and (as everything) supports substitutions of attributes", doc: "The URL to link to. Note that this will be URI-encoded before ",
required: true, required: true,
}, },
{ {
@ -175,27 +176,31 @@ class LinkVis extends SpecialVisualizationSvelte {
}, },
] ]
constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
let [text, href, classnames, download, ariaLabel, icon] = args let [text, href, classnames, download, ariaLabel, icon] = args
if (download === "") { if (download === "") {
download = undefined download = undefined
} }
const newTab = download === undefined && !href.startsWith("#") const newTab = download === undefined && !href.startsWith("#")
const textStore = tags.map((tags) => Utils.SubstituteKeys(text, tags)) const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags))
const hrefStore = tags.map((tags) => Utils.SubstituteKeys(href, tags)) const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags))
return new SvelteUIElement(DynLink, { return new SvelteUIElement(DynLink, {
text: textStore, text: textStore,
href: hrefStore, href: hrefStore,
classnames: new ImmutableStore(classnames), classnames: new ImmutableStore(classnames),
download: tags.map((tags) => Utils.SubstituteKeys(download, tags)), download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
ariaLabel: tags.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)), ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
newTab: new ImmutableStore(newTab), newTab: new ImmutableStore(newTab),
icon: tags.map((tags) => Utils.SubstituteKeys(icon, tags)), icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
}) })
} }
} }
export class WebAndCommunicationSpecialVisualisations { export class WebAndCommunicationSpecialVisualisations {
public static initList(): SpecialVisualization[] { public static initList(): (SpecialVisualization & { group })[] {
return [ return [
new FediverseLinkVis(), new FediverseLinkVis(),
new WikipediaVis(), new WikipediaVis(),

View file

@ -28,7 +28,6 @@ import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropert
import SvelteUIElement from "./Base/SvelteUIElement" import SvelteUIElement from "./Base/SvelteUIElement"
import { Utils } from "../Utils" import { Utils } from "../Utils"
import { ServerSourceInfo } from "../Models/SourceOverview" import { ServerSourceInfo } from "../Models/SourceOverview"
import { Translation } from "./i18n/Translation"
/** /**
* The state needed to render a special Visualisation. * The state needed to render a special Visualisation.
@ -88,34 +87,9 @@ export interface SpecialVisualizationState {
reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void> reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void>
} }
export interface SpecialVisualisationArg {
name: string
defaultValue?: string
doc: string
required?: false | boolean
type?: "key" | "translation" | string
}
export class SpecialVisualizationUtils {
static parseArgs(specs: { name: string; defaultValue?: string }[], args: string[]){
return Utils.ParseVisArgs(specs, args)
}
}
export interface SpecialVisualisationParams {
state: SpecialVisualizationState
tags: UIEventSource<Record<string, string>>
args: string[]
feature: Feature
layer: LayerConfig
}
export abstract class SpecialVisualization { export abstract class SpecialVisualization {
readonly funcName: string readonly funcName: string
readonly docs: string readonly docs: string | BaseUIElement
/** /**
* The 'group' is merely what association it has in the docs * The 'group' is merely what association it has in the docs
*/ */
@ -133,16 +107,34 @@ export abstract class SpecialVisualization {
/** /**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
*/ */
readonly args: SpecialVisualisationArg[] readonly args: {
name: string
defaultValue?: string
doc: string
required?: false | boolean
type?: "key" | string
}[]
readonly getLayerDependencies?: (argument: string[]) => string[] readonly getLayerDependencies?: (argument: string[]) => string[]
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[] structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
abstract constr(options: SpecialVisualisationParams): BaseUIElement abstract constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement
} }
export abstract class SpecialVisualizationSvelte extends SpecialVisualization { export abstract class SpecialVisualizationSvelte extends SpecialVisualization {
abstract constr(options: SpecialVisualisationParams): SvelteUIElement abstract constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement
} }
export type RenderingSpecification = export type RenderingSpecification =

View file

@ -1,4 +1,4 @@
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationSvelte } from "./SpecialVisualization" import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz" import { MultiApplyViz } from "./Popup/MultiApplyViz"
import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis"
@ -176,24 +176,20 @@ export default class SpecialVisualizations {
} }
private static initList(): SpecialVisualization[] { private static initList(): SpecialVisualization[] {
const specialVisualizationsSv: SpecialVisualizationSvelte[] = [
...ImageVisualisations.initList(),
...FavouriteVisualisations.initList(),
...SettingsVisualisations.initList(),
...DataImportSpecialVisualisations.initList(),
...DataExportVisualisations.initList(),
new UploadToOsmViz(),
new MultiApplyViz(),
]
const specialVisualizations: SpecialVisualization[] = [ const specialVisualizations: SpecialVisualization[] = [
...ImageVisualisations.initList(),
...NoteVisualisations.initList(), ...NoteVisualisations.initList(),
...FavouriteVisualisations.initList(),
...UISpecialVisualisations.initList(), ...UISpecialVisualisations.initList(),
...SettingsVisualisations.initList(),
...ReviewSpecialVisualisations.initList(), ...ReviewSpecialVisualisations.initList(),
...DataImportSpecialVisualisations.initList(),
...TagrenderingManipulationSpecialVisualisations.initList(), ...TagrenderingManipulationSpecialVisualisations.initList(),
...WebAndCommunicationSpecialVisualisations.initList(), ...WebAndCommunicationSpecialVisualisations.initList(),
...DataVisualisations.initList(), ...DataVisualisations.initList(),
...specialVisualizationsSv ...DataExportVisualisations.initList(),
new UploadToOsmViz(),
new MultiApplyViz(),
] ]
specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations)) specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations))

View file

@ -18,6 +18,7 @@
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid" import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import Filter from "../../assets/svg/Filter.svelte" import Filter from "../../assets/svg/Filter.svelte"
import { Lists } from "../../Utils/Lists"
export let paths: string[] export let paths: string[]

View file

@ -1,6 +1,6 @@
import { Utils } from "../../Utils"
import { Feature, Polygon } from "geojson" import { Feature, Polygon } from "geojson"
import { OsmFeature } from "../../Models/OsmFeature" import { OsmFeature } from "../../Models/OsmFeature"
import { Lists } from "../../Utils/Lists"
export interface ChangeSetData extends Feature<Polygon> { export interface ChangeSetData extends Feature<Polygon> {
id: number id: number

View file

@ -1,5 +1,6 @@
import DOMPurify from "dompurify" import DOMPurify from "dompurify"
import { Lists } from "./Utils/Lists" import { Lists } from "./Utils/Lists"
import { Strings } from "./Utils/Strings"
export class Utils { export class Utils {
/** /**