Merge branch 'develop'

This commit is contained in:
Pieter Vander Vennet 2024-01-07 18:23:12 +01:00
commit be0154bfe5
86 changed files with 1669 additions and 512 deletions

2
.gitignore vendored
View file

@ -25,6 +25,8 @@ index_*.ts
*.doctest.ts *.doctest.ts
service-worker.js service-worker.js
.env .env
src/assets/editor-layer-index.json
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json

View file

@ -51,7 +51,7 @@
</div> </div>
<script type="module" src="./src/notfound.ts"></script> <script type="module" src="./src/notfound.ts"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
</body> </body>
</html> </html>

View file

@ -22,7 +22,7 @@
] ]
}, },
"icon": { "icon": {
"render": "addSmall:#000", "render": "addSmall",
"mappings": [ "mappings": [
{ {
"if": "detach=yes", "if": "detach=yes",

View file

@ -0,0 +1,59 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="251.000000pt" height="250.000000pt" viewBox="0 0 251.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1410 2282 c-48 -24 -80 -75 -80 -127 l0 -42 -73 -5 c-108 -7 -147
-41 -147 -128 l0 -40 -321 0 c-326 0 -348 -2 -376 -39 -21 -27 -15 -79 12
-106 l24 -25 331 0 c326 0 330 0 330 -20 0 -32 26 -74 54 -89 22 -12 25 -21
28 -75 3 -54 5 -61 25 -64 17 -3 26 -16 40 -58 10 -33 25 -60 38 -66 11 -6 25
-18 31 -27 18 -24 71 32 91 96 12 38 22 53 37 55 18 3 21 11 24 63 3 55 6 62
35 80 21 13 37 36 47 63 11 33 19 42 37 42 l23 0 0 -296 c0 -192 4 -302 11
-316 14 -26 67 -48 101 -41 14 3 37 16 50 30 21 23 23 35 28 168 4 129 7 146
25 159 28 21 58 20 89 -5 l26 -20 0 -270 c0 -173 4 -269 10 -269 6 0 10 97 10
271 l0 271 -29 29 c-33 32 -67 37 -110 14 -41 -21 -51 -61 -51 -200 0 -105 -2
-124 -18 -138 -25 -23 -78 -21 -102 3 -19 19 -20 33 -20 315 l0 295 164 0
c159 0 166 1 192 24 35 30 37 87 5 120 -20 19 -34 21 -190 24 l-169 3 -4 129
c-3 112 -6 132 -24 157 -52 70 -132 92 -204 55z m157 -33 c43 -40 53 -78 53
-199 l0 -110 -25 0 c-21 0 -25 5 -25 30 0 44 -24 96 -54 116 -16 10 -55 19
-96 22 l-70 5 0 34 c0 46 24 90 63 114 43 27 117 21 154 -12z m-67 -209 c18
-18 20 -33 20 -165 0 -178 -4 -185 -105 -185 l-65 0 0 114 c0 66 -4 117 -10
121 -6 4 -10 -37 -10 -114 l0 -121 -67 0 c-55 0 -71 4 -90 22 -22 20 -23 28
-23 162 0 191 -5 186 187 186 130 0 145 -2 163 -20z m-390 -190 l0 -40 -319 0
c-277 0 -322 2 -335 16 -9 8 -16 21 -16 28 0 34 21 36 347 36 l323 0 0 -40z
m868 11 c7 -46 -16 -51 -220 -51 l-188 0 0 40 0 40 203 -2 202 -3 3 -24z
m-538 -261 l0 -40 -105 0 -105 0 0 40 0 40 105 0 105 0 0 -40z m-54 -96 c-3
-9 -9 -27 -12 -40 -5 -21 -12 -25 -37 -22 -25 2 -32 10 -43 41 l-13 37 56 0
c46 0 54 -3 49 -16z"/>
<path d="M2035 1419 c-10 -26 -1 -504 10 -502 14 4 19 493 6 506 -7 7 -12 5
-16 -4z"/>
<path d="M2120 1165 c0 -163 4 -255 10 -255 6 0 10 92 10 255 0 163 -4 255
-10 255 -6 0 -10 -92 -10 -255z"/>
<path d="M1324 1307 c-9 -25 0 -62 16 -62 10 0 15 10 15 34 0 36 -21 54 -31
28z"/>
<path d="M1304 1192 c-6 -4 -16 -17 -23 -30 -10 -18 -9 -23 4 -28 21 -9 45 14
45 42 0 25 -7 29 -26 16z"/>
<path d="M902 1123 c2 -13 13 -18 38 -18 25 0 36 5 38 18 3 14 -4 17 -38 17
-34 0 -41 -3 -38 -17z"/>
<path d="M1030 1119 c0 -17 5 -20 37 -17 27 2 39 8 41 21 3 14 -4 17 -37 17
-35 0 -41 -3 -41 -21z"/>
<path d="M1152 1123 c2 -13 13 -18 38 -18 25 0 36 5 38 18 3 14 -4 17 -38 17
-34 0 -41 -3 -38 -17z"/>
<path d="M796 990 c-57 -59 -66 -73 -56 -85 11 -13 67 -15 380 -15 203 0 375
3 384 6 29 11 16 39 -47 102 l-63 62 -265 0 -266 0 -67 -70z m609 -10 l39 -40
-319 0 -319 0 39 40 39 40 241 0 241 0 39 -40z"/>
<path d="M741 851 c-8 -5 -11 -16 -8 -25 6 -14 48 -16 396 -16 321 0 390 2
394 14 13 34 -13 36 -392 36 -207 0 -382 -4 -390 -9z"/>
<path d="M735 760 c-17 -27 18 -30 396 -30 382 0 389 0 389 20 0 20 -7 20
-389 20 -249 0 -392 -4 -396 -10z"/>
<path d="M734 677 c-3 -8 -4 -54 -2 -103 l3 -89 389 -3 c305 -2 391 1 398 10
13 21 10 172 -4 186 -18 18 -777 17 -784 -1z m746 -97 l0 -60 -350 0 -350 0 0
60 0 60 350 0 350 0 0 -60z"/>
<path d="M580 420 c0 -7 197 -10 571 -10 377 0 568 3 564 10 -4 6 -205 10
-571 10 -369 0 -564 -3 -564 -10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,57 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="251.000000pt" height="251.000000pt" viewBox="0 0 251.000000 251.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,251.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M734 2059 c-11 -19 9 -28 71 -31 l60 -3 38 -66 38 -67 -36 -72 c-25
-49 -41 -70 -50 -66 -57 21 -139 29 -201 19 -175 -30 -302 -155 -333 -328 -46
-257 191 -496 451 -454 163 26 284 139 324 302 l17 67 76 0 76 0 120 239 c65
132 122 238 126 235 4 -2 21 -29 38 -59 l31 -54 -55 -52 c-141 -133 -166 -344
-60 -505 88 -134 249 -202 403 -170 81 17 120 35 180 84 132 106 180 311 109
464 -30 67 -101 147 -162 183 -95 55 -228 68 -332 31 -50 -18 -39 -28 -128
132 -72 130 -105 167 -158 177 -52 10 -102 2 -102 -15 0 -11 15 -17 53 -21 65
-8 96 -28 122 -79 11 -21 20 -42 20 -45 0 -3 -109 -4 -242 -3 l-242 3 -32 55
c-18 30 -33 58 -34 63 0 4 14 7 30 7 23 0 30 4 30 20 0 19 -7 20 -119 20 -76
0 -122 -4 -127 -11z m630 -411 c-58 -117 -107 -214 -109 -216 -1 -2 -49 79
-105 180 -56 101 -111 198 -122 216 l-20 32 231 0 231 0 -106 -212z m-275 -20
c61 -108 114 -203 117 -212 5 -13 -2 -16 -43 -16 l-50 0 -12 58 c-23 103 -99
217 -175 259 -16 9 -16 13 10 66 16 31 31 54 35 49 4 -4 57 -95 118 -204z
m-277 92 c35 -10 36 -12 26 -34 -9 -21 -14 -23 -42 -15 -17 5 -56 9 -87 9
-208 0 -352 -209 -279 -404 81 -213 357 -262 508 -91 38 44 72 117 72 157 0
13 8 18 26 18 26 0 27 -1 21 -45 -14 -95 -98 -204 -196 -252 -49 -25 -67 -28
-151 -28 -84 1 -101 4 -148 28 -75 39 -124 87 -163 159 -31 59 -34 69 -34 158
0 76 4 104 22 142 38 84 125 163 212 194 44 16 162 18 213 4z m1112 -11 c113
-42 208 -166 222 -289 13 -119 -51 -259 -150 -327 -163 -111 -390 -68 -495 95
-89 139 -73 314 39 436 46 49 63 56 79 29 7 -14 2 -24 -29 -51 -80 -68 -116
-193 -85 -298 86 -298 494 -298 580 0 40 139 -23 279 -157 346 -50 24 -74 30
-128 30 -37 0 -82 -5 -100 -11 -27 -10 -34 -9 -42 4 -14 25 -10 31 25 44 54
20 179 16 241 -8z m-971 -73 c54 -52 95 -128 104 -191 5 -43 5 -45 -19 -45
-22 0 -26 6 -32 41 -9 59 -41 114 -92 163 -31 30 -43 48 -38 60 10 26 29 19
77 -28z m-147 11 c5 -4 -21 -65 -55 -136 -35 -70 -61 -133 -57 -140 5 -7 54
-11 153 -11 l145 0 -7 -37 c-16 -91 -101 -183 -194 -210 -207 -60 -402 126
-351 336 20 85 93 166 177 197 39 15 175 15 189 1z m1117 -17 c206 -104 198
-408 -12 -504 -63 -28 -169 -28 -232 0 -140 65 -203 238 -136 376 28 58 79
114 95 104 5 -3 38 -57 73 -120 36 -64 72 -117 82 -120 32 -8 19 32 -49 150
-36 64 -64 118 -62 120 33 32 173 29 241 -6z m-1018 -47 c40 -39 85 -123 85
-160 0 -23 -2 -23 -120 -23 -66 0 -120 2 -120 5 0 11 105 215 111 215 3 0 23
-17 44 -37z"/>
<path d="M765 881 c-45 -21 -97 -69 -92 -85 2 -6 43 -13 97 -16 l93 -5 26 -47
26 -48 -26 -47 -26 -48 -98 -5 c-69 -3 -100 -9 -103 -18 -8 -24 75 -90 135
-108 87 -25 149 -8 221 62 l56 54 179 0 180 0 47 -49 c37 -39 60 -53 106 -67
218 -63 384 194 236 367 -46 53 -101 79 -172 79 -70 0 -124 -25 -172 -78 l-38
-42 -187 0 -186 0 -35 41 c-20 23 -54 50 -76 60 -53 24 -139 24 -191 0z m171
-40 c23 -10 50 -31 60 -46 48 -68 34 -65 258 -65 l204 0 37 46 c42 51 101 83
153 84 64 0 138 -44 168 -102 18 -35 18 -131 0 -166 -66 -127 -243 -138 -321
-20 l-25 38 -213 0 -213 0 -38 -46 c-21 -25 -54 -52 -72 -60 -53 -22 -123 -18
-169 11 l-40 24 75 1 c41 0 80 4 86 8 6 4 26 35 45 69 l34 62 -29 57 c-42 80
-48 84 -124 85 l-67 1 40 19 c51 24 99 24 151 0z"/>
<path d="M1556 744 c-14 -26 -26 -54 -26 -64 0 -10 12 -38 26 -64 l26 -46 63
0 c69 0 87 12 115 80 14 33 4 70 -32 115 -17 22 -27 25 -83 25 l-63 0 -26 -46z
m151 -29 c16 -33 16 -37 0 -70 -15 -31 -21 -35 -56 -35 -34 0 -42 4 -60 36
l-21 36 21 34 c18 29 26 34 60 34 35 0 41 -4 56 -35z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,52 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="250.000000pt" viewBox="0 0 250.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M995 2133 c-139 -22 -328 -97 -426 -169 l-49 -36 0 -638 c0 -572 2
-638 16 -644 9 -3 195 -6 415 -6 348 0 399 -2 399 -15 0 -12 -14 -15 -64 -15
-35 0 -71 -3 -80 -6 -27 -10 -20 -62 13 -95 29 -29 31 -29 145 -29 l115 0 3
-82 3 -83 100 0 100 0 3 83 3 82 104 0 c59 0 115 5 130 12 46 21 73 99 39 112
-9 3 -47 6 -85 6 l-69 0 0 36 c0 28 9 46 36 77 62 69 89 139 89 237 0 100 -25
165 -94 242 -64 72 -67 95 -26 197 49 119 48 121 -25 121 l-60 0 0 204 0 204
-49 36 c-70 52 -222 121 -321 147 -84 21 -291 34 -365 22z m362 -68 c96 -25
206 -73 285 -128 l48 -32 0 -192 0 -193 -25 0 -25 0 0 174 c0 148 -3 177 -16
191 -32 31 -179 97 -273 122 -137 37 -315 37 -453 0 -118 -31 -276 -109 -283
-141 -3 -11 -4 -268 -3 -571 l3 -550 344 -3 344 -2 23 -25 23 -25 -394 0 -395
0 0 608 0 607 48 32 c216 147 488 193 749 128z m-109 -60 c85 -8 179 -38 280
-87 l82 -41 0 -179 0 -178 -25 0 c-24 0 -24 2 -27 83 l-3 82 -68 3 c-91 4 -97
-2 -97 -94 l0 -74 -34 0 c-19 0 -38 -4 -41 -10 -3 -5 10 -47 30 -92 19 -45 35
-96 35 -115 l0 -33 -95 0 c-84 0 -95 2 -95 18 0 9 8 22 19 29 28 17 61 94 61
141 0 56 -15 90 -57 134 l-37 38 28 59 c16 32 25 64 22 70 -5 7 -55 11 -147
11 -159 0 -155 3 -112 -85 l27 -55 -31 -29 c-60 -54 -80 -124 -58 -198 8 -27
28 -62 45 -78 47 -45 40 -55 -35 -55 l-64 0 3 133 c2 72 1 138 -1 145 -4 8
-30 12 -84 12 l-78 0 0 -165 0 -165 331 0 330 0 -37 -41 c-52 -58 -77 -121
-83 -211 l-5 -78 -103 0 c-114 0 -125 7 -78 51 31 29 45 53 55 97 13 57 -1 62
-181 62 -87 0 -165 -3 -174 -6 -34 -13 -7 -113 43 -158 36 -32 27 -45 -36 -48
-50 -3 -58 -6 -58 -23 0 -19 8 -20 275 -23 l275 -3 21 -45 21 -44 -323 0 -324
0 0 558 0 559 83 41 c45 22 114 50 153 62 74 22 214 39 274 34 19 -2 64 -6 98
-9z m-78 -280 c0 -3 -9 -23 -20 -45 -28 -54 -26 -66 20 -107 70 -63 77 -142
18 -208 -29 -34 -32 -35 -102 -35 -79 0 -106 12 -131 60 -31 60 -15 128 44
184 35 33 40 74 16 116 -8 14 -14 28 -15 33 0 4 38 7 85 7 47 0 85 -2 85 -5z
m350 -145 l0 -60 -40 0 -40 0 0 60 0 60 40 0 40 0 0 -60z m-714 -175 c-1 -125
-9 -146 -48 -128 -21 9 -22 16 -24 126 l-1 117 37 0 38 0 -2 -115z m985 53
c-8 -23 -11 -23 -202 -26 -174 -2 -195 -1 -206 15 -7 9 -13 20 -13 25 0 4 97
8 215 8 l214 0 -8 -22z m-38 -96 c-22 -68 -12 -110 43 -174 40 -46 84 -116 84
-133 0 -3 -133 -5 -295 -5 -199 0 -295 3 -295 10 0 21 31 70 75 120 57 65 72
115 51 178 -9 25 -16 47 -16 49 0 2 83 3 184 3 l184 0 -15 -48z m-613 -72 c0
-18 -7 -20 -60 -20 -53 0 -60 2 -60 20 0 18 7 20 60 20 53 0 60 -2 60 -20z
m-80 -236 c0 -11 -23 -50 -43 -71 -18 -20 -30 -23 -103 -23 -82 0 -83 0 -107
33 -51 68 -53 67 108 67 80 0 145 -3 145 -6z m830 -96 c0 -83 -25 -149 -80
-211 l-32 -37 -192 0 -192 0 -36 38 c-40 40 -72 104 -82 165 l-7 37 286 0
c183 0 285 4 285 10 0 6 -102 10 -285 10 -157 0 -285 3 -285 8 0 4 3 17 6 30
l6 22 304 0 304 0 0 -72z m-910 -43 c0 -12 -13 -15 -60 -15 -47 0 -60 3 -60
15 0 12 13 15 60 15 47 0 60 -3 60 -15z m790 -265 l0 -40 -185 0 -185 0 0 40
0 40 185 0 185 0 0 -40z m141 -110 c-22 -19 -40 -20 -328 -20 -292 0 -343 4
-343 30 0 6 131 10 348 10 346 -1 347 -1 323 -20z m-271 -120 l0 -60 -60 0
-60 0 0 60 0 60 60 0 60 0 0 -60z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,64 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="250.000000pt" viewBox="0 0 250.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M612 2138 c-8 -8 -9 -15 -1 -25 9 -10 113 -13 535 -13 601 0 544 11
544 -104 l0 -66 -610 0 c-400 0 -610 -3 -610 -10 0 -7 210 -10 610 -10 l610 0
0 -33 c0 -70 34 -67 -662 -67 -343 0 -627 -3 -631 -6 -3 -3 -3 -12 0 -20 4
-12 64 -14 353 -14 l348 0 4 -74 c4 -79 19 -109 66 -127 19 -7 22 -16 22 -67
0 -54 2 -60 40 -97 l40 -39 0 -311 0 -311 48 -47 c26 -26 52 -47 58 -47 5 0
31 22 57 48 l47 48 0 310 0 310 40 39 c38 37 40 43 40 97 0 51 3 60 23 67 46
18 61 48 65 126 4 69 6 74 32 85 42 17 50 48 50 185 0 117 -1 126 -24 152
l-24 28 -529 3 c-401 2 -531 0 -541 -10z m988 -436 c0 -40 -5 -73 -12 -80 -17
-17 -409 -17 -426 0 -7 7 -12 40 -12 80 l0 68 225 0 225 0 0 -68z m-80 -189
c0 -39 -5 -52 -32 -80 -31 -32 -35 -33 -112 -33 -76 0 -82 1 -113 32 -28 27
-33 39 -33 80 l0 48 145 0 145 0 0 -47z m-80 -196 c0 -27 -11 -43 -58 -90 -32
-31 -61 -57 -65 -57 -4 0 -7 41 -7 90 l0 90 65 0 65 0 0 -33z m-64 -216 l-66
-66 0 45 c0 41 5 49 63 108 l62 63 3 -42 c3 -40 0 -46 -62 -108z m7 -123 c-31
-32 -61 -58 -65 -58 -4 0 -8 19 -8 42 0 39 6 48 63 106 l62 63 3 -47 c3 -46 1
-49 -55 -106z m-5 -126 l-63 -61 -3 41 c-3 39 0 45 60 105 l63 64 3 -44 c3
-42 1 -47 -60 -105z m30 -119 l-32 -33 -30 30 -30 30 59 60 60 61 3 -57 c3
-53 1 -59 -30 -91z"/>
<path d="M879 1449 c-11 -11 -18 -27 -15 -36 3 -8 6 -19 6 -24 0 -17 50 -30
71 -19 28 15 35 57 14 80 -23 26 -50 25 -76 -1z m61 -14 c18 -21 5 -45 -25
-45 -20 0 -25 5 -25 23 0 39 26 50 50 22z"/>
<path d="M1784 1459 c-16 -21 -164 -396 -164 -416 0 -35 20 7 100 206 74 185
92 246 64 210z"/>
<path d="M870 1209 c0 -18 281 -374 298 -378 13 -2 14 0 6 10 -6 8 -75 96
-153 197 -78 100 -144 182 -147 182 -2 0 -4 -5 -4 -11z"/>
<path d="M1907 1154 c-70 -35 -125 -69 -122 -74 7 -11 265 115 265 130 0 16
-7 13 -143 -56z"/>
<path d="M831 996 c-15 -18 -4 -46 19 -46 28 0 45 27 30 45 -16 19 -34 19 -49
1z"/>
<path d="M273 924 c-11 -28 18 -34 174 -34 l153 0 5 -27 c40 -192 104 -296
239 -384 167 -110 395 -110 562 0 135 88 199 192 239 384 l5 27 278 0 c152 0
283 4 291 9 8 5 11 16 8 25 -6 14 -40 16 -305 16 -218 0 -301 -3 -310 -12 -7
-7 -12 -28 -12 -47 0 -97 -57 -218 -140 -301 -187 -187 -483 -187 -670 0 -83
83 -140 204 -140 301 0 19 -5 40 -12 47 -8 8 -64 12 -185 12 -150 0 -174 -2
-180 -16z"/>
<path d="M421 826 c-16 -19 -10 -66 10 -79 26 -16 68 0 75 30 4 14 2 33 -3 42
-13 21 -66 26 -82 7z m65 -21 c7 -18 -13 -45 -33 -45 -17 0 -27 24 -19 45 7
20 45 19 52 0z"/>
<path d="M2040 821 c-15 -28 -12 -46 8 -64 27 -24 59 -21 78 8 15 23 15 27 0
50 -21 31 -70 35 -86 6z m68 -29 c4 -28 -24 -41 -44 -20 -20 20 -7 50 20 46
13 -2 22 -12 24 -26z"/>
<path d="M870 791 c0 -10 277 -131 300 -131 29 0 -8 20 -147 79 -151 65 -153
66 -153 52z"/>
<path d="M1748 775 c-17 -37 3 -75 40 -75 35 0 52 16 52 50 0 34 -17 50 -52
50 -21 0 -32 -7 -40 -25z m67 -25 c0 -18 -6 -26 -23 -28 -13 -2 -25 3 -28 12
-10 26 4 48 28 44 17 -2 23 -10 23 -28z"/>
<path d="M1923 633 c-19 -7 -16 -50 3 -57 19 -7 44 10 44 30 0 16 -30 33 -47
27z"/>
<path d="M1635 570 c-26 -29 -24 -79 4 -102 70 -56 164 39 101 102 -26 26 -81
26 -105 0z m89 -16 c21 -20 20 -43 -1 -66 -32 -36 -83 -17 -83 32 0 14 5 31
12 38 17 17 54 15 72 -4z"/>
<path d="M1426 414 c-29 -29 -10 -94 27 -94 25 0 57 32 57 55 0 23 -32 55 -57
55 -6 0 -19 -7 -27 -16z m58 -27 c8 -12 7 -21 -5 -32 -21 -21 -49 -9 -49 20 0
36 34 44 54 12z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -243,7 +243,8 @@
"sourceString": [ "sourceString": [
"device-key", "device-key",
"{device-name}", "{device-name}",
"{negative-name}" "{negative-name}",
"{icon}"
], ],
"into": [ "into": [
[ [
@ -261,7 +262,8 @@
"de": "3D-Drucker", "de": "3D-Drucker",
"ca": "Impressora 3D", "ca": "Impressora 3D",
"cs": "3D-tiskárna" "cs": "3D-tiskárna"
} },
"./assets/layers/hackerspace/3d_printer.svg"
], ],
[ [
"lasercutter", "lasercutter",
@ -278,7 +280,8 @@
"de": "Laserschneider", "de": "Laserschneider",
"ca": "tallador laser", "ca": "tallador laser",
"cs": "laserová řezačka" "cs": "laserová řezačka"
} },
"./assets/layers/hackerspace/lasercutter.svg"
], ],
[ [
"cnc_drilling_machine", "cnc_drilling_machine",
@ -295,7 +298,67 @@
"de": "CNC-Fräse", "de": "CNC-Fräse",
"ca": "trepant CNC", "ca": "trepant CNC",
"cs": "CNC vrtačka" "cs": "CNC vrtačka"
} },
"./assets/layers/hackerspace/cnc.svg"
],
[
"media_studio",
{
"en": "a multimedia studio"
},
{
"en": "multimedia studio"
},
"./assets/layers/hackerspace/media_studio.svg"
],
[
"sewing_machine",
{
"en": "a sewing machine"
},
{
"en": "sewing machine"
},
"./assets/layers/hackerspace/sewing_machine.svg"
],
[
"workshop:wood",
{
"en": "a woodworking workshop"
},
{
"en": "woodworking workshop"
},
"./assets/layers/hackerspace/woodworking.svg"
], [
"workshop:ceramics",
{
"en": "a ceramics workshop"
},
{
"en": "ceramics workshop"
},
"./assets/layers/hackerspace/ceramics.svg"
],
[
"workshop:metal",
{
"en": "a metal workshop"
},
{
"en": "meta workshop"
},
"./assets/layers/hackerspace/metal.svg"
],
[
"bicycle:diy",
{
"en": "a bicycle repair workshop"
},
{
"en": "bicycle repair workshop"
},
"./assets/layers/hackerspace/bicycle.svg"
] ]
] ]
}, },
@ -308,9 +371,11 @@
"ca": "Hi ha {device-name} disponible a aquest espai hacker?", "ca": "Hi ha {device-name} disponible a aquest espai hacker?",
"cs": "Je {device-name} dostupné v tomto hackerspace?" "cs": "Je {device-name} dostupné v tomto hackerspace?"
}, },
"#iconsize": "large",
"mappings": [ "mappings": [
{ {
"if": "service:device-key=yes", "if": "service:device-key=yes",
"icon": "{icon}",
"then": { "then": {
"en": "There is {device-name} available at this hackerspace", "en": "There is {device-name} available at this hackerspace",
"nl": "Er is {device-name} beschikbaar in deze hackerspace", "nl": "Er is {device-name} beschikbaar in deze hackerspace",

View file

@ -0,0 +1,57 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="251.000000pt" height="250.000000pt" viewBox="0 0 251.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1030 2000 l0 -60 -264 0 c-264 0 -265 0 -292 -24 -39 -33 -39 -89 0
-122 27 -24 28 -24 292 -24 l264 0 0 -40 0 -40 40 0 40 0 0 -58 c0 -49 5 -65
36 -112 84 -127 118 -139 175 -62 77 102 79 107 79 170 l0 61 43 3 c41 3 42 4
45 41 l3 37 254 0 c269 0 294 4 314 47 18 39 13 63 -18 94 l-29 29 -261 0
-260 0 -3 58 -3 57 -227 3 -228 2 0 -60z m410 -125 l0 -145 -185 0 -185 0 0
145 0 145 185 0 185 0 0 -145z m-410 -25 l0 -40 -251 0 c-153 0 -258 4 -270
10 -18 10 -26 50 -12 63 3 4 125 7 270 7 l263 0 0 -40z m993 18 c9 -13 7 -22
-7 -37 -18 -20 -29 -21 -273 -21 l-253 0 0 40 0 40 261 -2 c233 -3 263 -5 272
-20z m-663 -228 l0 -50 -105 0 -105 0 0 50 0 50 105 0 105 0 0 -50z m-20 -75
c0 -3 -12 -23 -26 -45 -22 -32 -33 -40 -58 -40 -22 0 -35 7 -45 23 -8 12 -21
32 -29 45 l-14 22 86 0 c47 0 86 -2 86 -5z m-73 -131 c-3 -3 -12 -4 -19 -1 -8
3 -5 6 6 6 11 1 17 -2 13 -5z"/>
<path d="M1120 1875 l0 -96 53 3 52 3 0 90 0 90 -52 3 -53 3 0 -96z m80 0 l0
-75 -30 0 -30 0 0 75 0 75 30 0 30 0 0 -75z"/>
<path d="M1248 1133 l-3 -246 -39 97 c-37 90 -51 114 -62 104 -2 -3 14 -47 36
-98 22 -52 40 -96 40 -99 0 -3 -25 19 -55 49 -30 29 -58 51 -62 47 -4 -4 18
-33 48 -63 61 -63 69 -63 -79 0 -94 40 -100 24 -7 -18 157 -71 151 -65 53 -65
-52 -1 -88 -5 -88 -11 0 -6 40 -10 100 -10 l100 0 0 -56 c0 -48 -4 -59 -29
-85 l-29 -29 -326 0 c-179 0 -326 3 -325 8 0 4 56 77 123 162 l122 155 87 3
c79 3 87 5 87 22 0 19 -7 20 -98 20 l-99 0 -147 -190 c-89 -115 -146 -197
-144 -208 3 -16 29 -17 331 -20 l327 -2 0 -45 0 -45 -240 0 c-153 0 -240 -4
-240 -10 0 -7 223 -10 650 -10 427 0 650 3 650 10 0 6 -137 10 -391 10 l-390
0 3 46 3 45 441 -1 c242 0 448 2 457 6 10 3 17 10 17 15 0 5 -59 97 -130 205
l-131 195 -97 -3 c-78 -2 -97 -6 -97 -18 0 -12 18 -16 86 -18 l86 -3 107 -159
c58 -88 106 -162 106 -165 0 -3 -171 -5 -379 -5 l-379 0 19 37 c11 21 19 58
19 85 l0 48 124 0 c166 0 177 18 14 22 l-123 3 50 19 c98 38 116 47 110 56 -3
5 -28 1 -58 -11 -29 -12 -59 -23 -67 -26 -8 -3 32 42 90 101 57 58 98 106 90
106 -9 0 -58 -43 -110 -95 -52 -52 -95 -93 -97 -91 -2 2 12 40 32 86 20 46 33
85 30 88 -10 10 -22 -12 -59 -98 l-35 -85 0 243 c-1 151 -5 242 -11 242 -6 0
-11 -94 -12 -247z"/>
<path d="M1040 1330 c0 -16 23 -60 32 -60 11 0 10 14 -4 45 -11 25 -28 34 -28
15z"/>
<path d="M1430 1281 c-17 -43 -7 -63 13 -24 19 36 21 53 8 53 -5 0 -14 -13
-21 -29z"/>
<path d="M1090 1210 c0 -16 23 -60 32 -60 11 0 10 14 -4 45 -11 25 -28 34 -28
15z"/>
<path d="M1377 1148 c-4 -12 -4 -24 -1 -27 6 -7 24 20 24 37 0 21 -17 13 -23
-10z"/>
<path d="M960 1122 c0 -14 59 -66 67 -59 3 4 -6 21 -22 37 -28 30 -45 38 -45
22z"/>
<path d="M1522 959 c-13 -5 -21 -13 -17 -18 7 -12 47 1 53 17 4 14 -4 14 -36
1z"/>
<path d="M875 830 c3 -5 26 -10 51 -10 24 0 44 5 44 10 0 6 -23 10 -51 10 -31
0 -48 -4 -44 -10z"/>
<path d="M1585 830 c3 -5 15 -10 25 -10 10 0 22 5 25 10 4 6 -7 10 -25 10 -18
0 -29 -4 -25 -10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,92 @@
[
{
"path": "3d_printer.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "bicycle.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "ceramics.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "cnc.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "lasercutter.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "media_studio.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "metal.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "sewing_machine.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
},
{
"path": "woodworking.svg",
"license": "CC-By-SA",
"authors": [
"Verbund Offener Werkstätten"
],
"sources": [
"https://www.offene-werkstaetten.org/de/werkstatt-suche"
]
}
]

View file

@ -0,0 +1,107 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="250.000000pt" viewBox="0 0 250.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M567 2053 c-4 -3 -7 -104 -7 -223 l0 -217 -52 -6 c-40 -5 -59 -14
-83 -37 l-30 -30 -3 -463 c-2 -425 -1 -465 15 -497 29 -56 46 -60 265 -60
l198 0 32 29 33 29 3 181 c3 171 4 181 23 181 15 0 19 -7 19 -39 0 -26 5 -41
16 -45 24 -9 497 -7 512 2 9 7 12 55 10 208 l-3 199 -265 0 -265 0 -3 -37 c-3
-30 -7 -38 -23 -38 -18 0 -19 8 -19 100 l0 100 350 0 c304 0 350 -2 350 -15 0
-10 -10 -15 -29 -15 -51 0 -52 -8 -49 -280 l3 -252 33 -29 c29 -26 39 -29 102
-29 l70 0 0 -60 0 -60 -55 0 c-75 0 -121 -21 -141 -64 -32 -67 -35 -66 261
-66 205 0 265 3 266 13 7 74 -46 117 -145 117 l-56 0 0 60 0 60 63 0 c137 0
147 22 147 327 0 255 -2 263 -61 263 l-29 0 0 88 c0 65 -4 94 -16 111 -20 28
-72 51 -114 51 l-30 0 0 209 c0 115 -3 216 -6 225 -6 14 -72 16 -643 16 -351
0 -641 -3 -644 -7z m1243 -238 l0 -205 -32 0 c-76 0 -138 -62 -138 -137 l0
-33 -350 0 -350 0 0 40 c0 23 -7 55 -16 71 -24 46 -66 59 -199 59 l-115 0 0
205 0 205 600 0 600 0 0 -205z m-936 -271 c14 -14 16 -71 16 -484 l0 -470 -24
-15 c-21 -14 -54 -16 -211 -13 -171 3 -188 5 -201 22 -12 16 -14 99 -14 480 0
413 2 461 17 478 14 16 35 18 209 18 162 0 195 -3 208 -16z m1086 -4 c11 -11
20 -31 20 -45 0 -22 -4 -25 -35 -25 -19 0 -35 -4 -35 -10 0 -5 16 -10 35 -10
32 0 35 -2 35 -29 0 -26 -4 -30 -32 -33 -18 -2 -33 -7 -33 -13 0 -5 15 -11 33
-13 28 -3 32 -7 32 -33 0 -27 -3 -29 -35 -29 -19 0 -35 -4 -35 -10 0 -5 16
-10 35 -10 32 0 35 -2 35 -30 0 -28 -3 -30 -35 -30 -19 0 -35 -4 -35 -10 0 -5
16 -10 35 -10 32 0 35 -2 35 -29 0 -26 -4 -30 -32 -33 -18 -2 -33 -7 -33 -13
0 -5 15 -11 33 -13 28 -3 32 -7 32 -33 0 -27 -3 -29 -35 -29 -19 0 -35 -4 -35
-10 0 -5 16 -10 35 -10 31 0 35 -3 35 -25 0 -14 -9 -34 -20 -45 -18 -18 -33
-20 -127 -20 -119 0 -143 10 -143 62 0 24 4 28 29 28 17 0 33 5 36 10 4 6 -8
10 -29 10 -33 0 -36 2 -36 29 0 26 4 30 33 33 17 2 32 8 32 13 0 6 -15 11 -32
13 -29 3 -33 7 -33 33 0 27 3 29 36 29 21 0 33 4 29 10 -3 6 -19 10 -36 10
-26 0 -29 3 -29 30 0 27 3 30 29 30 17 0 33 5 36 10 4 6 -8 10 -29 10 -33 0
-36 2 -36 29 0 26 4 30 33 33 17 2 32 8 32 13 0 6 -15 11 -32 13 -29 3 -33 7
-33 33 0 27 3 29 36 29 21 0 33 4 29 10 -3 6 -19 10 -36 10 -25 0 -29 4 -29
27 0 53 22 63 142 63 95 0 110 -2 128 -20z m-320 -290 c0 -47 -3 -60 -15 -60
-12 0 -15 13 -15 60 0 47 3 60 15 60 12 0 15 -13 15 -60z m420 0 c0 -53 -2
-60 -20 -60 -18 0 -20 7 -20 60 0 53 2 60 20 60 18 0 20 -7 20 -60z m-580
-185 l0 -165 -230 0 -230 0 0 165 0 165 230 0 230 0 0 -165z m-500 -5 c0 -73
-2 -80 -20 -80 -18 0 -20 7 -20 80 0 73 2 80 20 80 18 0 20 -7 20 -80z m660 8
c0 -138 44 -178 193 -178 156 0 187 27 187 162 0 81 2 88 20 88 19 0 20 -7 20
-145 0 -197 14 -185 -223 -185 -243 0 -227 -13 -227 181 0 126 2 149 15 149
12 0 15 -15 15 -72z m210 -371 c0 -43 5 -78 12 -85 7 -7 42 -12 89 -12 56 0
80 -4 89 -15 7 -9 11 -18 8 -20 -3 -3 -102 -4 -221 -3 -189 3 -215 5 -207 18
7 11 34 16 97 20 l88 5 3 83 c3 74 5 82 22 82 18 0 20 -7 20 -73z"/>
<path d="M700 1886 c0 -79 -2 -86 -20 -86 -18 0 -20 -7 -20 -70 l0 -70 50 0
50 0 0 70 c0 63 -2 70 -20 70 -18 0 -20 7 -20 79 0 44 -4 83 -10 86 -6 4 -10
-25 -10 -79z m40 -156 c0 -49 -1 -50 -30 -50 -29 0 -30 1 -30 50 0 49 1 50 30
50 29 0 30 -1 30 -50z"/>
<path d="M866 1963 c-3 -3 -6 -42 -6 -85 0 -71 -2 -78 -20 -78 -18 0 -20 -7
-20 -70 l0 -70 55 0 55 0 0 70 c0 63 -2 70 -20 70 -18 0 -20 7 -20 79 0 74 -7
100 -24 84z m44 -233 l0 -50 -35 0 -35 0 0 50 0 50 35 0 35 0 0 -50z"/>
<path d="M990 1899 c0 -62 2 -71 20 -76 18 -5 20 -14 20 -84 0 -46 4 -79 10
-79 6 0 10 33 10 80 0 73 2 80 20 80 18 0 20 7 20 75 l0 75 -50 0 -50 0 0 -71z
m80 -4 c0 -54 0 -55 -30 -55 -30 0 -30 1 -30 55 0 54 0 55 30 55 30 0 30 -1
30 -55z"/>
<path d="M1200 1951 c0 -14 -6 -21 -20 -21 -18 0 -20 -7 -20 -75 0 -68 2 -75
20 -75 18 0 20 -7 20 -60 0 -33 4 -60 10 -60 6 0 10 27 10 60 0 53 2 60 20 60
18 0 20 7 20 71 0 62 -2 71 -20 76 -11 3 -20 11 -20 19 0 7 -4 16 -10 19 -5 3
-10 -3 -10 -14z m40 -96 c0 -54 0 -55 -30 -55 -30 0 -30 1 -30 55 0 54 0 55
30 55 30 0 30 -1 30 -55z"/>
<path d="M1366 1963 c-3 -3 -6 -21 -6 -39 0 -23 -5 -33 -20 -37 -17 -4 -20
-14 -20 -72 0 -58 3 -68 19 -72 14 -4 21 -15 23 -42 4 -47 22 -47 26 0 2 27 9
38 23 42 16 4 19 14 19 72 0 58 -3 68 -20 72 -15 4 -20 14 -20 39 0 32 -11 50
-24 37z m44 -153 l0 -50 -35 0 -35 0 0 50 0 50 35 0 35 0 0 -50z"/>
<path d="M1490 1899 c0 -62 2 -71 20 -76 18 -5 20 -14 20 -84 0 -46 4 -79 10
-79 6 0 10 33 10 80 0 73 2 80 20 80 18 0 20 7 20 75 l0 75 -50 0 -50 0 0 -71z
m80 -4 c0 -54 0 -55 -30 -55 -30 0 -30 1 -30 55 0 54 0 55 30 55 30 0 30 -1
30 -55z"/>
<path d="M1700 1886 c0 -79 -2 -86 -20 -86 -18 0 -20 -7 -20 -70 l0 -70 50 0
50 0 0 70 c0 63 -2 70 -20 70 -18 0 -20 7 -20 79 0 44 -4 83 -10 86 -6 4 -10
-25 -10 -79z m40 -156 c0 -49 -1 -50 -30 -50 -29 0 -30 1 -30 50 0 49 1 50 30
50 29 0 30 -1 30 -50z"/>
<path d="M1002 1578 c-30 -30 -2 -88 42 -88 29 0 46 18 46 50 0 33 -17 50 -50
50 -14 0 -31 -5 -38 -12z m66 -36 c4 -28 -24 -40 -45 -19 -21 21 -9 49 19 45
15 -2 24 -11 26 -26z"/>
<path d="M1171 1576 c-16 -19 -10 -66 10 -79 26 -16 68 0 75 30 4 14 2 33 -3
42 -13 21 -66 26 -82 7z m65 -21 c7 -18 -13 -45 -33 -45 -17 0 -27 24 -19 45
7 20 45 19 52 0z"/>
<path d="M1336 1574 c-9 -8 -16 -21 -16 -27 0 -25 32 -57 55 -57 23 0 55 32
55 57 0 19 -30 43 -55 43 -13 0 -31 -7 -39 -16z m68 -20 c7 -19 -10 -44 -29
-44 -19 0 -36 25 -29 44 3 9 16 16 29 16 13 0 26 -7 29 -16z"/>
<path d="M1502 1578 c-17 -17 -15 -54 4 -72 18 -19 55 -21 72 -4 7 7 12 24 12
38 0 14 -5 31 -12 38 -7 7 -24 12 -38 12 -14 0 -31 -5 -38 -12z m66 -36 c4
-28 -24 -40 -45 -19 -21 21 -9 49 19 45 15 -2 24 -11 26 -26z"/>
<path d="M614 1496 c-3 -8 -4 -29 -2 -48 3 -32 4 -33 56 -36 l52 -3 0 50 0 51
-50 0 c-34 0 -52 -5 -56 -14z m86 -36 c0 -27 -3 -30 -30 -30 -27 0 -30 3 -30
30 0 27 3 30 30 30 27 0 30 -3 30 -30z"/>
<path d="M611 1345 c-110 -34 -161 -153 -112 -261 63 -139 266 -138 335 1 72
145 -68 308 -223 260z m127 -55 c90 -55 97 -180 12 -240 -45 -32 -89 -36 -144
-12 -37 17 -51 30 -66 64 -25 54 -25 84 -1 128 39 75 130 102 199 60z"/>
<path d="M625 1271 c-75 -31 -91 -141 -28 -191 26 -20 90 -29 78 -10 -3 6 -15
10 -26 10 -11 0 -31 11 -44 25 -48 47 -21 135 45 150 17 4 28 11 24 16 -6 11
-22 11 -49 0z"/>
<path d="M755 1151 c-3 -17 -11 -33 -16 -36 -5 -4 -9 -13 -9 -22 0 -14 2 -14
19 1 23 21 36 65 23 78 -6 6 -12 -1 -17 -21z"/>
<path d="M490 750 c0 -6 65 -10 175 -10 110 0 175 4 175 10 0 6 -65 10 -175
10 -110 0 -175 -4 -175 -10z"/>
<path d="M495 670 c-4 -7 55 -10 169 -10 111 0 176 4 176 10 0 14 -337 14
-345 0z"/>
<path d="M1324 1166 c-14 -37 0 -51 51 -51 49 0 50 1 50 30 0 29 -2 30 -48 33
-35 2 -49 -1 -53 -12z m86 -16 c0 -5 -16 -10 -35 -10 -19 0 -35 5 -35 10 0 6
16 10 35 10 19 0 35 -4 35 -10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,68 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="251.000000pt" viewBox="0 0 250.000000 251.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,251.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M860 2267 c-28 -14 -390 -605 -409 -668 -16 -52 -14 -138 5 -191 29
-87 109 -153 332 -276 l64 -35 -121 -121 c-67 -67 -121 -126 -121 -132 0 -15
94 -104 110 -104 9 0 78 63 155 140 l140 140 185 0 c174 0 186 -1 198 -20 12
-19 23 -20 415 -20 398 0 403 0 447 23 51 26 78 59 90 110 16 72 -16 144 -81
182 -32 19 -52 20 -314 20 -268 0 -282 -1 -328 -22 -26 -12 -60 -31 -74 -43
-20 -15 -40 -20 -89 -20 -54 0 -64 -3 -69 -20 -6 -18 -15 -20 -116 -20 -61 0
-108 3 -106 8 3 4 89 145 191 315 124 205 186 317 186 335 0 17 -9 37 -22 49
-28 25 -595 368 -623 376 -11 4 -31 1 -45 -6z m342 -218 c165 -100 302 -187
304 -194 5 -12 -319 -560 -373 -632 -24 -33 -25 -33 -105 -33 -75 0 -84 -2
-109 -26 -20 -20 -34 -25 -52 -20 -13 3 -86 43 -163 89 -189 114 -234 176
-221 306 5 48 27 89 197 372 122 204 197 319 207 319 9 0 150 -82 315 -181z
m998 -899 l0 -120 -74 0 -74 0 -11 33 c-22 65 -96 117 -166 117 -70 0 -144
-52 -166 -117 l-11 -33 -69 0 -69 0 0 83 c0 95 4 102 80 136 41 19 65 21 303
21 l257 0 0 -120z m95 60 c29 -57 12 -130 -39 -164 -14 -9 -28 -16 -31 -16 -3
0 -5 54 -5 120 l0 121 30 -16 c16 -8 36 -29 45 -45z m-775 -100 l0 -80 -40 0
-40 0 0 80 0 80 40 0 40 0 0 -80z m439 23 c26 -20 71 -79 71 -94 0 -5 -70 -9
-155 -9 -85 0 -155 2 -155 5 0 30 58 97 100 114 36 15 108 7 139 -16z m-569
-23 l0 -40 -197 0 -196 0 -142 -140 -141 -140 -27 27 -27 28 152 152 153 153
212 0 213 0 0 -40z"/>
<path d="M877 2083 c-107 -171 -197 -332 -192 -344 8 -21 460 -291 481 -287
11 2 61 74 128 186 107 179 109 183 90 201 -29 27 -453 281 -469 281 -8 0 -25
-17 -38 -37z m202 -105 c64 -39 122 -75 130 -81 10 -9 -40 -25 -215 -69 -126
-32 -234 -58 -241 -58 -7 0 28 68 78 150 83 139 92 149 111 139 11 -6 73 -42
137 -81z m216 -132 l39 -24 -39 -10 c-22 -6 -91 -23 -152 -37 -62 -15 -113
-31 -113 -36 0 -13 9 -12 160 26 76 19 141 35 144 35 3 0 -36 -68 -85 -151
-55 -91 -96 -148 -103 -146 -45 18 -397 242 -385 246 19 6 472 119 484 120 6
0 28 -10 50 -23z"/>
<path d="M911 1718 c-20 -5 -30 -13 -25 -18 10 -10 82 5 99 21 12 12 -29 10
-74 -3z"/>
<path d="M632 1639 c5 -16 445 -287 468 -288 30 -2 -28 38 -240 164 -118 71
-219 130 -224 133 -5 3 -7 -1 -4 -9z"/>
<path d="M660 1518 c14 -15 316 -198 326 -198 24 0 -20 33 -147 110 -145 89
-214 123 -179 88z"/>
<path d="M1720 1700 c-45 -45 -11 -120 53 -120 42 0 67 26 67 68 0 41 -31 72
-70 72 -17 0 -39 -9 -50 -20z m81 -10 c22 -12 26 -59 7 -78 -19 -19 -66 -15
-78 7 -14 27 -13 47 6 65 18 19 38 20 65 6z"/>
<path d="M374 1195 c-15 -23 -15 -27 0 -50 28 -42 96 -24 96 25 0 49 -68 67
-96 25z m66 -10 c10 -12 10 -18 0 -30 -25 -30 -61 -7 -46 30 3 8 12 15 19 15
8 0 20 -7 27 -15z"/>
<path d="M577 1133 c-11 -10 -8 -171 3 -178 6 -4 10 28 10 89 0 96 -1 102 -13
89z"/>
<path d="M367 958 c73 -73 136 -129 140 -125 9 10 -237 257 -257 257 -8 0 45
-59 117 -132z"/>
<path d="M1030 885 c-24 -24 -41 -48 -37 -52 8 -7 97 75 97 89 0 17 -18 6 -60
-37z"/>
<path d="M1256 914 c-19 -18 -21 -55 -4 -72 15 -15 61 -15 76 0 7 7 12 24 12
38 0 14 -5 31 -12 38 -17 17 -54 15 -72 -4z m59 -34 c0 -18 -6 -26 -23 -28
-27 -4 -40 22 -22 44 19 22 45 13 45 -16z"/>
<path d="M1075 750 c4 -6 67 -10 156 -10 93 0 149 4 149 10 0 6 -59 10 -156
10 -101 0 -153 -3 -149 -10z"/>
<path d="M370 710 c0 -5 20 -10 44 -10 25 0 48 5 51 10 4 6 -13 10 -44 10 -28
0 -51 -4 -51 -10z"/>
<path d="M564 637 c-3 -8 -4 -45 -2 -83 l3 -69 683 -3 c619 -2 683 -1 689 14
3 9 0 20 -8 25 -8 5 -186 9 -396 9 l-383 0 0 44 c0 82 22 76 -296 76 -230 0
-286 -3 -290 -13z m536 -67 l0 -40 -245 0 -245 0 0 40 0 40 245 0 245 0 0 -40z"/>
<path d="M740 420 c0 -7 168 -10 485 -10 317 0 485 3 485 10 0 7 -168 10 -485
10 -317 0 -485 -3 -485 -10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,43 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="250.000000pt" viewBox="0 0 250.000000 250.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,250.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1527 2144 c-4 -4 -7 -25 -7 -46 0 -31 -3 -38 -20 -38 -18 0 -20 -7
-20 -80 l0 -80 -335 0 -335 0 0 41 c0 33 -3 40 -17 37 -13 -2 -19 -14 -21 -41
-3 -35 -5 -37 -40 -37 -89 0 -160 -42 -193 -115 -16 -33 -19 -67 -19 -202 l0
-163 28 -31 c24 -27 37 -33 85 -37 l56 -4 3 -37 c3 -34 5 -36 41 -39 l37 -3 0
-60 c0 -52 2 -59 20 -59 18 0 20 7 20 60 l0 60 39 0 c41 0 51 11 51 56 0 21 5
24 34 24 77 0 126 39 126 100 l0 30 210 0 210 0 0 -213 c0 -237 -5 -260 -70
-304 -33 -23 -37 -23 -384 -23 -387 0 -398 -2 -458 -66 -42 -46 -48 -75 -48
-243 0 -140 2 -160 18 -174 17 -16 81 -17 714 -17 660 0 696 1 711 18 15 17
17 76 17 653 0 593 -2 637 -19 674 -33 73 -104 115 -193 115 l-37 0 -3 123
c-3 100 -6 122 -18 122 -12 0 -15 -22 -18 -122 l-3 -123 -39 0 -40 0 0 64 c0
77 -6 96 -30 96 -15 0 -20 9 -22 42 -3 39 -16 57 -31 42z m33 -184 c0 -53 -2
-60 -20 -60 -18 0 -20 7 -20 60 0 53 2 60 20 60 18 0 20 -7 20 -60z m-818
-247 l3 -138 48 -3 47 -3 0 140 0 141 90 0 90 0 0 -207 c0 -178 -2 -209 -17
-225 -15 -16 -35 -18 -214 -18 l-199 0 -16 25 c-24 37 -17 319 9 358 25 39 75
67 119 67 l37 0 3 -137z m78 52 l0 -85 -30 0 -30 0 0 85 0 85 30 0 30 0 0 -85z
m660 -80 l0 -165 -210 0 -210 0 0 45 0 45 183 2 c113 2 182 7 182 13 0 6 -69
11 -182 13 l-183 2 0 105 0 105 210 0 210 0 0 -165z m387 145 c70 -42 68 -23
71 -637 l3 -553 -692 0 -692 0 5 79 c5 88 20 120 71 151 31 19 54 20 552 20
286 0 526 4 534 9 8 5 11 16 8 25 -5 14 -27 16 -138 16 -125 0 -131 1 -117 18
48 54 48 57 48 487 l0 405 158 0 c138 0 161 -2 189 -20z m-1047 -205 c0 -32
-2 -35 -30 -35 -28 0 -30 3 -30 35 0 32 2 35 30 35 28 0 30 -3 30 -35z m30
-295 c0 -18 -7 -20 -60 -20 -53 0 -60 2 -60 20 0 18 7 20 60 20 53 0 60 -2 60
-20z m1090 -773 c0 -29 -5 -58 -12 -65 -17 -17 -1339 -17 -1356 0 -7 7 -12 36
-12 65 l0 53 690 0 690 0 0 -53z"/>
<path d="M1577 1634 c-4 -4 -7 -38 -7 -76 l0 -69 158 3 157 3 0 70 0 70 -151
3 c-82 1 -153 -1 -157 -4z m283 -74 l0 -50 -135 0 -135 0 0 50 0 50 135 0 135
0 0 -50z"/>
<path d="M1664 1330 c-33 -13 -82 -77 -90 -115 -9 -50 19 -119 62 -151 46 -36
100 -41 157 -16 153 68 103 294 -65 291 -24 0 -52 -4 -64 -9z m163 -53 c18
-20 33 -46 33 -57 0 -19 -6 -20 -130 -20 -126 0 -130 1 -130 21 0 12 18 39 40
61 39 39 42 40 97 36 49 -4 60 -9 90 -41z m33 -114 c0 -49 -73 -113 -129 -113
-59 0 -131 60 -131 109 0 20 5 21 130 21 117 0 130 -2 130 -17z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -0,0 +1,62 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="250.000000pt" height="251.000000pt" viewBox="0 0 250.000000 251.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,251.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M750 2120 c0 -37 36 -239 44 -247 15 -15 14 11 -5 128 -20 116 -39
176 -39 119z"/>
<path d="M540 2043 c0 -13 82 -133 91 -133 15 0 10 12 -32 77 -37 56 -59 77
-59 56z"/>
<path d="M986 2008 c-8 -13 -36 -71 -62 -130 -26 -60 -53 -108 -60 -108 -7 0
-111 20 -230 45 -129 27 -221 42 -227 36 -5 -5 -19 -57 -32 -117 l-23 -107 26
-56 c28 -59 34 -62 98 -48 29 5 33 3 45 -26 22 -57 35 -66 80 -56 22 5 44 7
49 4 5 -3 16 -24 25 -48 l16 -42 -126 -280 -126 -279 3 -136 3 -135 451 -3
c354 -2 454 1 461 10 6 7 50 99 98 205 83 183 88 191 104 173 13 -16 22 -18
53 -11 30 7 37 6 41 -8 6 -25 389 -104 423 -89 29 14 29 13 101 364 l58 283
-21 22 c-16 16 -63 29 -195 56 -96 20 -177 39 -182 42 -4 4 9 46 30 92 l38 85
-3 137 -3 137 -434 0 c-238 0 -440 3 -448 6 -10 3 -21 -4 -31 -18z m399 -60
c-7 -18 -38 -85 -68 -149 l-54 -116 -164 34 c-90 18 -167 34 -170 36 -4 1 16
53 43 115 l51 112 188 0 188 0 -14 -32z m345 28 c0 -2 -13 -33 -30 -69 -33
-73 -34 -77 -21 -77 5 0 25 34 45 76 l36 76 38 -7 c20 -4 38 -8 39 -9 3 -3
-154 -357 -161 -364 -4 -3 -266 47 -274 53 -1 1 20 50 48 110 27 59 50 111 50
117 0 26 -27 -15 -72 -113 l-51 -109 -36 6 c-20 4 -39 10 -44 13 -4 4 23 73
60 154 l67 147 153 0 c84 0 153 -2 153 -4z m91 -301 c-39 -87 -56 -105 -84
-88 -11 6 0 38 52 155 l66 148 3 -63 c2 -55 -2 -74 -37 -152z m-383 -73 c183
-38 334 -71 337 -74 3 -3 -83 -437 -110 -554 -5 -24 -62 -39 -80 -22 -5 5 -12
77 -15 161 l-5 152 -281 3 -280 2 -18 36 -17 37 -52 -7 -53 -6 -19 43 -20 42
-52 -3 -53 -3 -16 40 c-18 46 -34 54 -87 42 l-38 -9 -19 44 c-22 49 -26 52
-91 40 -35 -6 -37 -5 -52 32 -14 34 -14 46 -1 108 8 38 17 76 19 85 6 17 67 6
1003 -189z m420 -91 c11 -6 1 -67 -49 -308 -61 -296 -62 -300 -85 -295 -13 2
-24 8 -24 12 0 4 27 137 60 295 33 158 60 292 60 296 0 11 21 11 38 0z m187
-37 c66 -14 124 -29 129 -34 12 -12 -100 -569 -117 -587 -9 -9 -42 -6 -144 16
-73 15 -135 30 -138 33 -3 3 22 135 55 294 33 159 60 292 60 297 0 12 28 8
155 -19z m-1237 -134 c2 -15 -40 -122 -107 -270 l-110 -245 -45 -3 -44 -3 102
228 c57 125 112 250 124 276 19 43 25 48 49 45 21 -2 29 -9 31 -28z m91 -51
c22 5 42 6 46 3 12 -12 3 -22 -21 -22 -19 0 -25 -4 -22 -17 3 -16 25 -18 250
-23 l246 -5 -92 -202 -91 -203 -93 0 -92 0 13 33 c8 17 33 76 56 131 23 54 40
101 36 104 -10 10 -21 -10 -76 -140 l-53 -128 -193 0 c-106 0 -193 3 -193 8 0
4 46 111 102 238 89 201 105 231 120 223 10 -6 33 -6 57 0z m597 -91 c-7 -18
-48 -111 -91 -205 l-78 -173 -44 0 c-24 0 -42 3 -40 8 2 4 44 96 93 205 l89
197 42 0 43 0 -14 -32z m-48 -361 l-83 -181 -3 64 c-3 61 0 71 80 248 l83 185
3 -67 c3 -66 1 -72 -80 -249z m-868 -167 l0 -100 -45 0 -45 0 0 100 0 100 45
0 45 0 0 -100z m420 44 c0 -34 4 -53 10 -49 6 3 10 28 10 56 l0 49 90 0 90 0
0 -100 0 -100 -215 0 -215 0 0 49 c0 28 -4 53 -10 56 -6 4 -10 -15 -10 -49 l0
-56 -75 0 -75 0 0 100 0 100 200 0 200 0 0 -56z m320 -44 l0 -100 -45 0 -45 0
0 100 0 100 45 0 45 0 0 -100z"/>
<path d="M1927 1403 c-11 -18 -88 -415 -84 -427 6 -15 143 -44 153 -33 12 12
95 430 87 438 -9 8 -104 29 -133 29 -10 0 -21 -3 -23 -7z m109 -56 c2 -2 -13
-83 -33 -180 -36 -174 -37 -177 -62 -177 -61 1 -61 0 6 304 l15 68 35 -5 c20
-4 37 -8 39 -10z"/>
<path d="M472 1334 c-39 -27 -19 -94 27 -94 24 0 51 31 51 58 0 18 -33 52 -50
52 -3 0 -16 -7 -28 -16z m55 -30 c8 -21 -19 -46 -40 -38 -17 6 -23 35 -10 47
12 13 44 7 50 -9z"/>
<path d="M1767 648 c62 -62 116 -109 120 -105 9 10 -197 217 -217 217 -8 0 36
-50 97 -112z"/>
<path d="M1580 611 c0 -41 4 -71 10 -71 6 0 10 28 10 64 0 36 -4 68 -10 71 -6
4 -10 -20 -10 -64z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Verbund Offener Werkstätten
SPDX-License-Identifier: CC-By-SA

View file

@ -289,6 +289,35 @@
} }
], ],
"condition": "conveying!=yes" "condition": "conveying!=yes"
},
{
"id": "incline",
"render": {
"en": "These stairs have an incline of {incline}"
},
"freeform": {
"key": "incline",
"type": "slope"
},
"question": {
"en": "What is the incline of these stairs?"
},
"mappings": [
{
"if": "incline=up",
"then": {
"en": "The upward direction is {direction_absolute()}"
},
"hideInAnswer": true
},
{
"if": "incline=down",
"then": {
"en": "The downward direction is {direction_absolute()}"
},
"hideInAnswer": true
}
]
} }
] ]
} }

View file

@ -113,6 +113,33 @@
"*": "{logout()}" "*": "{logout()}"
} }
}, },
{
"id": "a11y-features",
"question": {
"en": "What accessibility features should be applied?"
},
"mappings": [
{
"if": "mapcomplete-a11y=default",
"alsoShowIf": "mapcomplete-a11y=",
"then": {
"en": "Enable accessibility features when arrow keys are used to navigate the map"
}
},
{
"if": "mapcomplete-a11y=always",
"then": {
"en": "Always enable accessibility features"
}
},
{
"if": "mapcomplete-a11y=never",
"then": {
"en": "Never enable accessibility features"
}
}
]
},
{ {
"id": "background-layer-readonly", "id": "background-layer-readonly",
"condition": { "condition": {

View file

@ -22,7 +22,8 @@
"startZoom": 9, "startZoom": 9,
"startLat": 51.0249, "startLat": 51.0249,
"startLon": 4.026489, "startLon": 4.026489,
"defaultBackgroundId": "AGIVFlandersGRB",
"defaultBackgroundId": "osm",
"credits": [ "credits": [
"Pieter Vander Vennet" "Pieter Vander Vennet"
], ],

View file

@ -63,7 +63,7 @@
"startZoom": 8, "startZoom": 8,
"startLat": 50.642, "startLat": 50.642,
"startLon": 4.482, "startLon": 4.482,
"defaultBackgroundId": "AGIV", "defaultBackgroundId": "photo",
"credits": [ "credits": [
"Midgard" "Midgard"
], ],

View file

@ -53,7 +53,7 @@
<div id="main"></div> <div id="main"></div>
<script type="module" src="./src/all_themes_index.ts"></script> <script type="module" src="./src/all_themes_index.ts"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
<script> <script>
window.addEventListener('load', () => { window.addEventListener('load', () => {

View file

@ -8543,6 +8543,18 @@
}, },
"question": "Does this stair have a handrail?" "question": "Does this stair have a handrail?"
}, },
"incline": {
"mappings": {
"0": {
"then": "The upward direction is {direction_absolute()}"
},
"1": {
"then": "The downward direction is {direction_absolute()}"
}
},
"question": "What is the incline of these stairs?",
"render": "These stairs have an incline of {incline}"
},
"multilevels": { "multilevels": {
"override": { "override": {
"question": "Between which levels are these stairs?", "question": "Between which levels are these stairs?",
@ -9722,6 +9734,20 @@
"usersettings": { "usersettings": {
"description": "A special layer which is not meant to be shown on a map, but which is used to set user settings", "description": "A special layer which is not meant to be shown on a map, but which is used to set user settings",
"tagRenderings": { "tagRenderings": {
"a11y-features": {
"mappings": {
"0": {
"then": "Enable accessibility features when arrow keys are used to navigate the map"
},
"1": {
"then": "Always enable accessibility features"
},
"2": {
"then": "Never enable accessibility features"
}
},
"question": "What accessibility features should be applied?"
},
"all-questions-at-once": { "all-questions-at-once": {
"mappings": { "mappings": {
"0": { "0": {

View file

@ -370,14 +370,15 @@
"useSearch": "Gebruik de zoekfunctie hierboven om meer opties te zien", "useSearch": "Gebruik de zoekfunctie hierboven om meer opties te zien",
"useSearchForMore": "Gebruik de zoekfunctie om {total} meer waarden te vinden…", "useSearchForMore": "Gebruik de zoekfunctie om {total} meer waarden te vinden…",
"visualFeedback": { "visualFeedback": {
"closestFeaturesAre": "{n} object in beeld.", "closestFeaturesAre": "{n} objecten in beeld.",
"east": "Naar het oosten", "east": "Naar het oosten",
"in": "Aan het inzoomen naar zoomlevel {z}", "in": "Aan het inzoomen naar zoomlevel {z}",
"islocked": "Bewegen vergrendeld rond je huidige locatie. Duw op de geolocatie-knop om te ontgrendelen.", "islocked": "Bewegen vergrendeld rond je huidige locatie. Duw op de geolocatie-knop om te ontgrendelen.",
"locked": "Bewegen vergrendeld rond jouw huidige locatie.", "locked": "Bewegen vergrendeld rond jouw huidige locatie.",
"navigation": "Gebruik de pijltjestoetsen om te bewegen. Druk op spatie om het meest centrale punt te selecteren. Druk op een cijfertoets om andere items te selecteren.", "navigation": "Gebruik de pijltjestoetsen om te bewegen. Druk op spatie om het meest centrale punt te selecteren. Druk op een cijfertoets om andere items te selecteren.",
"noCloseFeatures": "Niet in beeld", "noCloseFeatures": "Geen objecten in beeld",
"north": "Naar het noorden", "north": "Naar het noorden",
"oneFeatureInView": "Eén object in beeld.",
"out": "Aan het uitzoomen naar zoomlevel {z}", "out": "Aan het uitzoomen naar zoomlevel {z}",
"south": "Naar het zuiden", "south": "Naar het zuiden",
"unlocked": "Bewegen ontgrendeld", "unlocked": "Bewegen ontgrendeld",

View file

@ -53,7 +53,7 @@
<div id="main"></div> <div id="main"></div>
<script type="module" src="./src/leaderboard.ts"></script> <script type="module" src="./src/leaderboard.ts"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
<script> <script>
window.addEventListener('load', () => { window.addEventListener('load', () => {

5
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.36.9", "version": "0.36.11",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.36.9", "version": "0.36.11",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@rgossiaux/svelte-headlessui": "^1.0.2", "@rgossiaux/svelte-headlessui": "^1.0.2",
@ -15,6 +15,7 @@
"@turf/boolean-intersects": "^6.5.0", "@turf/boolean-intersects": "^6.5.0",
"@turf/buffer": "^6.5.0", "@turf/buffer": "^6.5.0",
"@turf/collect": "^6.5.0", "@turf/collect": "^6.5.0",
"@turf/difference": "^6.5.0",
"@turf/distance": "^6.5.0", "@turf/distance": "^6.5.0",
"@turf/length": "^6.5.0", "@turf/length": "^6.5.0",
"@turf/turf": "^6.5.0", "@turf/turf": "^6.5.0",

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.36.9", "version": "0.36.11",
"repository": "https://github.com/pietervdvn/MapComplete", "repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues", "bugs": "https://github.com/pietervdvn/MapComplete/issues",
@ -103,6 +103,7 @@
"@turf/boolean-intersects": "^6.5.0", "@turf/boolean-intersects": "^6.5.0",
"@turf/buffer": "^6.5.0", "@turf/buffer": "^6.5.0",
"@turf/collect": "^6.5.0", "@turf/collect": "^6.5.0",
"@turf/difference": "^6.5.0",
"@turf/distance": "^6.5.0", "@turf/distance": "^6.5.0",
"@turf/length": "^6.5.0", "@turf/length": "^6.5.0",
"@turf/turf": "^6.5.0", "@turf/turf": "^6.5.0",

View file

@ -39,7 +39,7 @@
<div id="main"></div> <div id="main"></div>
<script type="module" src="./src/privacy_index.ts"></script> <script type="module" src="./src/privacy_index.ts"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
</body> </body>

View file

@ -1071,14 +1071,14 @@ video {
height: 6rem; height: 6rem;
} }
.h-full {
height: 100%;
}
.h-screen { .h-screen {
height: 100vh; height: 100vh;
} }
.h-full {
height: 100%;
}
.h-32 { .h-32 {
height: 8rem; height: 8rem;
} }
@ -2318,14 +2318,6 @@ input[type=text] {
width: 100%; width: 100%;
} }
.debug input, .debug textarea {
border: 6px solid red
}
.debug label input, .debug label textarea {
border: 1px solid grey;
}
/************************* BIG CATEGORIES ********************************/ /************************* BIG CATEGORIES ********************************/
/** /**

View file

@ -284,7 +284,7 @@ async function generateCsp(
if (typeof sv.needsUrls === "function") { if (typeof sv.needsUrls === "function") {
return return
} }
apiUrls.push(...sv.needsUrls) apiUrls.push(...(sv.needsUrls ?? []))
}) })
const usedSpecialVisualisations = ValidationUtils.getSpecialVisualisationsWithArgs(layoutJson) const usedSpecialVisualisations = ValidationUtils.getSpecialVisualisationsWithArgs(layoutJson)
@ -292,7 +292,7 @@ async function generateCsp(
if (typeof usedSpecialVisualisation === "string") { if (typeof usedSpecialVisualisation === "string") {
continue continue
} }
const neededUrls = usedSpecialVisualisation.func.needsUrls const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
if (typeof neededUrls === "function") { if (typeof neededUrls === "function") {
apiUrls.push(...neededUrls(usedSpecialVisualisation.args)) apiUrls.push(...neededUrls(usedSpecialVisualisation.args))
} }

View file

@ -4,6 +4,10 @@ import { Review } from "mangrove-reviews-typescript"
import { parse } from "csv-parse" import { parse } from "csv-parse"
import { Feature, FeatureCollection, Point } from "geojson" import { Feature, FeatureCollection, Point } from "geojson"
/**
* To be run from the repository root, e.g.
* vite-node scripts/generateReviewsAnalysis.ts -- ~/Downloads/mangrove.reviews_1704031255.csv
*/
export default class GenerateReviewsAnalysis extends Script { export default class GenerateReviewsAnalysis extends Script {
constructor() { constructor() {
super("Analyses a CSV-file with Mangrove reviews") super("Analyses a CSV-file with Mangrove reviews")
@ -104,6 +108,11 @@ export default class GenerateReviewsAnalysis extends Script {
} }
async main(args: string[]): Promise<void> { async main(args: string[]): Promise<void> {
if (args.length === 0) {
console.log(
"Usage: enter file path of mangrove.reviews_timestamp.csv as first argument"
)
}
const datapath = args[0] ?? "../MapComplete-data/mangrove.reviews_1674234503.csv" const datapath = args[0] ?? "../MapComplete-data/mangrove.reviews_1674234503.csv"
await this.analyze(datapath) await this.analyze(datapath)
} }

View file

@ -28,3 +28,15 @@ studio.mapcomplete.org {
to http://127.0.0.1:1235 to http://127.0.0.1:1235
} }
} }
bounce.mapcomplete.org {
reverse_proxy {
to http://127.0.0.1:1236
}
}
mapcomplete.osm.be {
reverse_proxy {
to http://127.0.0.1:1236
}
}

View file

@ -0,0 +1,45 @@
import * as http from "node:http"
/**
* Redirect people from
* "mapcomplete.osm.be/path?query=parameter#id" to "mapcomplete.org/path?query=parameter#id"
*/
const PORT = 1236
const CORS = "http://localhost:1234,https://mapcomplete.org,https://dev.mapcomplete.org"
async function redirect(req: http.IncomingMessage, res: http.ServerResponse) {
try {
console.log(
req.method + " " + req.url,
"from:",
req.headers.origin,
new Date().toISOString()
)
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
)
res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*")
if (req.method === "OPTIONS") {
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, UPDATE")
res.writeHead(204, { "Content-Type": "text/html" })
res.end()
return
}
console.log("Request url:", req.url)
const oldUrl = new URL("https://127.0.0.1:8080" + req.url)
const newUrl = "https://mapcomplete.org" + oldUrl.pathname + oldUrl.search + oldUrl.hash
res.writeHead(301, { "Content-Type": "text/html", Location: newUrl })
res.write("Moved permantently")
res.end()
} catch (e) {
console.error(e)
}
}
http.createServer(redirect).listen(PORT)
console.log(
`Server started at http://127.0.0.1:${PORT}/, the time is ${new Date().toISOString()}, version from package.json is`
)

View file

@ -264,7 +264,8 @@ class ClosestNObjectFunc implements ExtraFunction {
const bbox = GeoOperations.bbox( const bbox = GeoOperations.bbox(
GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance) GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)
) )
allFeatures = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates)) const coors = <[number, number][]>bbox.geometry.coordinates
allFeatures = params.getFeaturesWithin(name, new BBox(coors))
} else { } else {
allFeatures = [features] allFeatures = [features]
} }

View file

@ -39,8 +39,8 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
}) })
} }
protected addData(featuress: Feature[][]) { protected addData(sources: Feature[][]) {
featuress = Utils.NoNull(featuress) sources = Utils.NoNull(sources)
let somethingChanged = false let somethingChanged = false
const all: Map<string, Feature> = new Map() const all: Map<string, Feature> = new Map()
const unseen = new Set<string>() const unseen = new Set<string>()
@ -51,7 +51,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
unseen.add(oldValue.properties.id) unseen.add(oldValue.properties.id)
} }
for (const features of featuress) { for (const features of sources) {
for (const f of features) { for (const f of features) {
const id = f.properties.id const id = f.properties.id
unseen.delete(id) unseen.delete(id)

View file

@ -6,7 +6,7 @@ import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
/*** /***
* A tiled source which dynamically loads the required tiles at a fixed zoom level. * A tiled source which dynamically loads the required tiles at a fixed zoom level.
* A single featureSource will be initiliased for every tile in view; which will alter be merged into this featureSource * A single featureSource will be initialized for every tile in view; which will later be merged into this featureSource
*/ */
export default class DynamicTileSource extends FeatureSourceMerger { export default class DynamicTileSource extends FeatureSourceMerger {
constructor( constructor(

View file

@ -19,6 +19,35 @@ import { Utils } from "../Utils"
export class GeoOperations { export class GeoOperations {
private static readonly _earthRadius = 6378137 private static readonly _earthRadius = 6378137
private static readonly _originShift = (2 * Math.PI * GeoOperations._earthRadius) / 2 private static readonly _originShift = (2 * Math.PI * GeoOperations._earthRadius) / 2
private static readonly directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] as const
private static readonly directionsRelative = [
"straight",
"slight_right",
"right",
"sharp_right",
"behind",
"sharp_left",
"left",
"slight_left",
] as const
private static reverseBearing = {
N: 0,
NNE: 22.5,
NE: 45,
ENE: 67.5,
E: 90,
ESE: 112.5,
SE: 135,
SSE: 157.5,
S: 180,
SSW: 202.5,
SW: 225,
WSW: 247.5,
W: 270,
WNW: 292.5,
NW: 315,
NNW: 337.5,
}
/** /**
* Create a union between two features * Create a union between two features
@ -124,7 +153,7 @@ export class GeoOperations {
continue continue
} }
const intersection = GeoOperations.calculateInstersection( const intersection = GeoOperations.calculateIntersection(
feature, feature,
otherFeature, otherFeature,
featureBBox featureBBox
@ -155,7 +184,7 @@ export class GeoOperations {
// Calculate the surface area of the intersection // Calculate the surface area of the intersection
const intersection = this.calculateInstersection(feature, otherFeature, featureBBox) const intersection = this.calculateIntersection(feature, otherFeature, featureBBox)
if (intersection === null) { if (intersection === null) {
continue continue
} }
@ -293,9 +322,11 @@ export class GeoOperations {
* @param way * @param way
*/ */
public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString> public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
public static forceLineString( public static forceLineString(
way: Feature<MultiLineString | MultiPolygon> way: Feature<MultiLineString | MultiPolygon>
): Feature<MultiLineString> ): Feature<MultiLineString>
public static forceLineString( public static forceLineString(
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon> way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
): Feature<LineString | MultiLineString> { ): Feature<LineString | MultiLineString> {
@ -800,6 +831,150 @@ export class GeoOperations {
return { lon, lat } return { lon, lat }
} }
public static SplitSelfIntersectingWays(features: Feature[]): Feature[] {
const result: Feature[] = []
for (const feature of features) {
if (feature.geometry.type === "LineString") {
let coors = feature.geometry.coordinates
for (let i = coors.length - 1; i >= 0; i--) {
// Go back, to nick of the back when needed
const ci = coors[i]
for (let j = i + 1; j < coors.length; j++) {
const cj = coors[j]
if (
Math.abs(ci[0] - cj[0]) <= 0.000001 &&
Math.abs(ci[1] - cj[1]) <= 0.0000001
) {
// Found a self-intersecting way!
console.debug("SPlitting way", feature.properties.id)
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) },
})
coors = coors.slice(0, i + 1)
break
}
}
}
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors },
})
}
}
return result
}
/**
* GeoOperations.distanceToHuman(52.8) // => "53m"
* GeoOperations.distanceToHuman(2800) // => "2.8km"
* GeoOperations.distanceToHuman(12800) // => "13km"
*
* @param meters
*/
public static distanceToHuman(meters: number): string {
if (meters === undefined) {
return ""
}
meters = Math.round(meters)
if (meters < 1000) {
return meters + "m"
}
if (meters >= 10000) {
const km = Math.round(meters / 1000)
return km + "km"
}
meters = Math.round(meters / 100)
const kmStr = "" + meters
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km"
}
/**
* GeoOperations.parseBearing("N") // => 0
* GeoOperations.parseBearing("E") // => 90
* GeoOperations.parseBearing("NE") // => 45
* GeoOperations.parseBearing("NNE") // => 22.5
*
* GeoOperations.parseBearing("90") // => 90
* GeoOperations.parseBearing("-90°") // => 270
* GeoOperations.parseBearing("180 °") // => 180
*
* GeoOperations.parseBearing(180) // => 180
* GeoOperations.parseBearing(-270) // => 90
*
*/
public static parseBearing(str: string | number) {
let n: number
if (typeof str === "string") {
str = str.trim()
if (str.endsWith("°")) {
str = str.substring(0, str.length - 1).trim()
}
n = Number(str)
} else {
n = str
}
if (!isNaN(n)) {
while (n < 0) {
n += 360
}
return n % 360
}
return GeoOperations.reverseBearing[str]
}
/**
* GeoOperations.bearingToHuman(0) // => "N"
* GeoOperations.bearingToHuman(-9) // => "N"
* GeoOperations.bearingToHuman(-10) // => "N"
* GeoOperations.bearingToHuman(-180) // => "S"
* GeoOperations.bearingToHuman(181) // => "S"
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHuman(
bearing: number
): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
while (bearing < 0) {
bearing += 360
}
bearing %= 360
bearing += 22.5
const segment = Math.floor(bearing / 45) % GeoOperations.directions.length
return GeoOperations.directions[segment]
}
/**
* GeoOperations.bearingToHuman(0) // => "N"
* GeoOperations.bearingToHuman(-10) // => "N"
* GeoOperations.bearingToHuman(-180) // => "S"
* GeoOperations.bearingToHuman(181) // => "S"
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHumanRelative(
bearing: number
):
| "straight"
| "slight_right"
| "right"
| "sharp_right"
| "behind"
| "sharp_left"
| "left"
| "slight_left" {
while (bearing < 0) {
bearing += 360
}
bearing %= 360
bearing += 22.5
const segment = Math.floor(bearing / 45) % GeoOperations.directionsRelative.length
return GeoOperations.directionsRelative[segment]
}
/** /**
* Helper function which does the heavy lifting for 'inside' * Helper function which does the heavy lifting for 'inside'
*/ */
@ -854,7 +1029,7 @@ export class GeoOperations {
* Returns 0 if both are linestrings * Returns 0 if both are linestrings
* Returns null if the features are not intersecting * Returns null if the features are not intersecting
*/ */
private static calculateInstersection( private static calculateIntersection(
feature, feature,
otherFeature, otherFeature,
featureBBox: BBox, featureBBox: BBox,
@ -924,7 +1099,7 @@ export class GeoOperations {
return null return null
} }
if (otherFeature.geometry.type === "LineString") { if (otherFeature.geometry.type === "LineString") {
return this.calculateInstersection( return this.calculateIntersection(
otherFeature, otherFeature,
feature, feature,
otherFeatureBBox, otherFeatureBBox,
@ -944,131 +1119,22 @@ export class GeoOperations {
// See https://github.com/Turfjs/turf/pull/2238 // See https://github.com/Turfjs/turf/pull/2238
return null return null
} }
if (e.message.indexOf("SweepLine tree") >= 0) {
console.log("Applying fallback intersection...")
const intersection = turf.intersect(
turf.truncate(feature),
turf.truncate(otherFeature)
)
if (intersection == null) {
return null
}
return turf.area(intersection) // in m²
// Another workaround: https://github.com/Turfjs/turf/issues/2258
}
throw e throw e
} }
} }
throw "CalculateIntersection fallthrough: can not calculate an intersection between features" throw "CalculateIntersection fallthrough: can not calculate an intersection between features"
} }
public static SplitSelfIntersectingWays(features: Feature[]): Feature[] {
const result: Feature[] = []
for (const feature of features) {
if (feature.geometry.type === "LineString") {
let coors = feature.geometry.coordinates
for (let i = coors.length - 1; i >= 0; i--) {
// Go back, to nick of the back when needed
const ci = coors[i]
for (let j = i + 1; j < coors.length; j++) {
const cj = coors[j]
if (
Math.abs(ci[0] - cj[0]) <= 0.000001 &&
Math.abs(ci[1] - cj[1]) <= 0.0000001
) {
// Found a self-intersecting way!
console.debug("SPlitting way", feature.properties.id)
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) },
})
coors = coors.slice(0, i + 1)
break
}
}
}
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors },
})
}
}
return result
}
/**
* GeoOperations.distanceToHuman(52.8) // => "53m"
* GeoOperations.distanceToHuman(2800) // => "2.8km"
* GeoOperations.distanceToHuman(12800) // => "13km"
*
* @param meters
*/
public static distanceToHuman(meters: number): string {
if (meters === undefined) {
return ""
}
meters = Math.round(meters)
if (meters < 1000) {
return meters + "m"
}
if (meters >= 10000) {
const km = Math.round(meters / 1000)
return km + "km"
}
meters = Math.round(meters / 100)
const kmStr = "" + meters
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km"
}
private static readonly directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] as const
private static readonly directionsRelative = [
"straight",
"slight_right",
"right",
"sharp_right",
"behind",
"sharp_left",
"left",
"slight_left",
] as const
/**
* GeoOperations.bearingToHuman(0) // => "N"
* GeoOperations.bearingToHuman(-9) // => "N"
* GeoOperations.bearingToHuman(-10) // => "N"
* GeoOperations.bearingToHuman(-180) // => "S"
* GeoOperations.bearingToHuman(181) // => "S"
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHuman(
bearing: number
): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
while (bearing < 0) {
bearing += 360
}
bearing %= 360
bearing += 22.5
const segment = Math.floor(bearing / 45) % GeoOperations.directions.length
return GeoOperations.directions[segment]
}
/**
* GeoOperations.bearingToHuman(0) // => "N"
* GeoOperations.bearingToHuman(-10) // => "N"
* GeoOperations.bearingToHuman(-180) // => "S"
* GeoOperations.bearingToHuman(181) // => "S"
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHumanRelative(
bearing: number
):
| "straight"
| "slight_right"
| "right"
| "sharp_right"
| "behind"
| "sharp_left"
| "left"
| "slight_left" {
while (bearing < 0) {
bearing += 360
}
bearing %= 360
bearing += 22.5
const segment = Math.floor(bearing / 45) % GeoOperations.directionsRelative.length
return GeoOperations.directionsRelative[segment]
}
} }

View file

@ -84,10 +84,20 @@ export class Mapillary extends ImageProvider {
private static ExtractKeyFromURL(value: string): number { private static ExtractKeyFromURL(value: string): number {
let key: string let key: string
const newApiFormat = value.match(/https?:\/\/www.mapillary.com\/app\/\?pKey=([0-9]*)/) if (value.startsWith("http")) {
if (newApiFormat !== null) { try {
key = newApiFormat[1] const url = new URL(value.toLowerCase())
} else if (value.startsWith(Mapillary.valuePrefix)) { if (url.searchParams.has("pkey")) {
const pkey = Number(url.searchParams.get("pkey"))
if (!isNaN(pkey)) {
return pkey
}
}
} catch (e) {
console.log("Could not parse value for mapillary:", value)
}
}
if (value.startsWith(Mapillary.valuePrefix)) {
key = value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1) key = value.substring(0, value.lastIndexOf("?")).substring(value.lastIndexOf("/") + 1)
} else if (value.match("[0-9]*")) { } else if (value.match("[0-9]*")) {
key = value key = value

View file

@ -274,17 +274,20 @@ export default class MetaTagging {
console.warn( console.warn(
"Could not calculate a " + "Could not calculate a " +
(isStrict ? "strict " : "") + (isStrict ? "strict " : "") +
" calculated tag for key " + "calculated tag for key",
key + key,
" defined by " + "for feature",
code + feat.properties.id,
" (in layer" + " defined by",
code,
"(in layer",
layerId + layerId +
") due to \n" + ") due to \n" +
e + e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", "\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e, e,
e.stack e.stack,
{ feat }
) )
MetaTagging.errorPrintCount++ MetaTagging.errorPrintCount++
if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) { if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) {

View file

@ -148,7 +148,6 @@ export class Changes {
} }
public applyChanges(changes: ChangeDescription[]) { public applyChanges(changes: ChangeDescription[]) {
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes) this.pendingChanges.data.push(...changes)
this.pendingChanges.ping() this.pendingChanges.ping()
this.allChanges.data.push(...changes) this.allChanges.data.push(...changes)
@ -191,7 +190,6 @@ export class Changes {
} }
// This is a new object that should be created // This is a new object that should be created
states.set(id, "created") states.set(id, "created")
console.log("Creating object for changeDescription", change)
let osmObj: OsmObject = undefined let osmObj: OsmObject = undefined
switch (change.type) { switch (change.type) {
case "node": case "node":
@ -255,7 +253,6 @@ export class Changes {
const nlon = Utils.Round7(change.changes.lon) const nlon = Utils.Round7(change.changes.lon)
const n = <OsmNode>obj const n = <OsmNode>obj
if (n.lat !== nlat || n.lon !== nlon) { if (n.lat !== nlat || n.lon !== nlon) {
console.log("Node moved:", n.lat, nlat, n.lon, nlon)
n.lat = nlat n.lat = nlat
n.lon = nlon n.lon = nlon
changed = true changed = true
@ -443,7 +440,6 @@ export class Changes {
objects.forEach((obj) => SimpleMetaTagger.removeBothTagging(obj.tags)) objects.forEach((obj) => SimpleMetaTagger.removeBothTagging(obj.tags))
} }
console.log("Got the fresh objects!", objects, "pending: ", pending)
if (pending.length == 0) { if (pending.length == 0) {
console.log("No pending changes...") console.log("No pending changes...")
return true return true
@ -528,9 +524,7 @@ export class Changes {
await this._changesetHandler.UploadChangeset( await this._changesetHandler.UploadChangeset(
(csId, remappings) => { (csId, remappings) => {
if (remappings.size > 0) { if (remappings.size > 0) {
console.log("Rewriting pending changes from", pending, "with", remappings)
pending = pending.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings)) pending = pending.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings))
console.log("Result is", pending)
} }
const changes: { const changes: {

View file

@ -144,6 +144,9 @@ class CountryTagger extends SimpleMetaTagger {
tagsSource.data["_country"] = newCountry tagsSource.data["_country"] = newCountry
tagsSource?.ping() tagsSource?.ping()
} else { } else {
// We set, be we don't ping... this is for later
tagsSource.data["_country"] = newCountry
/** /**
* What is this weird construction? * What is this weird construction?
* *
@ -160,7 +163,6 @@ class CountryTagger extends SimpleMetaTagger {
*/ */
window.requestIdleCallback(() => { window.requestIdleCallback(() => {
tagsSource.data["_country"] = newCountry
tagsSource?.ping() tagsSource?.ping()
}) })
} }
@ -478,10 +480,19 @@ export default class SimpleMetaTaggers {
// isOpen is irrelevant // isOpen is irrelevant
return false return false
} }
if (feature.properties.opening_hours === undefined) {
return false
}
if (feature.properties.opening_hours === "24/7") { if (feature.properties.opening_hours === "24/7") {
feature.properties._isOpen = "yes" feature.properties._isOpen = "yes"
return true return true
} }
console.log(
"Calculating opening hours for",
feature.properties.name,
":",
feature.properties.opening_hours
)
// _isOpen is calculated dynamically on every call // _isOpen is calculated dynamically on every call
Object.defineProperty(feature.properties, "_isOpen", { Object.defineProperty(feature.properties, "_isOpen", {
@ -492,7 +503,8 @@ export default class SimpleMetaTaggers {
if (tags.opening_hours === undefined) { if (tags.opening_hours === undefined) {
return return
} }
if (tags._country === undefined) { const country = tags._country
if (country === undefined) {
return return
} }

View file

@ -41,6 +41,7 @@ export default class UserRelatedState {
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined> public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined>
public readonly fixateNorth: UIEventSource<undefined | "yes"> public readonly fixateNorth: UIEventSource<undefined | "yes">
public readonly a11y: UIEventSource<undefined | "always" | "never" | "default">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
/** /**
* The language as saved into the preferences of the user, if logged in. * The language as saved into the preferences of the user, if logged in.
@ -109,6 +110,10 @@ export default class UserRelatedState {
this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags") this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags")
this.showCrosshair = <UIEventSource<any>>this.osmConnection.GetPreference("show_crosshair") this.showCrosshair = <UIEventSource<any>>this.osmConnection.GetPreference("show_crosshair")
this.fixateNorth = <UIEventSource<"yes">>this.osmConnection.GetPreference("fixate-north") this.fixateNorth = <UIEventSource<"yes">>this.osmConnection.GetPreference("fixate-north")
this.a11y = <UIEventSource<"always" | "never" | "default">>(
this.osmConnection.GetPreference("a11y")
)
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")
) )

View file

@ -252,7 +252,7 @@ export default class FeatureReviews {
// `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3 // `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3
const self = this const self = this
return this._name.map(function (name) { return this._name.map(function (name) {
let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}` let uri = `geo:${self._lat},${self._lon}?u=${Math.round(self._uncertainty)}`
if (name) { if (name) {
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)) uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))
} }

View file

@ -83,6 +83,15 @@ export class AvailableRasterLayers {
}) })
) )
} }
public static allIds(): Set<string> {
const all: string[] = []
all.push(...AvailableRasterLayers.globalLayers.map((l) => l.properties.id))
all.push(...AvailableRasterLayers.EditorLayerIndex.map((l) => l.properties.id))
all.push(this.osmCarto.properties.id)
all.push(this.maptilerDefaultLayer.properties.id)
return new Set<string>(all)
}
} }
export class RasterLayerUtils { export class RasterLayerUtils {

View file

@ -55,12 +55,19 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
for (const key in obj) { for (const key in obj) {
let subtarget = target let subtarget = target
if (isTr && target[key] !== undefined) { if (isTr) {
// The target is a translation AND the current object is a translation // The target is a translation AND the current object is a translation
// This means we should recursively replace with the translated value // This means we should recursively replace with the translated value
if (target[key]) {
// A translation is available!
subtarget = target[key] subtarget = target[key]
} else if (target["en"]) {
subtarget = target["en"]
} else {
// Take the first
subtarget = target[Object.keys(target)[0]]
}
} }
obj[key] = replaceRecursive(obj[key], subtarget) obj[key] = replaceRecursive(obj[key], subtarget)
} }
return obj return obj

View file

@ -32,6 +32,7 @@ import { ConfigMeta } from "../../../UI/Studio/configMeta"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import { ConversionContext } from "./ConversionContext" import { ConversionContext } from "./ConversionContext"
import { ExpandRewrite } from "./ExpandRewrite" import { ExpandRewrite } from "./ExpandRewrite"
import { ALL } from "node:dns"
class ExpandFilter extends DesugaringStep<LayerConfigJson> { class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters() private static readonly predefinedFilters = ExpandFilter.load_filters()
@ -1133,9 +1134,43 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
) )
} }
private createTitleIconsBasedOn(
tr: QuestionableTagRenderingConfigJson
): TagRenderingConfigJson | undefined {
const mappings: { if: TagConfigJson; then: string }[] = tr.mappings
?.filter((m) => m.icon !== undefined)
.map((m) => {
const path: string = typeof m.icon === "string" ? m.icon : m.icon.path
const img = `<img class="m-1 h-6 w-6 low-interaction rounded" src='${path}'/>`
return { if: m.if, then: img }
})
if (!mappings || mappings.length === 0) {
return undefined
}
return <TagRenderingConfigJson>{
id: "title_icon_auto_" + tr.id,
mappings,
}
}
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
json = { ...json } json = { ...json }
json.titleIcons = [...json.titleIcons] json.titleIcons = [...json.titleIcons]
const allAutoIndex = json.titleIcons.indexOf(<any>"auto:*")
if (allAutoIndex >= 0) {
const generated = Utils.NoNull(
json.tagRenderings.map((tr) => {
if (typeof tr === "string") {
return undefined
}
return this.createTitleIconsBasedOn(<any>tr)
})
)
json.titleIcons.splice(allAutoIndex, 1, ...generated)
return json
}
for (let i = 0; i < json.titleIcons.length; i++) { for (let i = 0; i < json.titleIcons.length; i++) {
const titleIcon = json.titleIcons[i] const titleIcon = json.titleIcons[i]
if (typeof titleIcon !== "string") { if (typeof titleIcon !== "string") {
@ -1152,14 +1187,9 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
context.enters("titleIcons", i).err("TagRendering with id " + trId + " not found") context.enters("titleIcons", i).err("TagRendering with id " + trId + " not found")
continue continue
} }
const mappings: { if: TagConfigJson; then: string }[] = tr.mappings const generated = this.createTitleIconsBasedOn(tr)
?.filter((m) => m.icon !== undefined)
.map((m) => { if (!generated) {
const path: string = typeof m.icon === "string" ? m.icon : m.icon.path
const img = `<img class="m-1 h-6 w-6 low-interaction rounded" src='${path}'/>`
return { if: m.if, then: img }
})
if (mappings.length === 0) {
context context
.enters("titleIcons", i) .enters("titleIcons", i)
.warn( .warn(
@ -1169,10 +1199,7 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
) )
continue continue
} }
json.titleIcons[i] = <TagRenderingConfigJson>{ json.titleIcons[i] = generated
id: "title_icon_auto_" + trId,
mappings,
}
} }
return json return json
} }

View file

@ -21,6 +21,9 @@ import PresetConfig from "../PresetConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { Translatable } from "../Json/Translatable" import { Translatable } from "../Json/Translatable"
import { ConversionContext } from "./ConversionContext" import { ConversionContext } from "./ConversionContext"
import * as eli from "../../../assets/editor-layer-index.json"
import { AvailableRasterLayers } from "../../RasterLayers"
import Back from "../../../assets/svg/Back.svelte"
class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> { class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
private readonly _languages: string[] private readonly _languages: string[]
@ -124,6 +127,7 @@ export class DoesImageExist extends DesugaringStep<string> {
} }
export class ValidateTheme extends DesugaringStep<LayoutConfigJson> { export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
private static readonly _availableLayers = AvailableRasterLayers.allIds()
/** /**
* The paths where this layer is originally saved. Triggers some extra checks * The paths where this layer is originally saved. Triggers some extra checks
* @private * @private
@ -260,6 +264,19 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
.err("The overpassURL is a string, use a list of strings instead. Wrap it with [ ]") .err("The overpassURL is a string, use a list of strings instead. Wrap it with [ ]")
} }
if (json.defaultBackgroundId) {
const backgroundId = json.defaultBackgroundId
const isCategory =
backgroundId === "photo" || backgroundId === "map" || backgroundId === "osmbasedmap"
if (!isCategory && !ValidateTheme._availableLayers.has(backgroundId)) {
context
.enter("defaultBackgroundId")
.err("This layer ID is not known: " + backgroundId)
}
}
return json return json
} }
} }
@ -421,6 +438,7 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
// No need to check the writable tags, as this cannot write // No need to check the writable tags, as this cannot write
return json return json
} }
function addAll(keys: { forEach: (f: (s: string) => void) => void }, addTo: Set<string>) { function addAll(keys: { forEach: (f: (s: string) => void) => void }, addTo: Set<string>) {
keys?.forEach((k) => addTo.add(k)) keys?.forEach((k) => addTo.add(k))
} }
@ -1359,6 +1377,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> { export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
private readonly validator: ValidateLayer private readonly validator: ValidateLayer
constructor( constructor(
path: string, path: string,
isBuiltin: boolean, isBuiltin: boolean,
@ -1385,6 +1404,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
return prepared?.raw return prepared?.raw
} }
} }
export class ValidateLayer extends Conversion< export class ValidateLayer extends Conversion<
LayerConfigJson, LayerConfigJson,
{ parsed: LayerConfig; raw: LayerConfigJson } { parsed: LayerConfig; raw: LayerConfigJson }
@ -1462,6 +1482,19 @@ export class ValidateLayer extends Conversion<
} }
} }
for (let i = 0; i < json.presets?.length; i++) {
const preset = json.presets[i]
if (
preset.snapToLayer === undefined &&
preset.maxSnapDistance !== undefined &&
preset.maxSnapDistance !== null
) {
context
.enters("presets", i, "maxSnapDistance")
.err("A maxSnapDistance is given, but there is no layer given to snap to")
}
}
return { raw: json, parsed: layerConfig } return { raw: json, parsed: layerConfig }
} }
} }

View file

@ -145,7 +145,7 @@ export interface LayerConfigJson {
* There are a few extra functions available. Refer to <a>Docs/CalculatedTags.md</a> for more information * There are a few extra functions available. Refer to <a>Docs/CalculatedTags.md</a> for more information
* The functions will be run in order, e.g. * The functions will be run in order, e.g.
* [ * [
Not found... * "_max_overlap_m2=Math.max(...feat.overlapsWith("someOtherLayer").map(o => o.overlap)) * "_max_overlap_m2=Math.max(...feat.overlapsWith("someOtherLayer").map(o => o.overlap))
* "_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area * "_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area
* ] * ]
* *

View file

@ -184,14 +184,6 @@ export default class LayerConfig extends WithContextLoader {
snapToLayers, snapToLayers,
maxSnapDistance: pr.maxSnapDistance ?? 10, maxSnapDistance: pr.maxSnapDistance ?? 10,
} }
} else if (pr.maxSnapDistance !== undefined) {
throw (
"Layer " +
this.id +
" defines a maxSnapDistance, but does not include a `snapToLayer` (at " +
context +
")"
)
} }
const config: PresetConfig = { const config: PresetConfig = {

View file

@ -314,6 +314,7 @@ export default class LayoutConfig implements LayoutInformation {
return layer return layer
} }
} }
console.log("Fallthrough", this, tags)
return undefined return undefined
} }
} }

View file

@ -79,7 +79,7 @@ export default class TagRenderingConfig {
public readonly mappings?: Mapping[] public readonly mappings?: Mapping[]
public readonly editButtonAriaLabel?: Translation public readonly editButtonAriaLabel?: Translation
public readonly labels: string[] public readonly labels: string[]
public readonly classes: string[] public readonly classes: string[] | undefined
constructor( constructor(
config: string | TagRenderingConfigJson | QuestionableTagRenderingConfigJson, config: string | TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
@ -131,6 +131,9 @@ export default class TagRenderingConfig {
this.classes = json.classes ?? [] this.classes = json.classes ?? []
} }
this.classes = [].concat(...this.classes.map((cl) => cl.split(" "))) this.classes = [].concat(...this.classes.map((cl) => cl.split(" ")))
if (this.classes.length === 0) {
this.classes = undefined
}
this.render = Translations.T(<any>json.render, translationKey + ".render") this.render = Translations.T(<any>json.render, translationKey + ".render")
this.question = Translations.T(json.question, translationKey + ".question") this.question = Translations.T(json.question, translationKey + ".question")
@ -233,8 +236,10 @@ export default class TagRenderingConfig {
const commonIconSize = const commonIconSize =
Utils.NoNull( Utils.NoNull(
json.mappings.map((m) => (m.icon !== undefined ? m.icon["class"] : undefined)) json.mappings.map((m) => (!!m.icon ? m.icon["class"] : undefined))
)[0] ?? "small" )[0] ??
json["#iconsize"] ??
"small"
this.mappings = json.mappings.map((m, i) => this.mappings = json.mappings.map((m, i) =>
TagRenderingConfig.ExtractMapping( TagRenderingConfig.ExtractMapping(
m, m,
@ -364,7 +369,7 @@ export default class TagRenderingConfig {
let icon = undefined let icon = undefined
let iconClass = commonSize let iconClass = commonSize
if (mapping.icon !== undefined) { if (!!mapping.icon) {
if (typeof mapping.icon === "string" && mapping.icon !== "") { if (typeof mapping.icon === "string" && mapping.icon !== "") {
let stripped = mapping.icon let stripped = mapping.icon
if (stripped.endsWith(".svg")) { if (stripped.endsWith(".svg")) {
@ -378,7 +383,7 @@ export default class TagRenderingConfig {
} else { } else {
icon = mapping.icon icon = mapping.icon
} }
} else { } else if (mapping.icon["path"]) {
icon = mapping.icon["path"] icon = mapping.icon["path"]
iconClass = mapping.icon["class"] ?? iconClass iconClass = mapping.icon["class"] ?? iconClass
} }
@ -647,12 +652,6 @@ export default class TagRenderingConfig {
multiSelectedMapping: boolean[] | undefined, multiSelectedMapping: boolean[] | undefined,
currentProperties: Record<string, string> currentProperties: Record<string, string>
): UploadableTag { ): UploadableTag {
console.log("Constructing change spec", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
})
if (typeof freeformValue === "string") { if (typeof freeformValue === "string") {
freeformValue = freeformValue?.trim() freeformValue = freeformValue?.trim()
} }

View file

@ -452,7 +452,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
* Various small methods that need to be called * Various small methods that need to be called
*/ */
private miscSetup() { private miscSetup() {
this.userRelatedState.a11y.addCallbackAndRunD((a11y) => {
if (a11y === "always") {
this.visualFeedback.setData(true)
} else if (a11y === "never") {
this.visualFeedback.setData(false)
}
})
this.mapProperties.onKeyNavigationEvent((keyEvent) => { this.mapProperties.onKeyNavigationEvent((keyEvent) => {
if (this.userRelatedState.a11y.data === "never") {
return
}
if (["north", "east", "south", "west"].indexOf(keyEvent.key) >= 0) { if (["north", "east", "south", "west"].indexOf(keyEvent.key) >= 0) {
this.visualFeedback.setData(true) this.visualFeedback.setData(true)
return true // Our job is done, unregister return true // Our job is done, unregister
@ -482,7 +492,9 @@ export default class ThemeViewState implements SpecialVisualizationState {
* @private * @private
*/ */
private selectClosestAtCenter(i: number = 0) { private selectClosestAtCenter(i: number = 0) {
if (this.userRelatedState.a11y.data !== "never") {
this.visualFeedback.setData(true) this.visualFeedback.setData(true)
}
const toSelect = this.closestFeatures.features?.data?.[i] const toSelect = this.closestFeatures.features?.data?.[i]
if (!toSelect) { if (!toSelect) {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {

View file

@ -9,7 +9,7 @@
export let clss: string | undefined = undefined export let clss: string | undefined = undefined
</script> </script>
<button class={clss} on:click={() => osmConnection.AttemptLogin()}> <button class={clss} on:click={() => osmConnection.AttemptLogin()} style="margin-left: 0">
<ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")} /> <ToSvelte construct={Svg.login_svg().SetClass("w-12 m-1")} />
<slot> <slot>
<Tr t={Translations.t.general.loginWithOpenStreetMap} /> <Tr t={Translations.t.general.loginWithOpenStreetMap} />

View file

@ -8,6 +8,7 @@
onMount(() => { onMount(() => {
const uiElem = typeof construct === "function" ? construct() : construct const uiElem = typeof construct === "function" ? construct() : construct
html = uiElem?.ConstructElement() html = uiElem?.ConstructElement()
if (html !== undefined) { if (html !== undefined) {
elem?.replaceWith(html) elem?.replaceWith(html)
} }

View file

@ -10,6 +10,7 @@
import { createEventDispatcher, onDestroy } from "svelte" import { createEventDispatcher, onDestroy } from "svelte"
import { placeholder } from "../../Utils/placeholder" import { placeholder } from "../../Utils/placeholder"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid" import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { ariaLabel } from "../../Utils/ariaLabel"
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
export let bounds: UIEventSource<BBox> export let bounds: UIEventSource<BBox>
@ -116,6 +117,7 @@
}} }}
bind:value={searchContents} bind:value={searchContents}
use:placeholder={Translations.t.general.search.search} use:placeholder={Translations.t.general.search.search}
use:ariaLabel={Translations.t.general.search.search}
/> />
{#if feedback !== undefined} {#if feedback !== undefined}
<!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing--> <!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing-->

View file

@ -30,6 +30,7 @@
import Direction_gradient from "../../assets/svg/Direction_gradient.svelte" import Direction_gradient from "../../assets/svg/Direction_gradient.svelte"
import Mastodon from "../../assets/svg/Mastodon.svelte" import Mastodon from "../../assets/svg/Mastodon.svelte"
import Party from "../../assets/svg/Party.svelte" import Party from "../../assets/svg/Party.svelte"
import AddSmall from "../../assets/svg/AddSmall.svelte"
/** /**
* Renders a single icon. * Renders a single icon.
@ -111,6 +112,8 @@
<Mastodon {color} class={clss} /> <Mastodon {color} class={clss} />
{:else if icon === "party"} {:else if icon === "party"}
<Party {color} class={clss} /> <Party {color} class={clss} />
{:else if icon === "addSmall"}
<AddSmall {color} class={clss} />
{:else} {:else}
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" /> <img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" />
{/if} {/if}

View file

@ -511,15 +511,25 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
await Utils.waitFor(250) await Utils.waitFor(250)
} }
} }
public installCustomKeyboardHandler(viewport: Store<HTMLDivElement>) { public installCustomKeyboardHandler(viewportStore: UIEventSource<HTMLDivElement>) {
viewport.mapD( viewportStore.mapD(
(viewport) => { (viewport) => {
const map = this._maplibreMap.data const map = this._maplibreMap.data
if (!map) { if (!map) {
return return
} }
const oldKeyboard = map.keyboard const oldKeyboard = map.keyboard
oldKeyboard._panStep = viewport.getBoundingClientRect().width const w = viewport.getBoundingClientRect().width
if (w < 10) {
/// this is weird, but definitively wrong!
console.log("Got a very small bound", w, viewport)
// We try again later on
window.requestAnimationFrame(() => {
viewportStore.ping()
})
return
}
oldKeyboard._panStep = w
}, },
[this._maplibreMap] [this._maplibreMap]
) )

View file

@ -159,7 +159,7 @@ class ApplyButton extends UIElement {
private async Run() { private async Run() {
try { try {
console.log("Applying auto-action on " + this.target_feature_ids.length + " features") console.log("Applying auto-action on " + this.target_feature_ids.length + " features")
const appliedOn: string[] = []
for (let i = 0; i < this.target_feature_ids.length; i++) { for (let i = 0; i < this.target_feature_ids.length; i++) {
const targetFeatureId = this.target_feature_ids[i] const targetFeatureId = this.target_feature_ids[i]
const feature = this.state.indexedFeatures.featuresById.data.get(targetFeatureId) const feature = this.state.indexedFeatures.featuresById.data.get(targetFeatureId)
@ -190,6 +190,7 @@ class ApplyButton extends UIElement {
specialRendering.args specialRendering.args
) )
} }
appliedOn.push(targetFeatureId)
if (i % 50 === 0) { if (i % 50 === 0) {
await this.state.changes.flushChanges("Auto button: intermediate save") await this.state.changes.flushChanges("Auto button: intermediate save")
} }
@ -198,6 +199,12 @@ class ApplyButton extends UIElement {
console.log("Flushing changes...") console.log("Flushing changes...")
await this.state.changes.flushChanges("Auto button: done") await this.state.changes.flushChanges("Auto button: done")
this.buttonState.setData("done") this.buttonState.setData("done")
console.log(
"Applied changes onto",
appliedOn.length,
"items, unique IDs:",
new Set(appliedOn).size
)
} catch (e) { } catch (e) {
console.error("Error while running autoApply: ", e) console.error("Error while running autoApply: ", e)
this.buttonState.setData({ error: e }) this.buttonState.setData({ error: e })

View file

@ -127,13 +127,6 @@
<button slot="cancel" class="items-center" on:click={() => (currentState = "start")}> <button slot="cancel" class="items-center" on:click={() => (currentState = "start")}>
<Tr t={t.cancel} /> <Tr t={t.cancel} />
</button> </button>
<XCircleIcon
slot="upper-right"
class="h-8 w-8 cursor-pointer"
on:click={() => {
currentState = "start"
}}
/>
<div slot="under-buttons"> <div slot="under-buttons">
{#if selectedTags !== undefined} {#if selectedTags !== undefined}

View file

@ -20,6 +20,7 @@ export class ShareLinkViz implements SpecialVisualization {
}, },
] ]
needsUrls = [] needsUrls = []
svelteBased = true
public constr( public constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
@ -52,6 +53,8 @@ export class ShareLinkViz implements SpecialVisualization {
} }
} }
return new SvelteUIElement(ShareButton, { generateShareData, text }) return new SvelteUIElement(ShareButton, { generateShareData, text }).SetClass(
"w-full h-full"
)
} }
} }

View file

@ -41,7 +41,7 @@
} }
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>()) let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>())
let questionboxElem: HTMLBaseElement let questionboxElem: HTMLDivElement
let questionsToAsk = tags.map( let questionsToAsk = tags.map(
(tags) => { (tags) => {
const baseQuestions = (layer.tagRenderings ?? [])?.filter( const baseQuestions = (layer.tagRenderings ?? [])?.filter(
@ -49,7 +49,7 @@
) )
const questionsToAsk: TagRenderingConfig[] = [] const questionsToAsk: TagRenderingConfig[] = []
for (const baseQuestion of baseQuestions) { for (const baseQuestion of baseQuestions) {
if (skippedQuestions.data.has(baseQuestion.id) > 0) { if (skippedQuestions.data.has(baseQuestion.id)) {
continue continue
} }
if ( if (
@ -88,6 +88,7 @@
<div <div
bind:this={questionboxElem} bind:this={questionboxElem}
aria-live="polite"
class="marker-questionbox-root" class="marker-questionbox-root"
class:hidden={$questionsToAsk.length === 0 && skipped === 0 && answered === 0} class:hidden={$questionsToAsk.length === 0 && skipped === 0 && answered === 0}
> >

View file

@ -29,7 +29,7 @@
</script> </script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))} {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))}
<div {id} class={twMerge("link-underline inline-block w-full", config?.classes, extraClasses)}> <div {id} class={twMerge("link-underline inline-block w-full", config?.classes , extraClasses)}>
{#if $trs.length === 1} {#if $trs.length === 1}
<TagRenderingMapping mapping={$trs[0]} {tags} {state} {selectedElement} {layer} /> <TagRenderingMapping mapping={$trs[0]} {tags} {state} {selectedElement} {layer} />
{/if} {/if}

View file

@ -23,7 +23,7 @@
export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge
export let highlightedRendering: UIEventSource<string> = undefined export let highlightedRendering: UIEventSource<string> = undefined
export let clss export let clss = undefined
/** /**
* Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property * Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property
*/ */
@ -98,16 +98,6 @@
> >
<Tr t={Translations.t.general.cancel} /> <Tr t={Translations.t.general.cancel} />
</button> </button>
<button
slot="upper-right"
class="h-8 w-8 cursor-pointer border-none p-0"
use:ariaLabel={Translations.t.general.cancel}
on:click={() => {
editMode = false
}}
>
<XCircleIcon />
</button>
</TagRenderingQuestion> </TagRenderingQuestion>
{:else} {:else}
<div class="low-interaction flex items-center justify-between overflow-hidden rounded px-2"> <div class="low-interaction flex items-center justify-between overflow-hidden rounded px-2">

View file

@ -12,7 +12,6 @@
export let tags: UIEventSource<Record<string, string>> export let tags: UIEventSource<Record<string, string>>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let layer: LayerConfig export let layer: LayerConfig
export let mapping: { export let mapping: {
readonly then: Translation readonly then: Translation
readonly searchTerms?: Record<string, string[]> readonly searchTerms?: Record<string, string[]>
@ -30,7 +29,7 @@
{#if mapping.icon !== undefined} {#if mapping.icon !== undefined}
<div class="inline-flex items-center"> <div class="inline-flex items-center">
<Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass}`, "mx-2")} /> <Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-2")} />
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} /> <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} />
</div> </div>
{:else if mapping.then !== undefined} {:else if mapping.then !== undefined}

View file

@ -83,7 +83,7 @@
</script> </script>
{#if $matchesTerm && !$mappingIsHidden} {#if $matchesTerm && !$mappingIsHidden}
<label class={twJoin("flex", mappingIsSelected && "checked")}> <label class={twJoin("flex gap-x-1", mappingIsSelected && "checked")}>
<slot /> <slot />
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer} /> <TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer} />
</label> </label>

View file

@ -151,7 +151,7 @@
$freeformInput, $freeformInput,
selectedMapping, selectedMapping,
checkedMappings, checkedMappings,
tags.data tags.data,
) )
} catch (e) { } catch (e) {
console.error("Could not calculate changeSpecification:", e) console.error("Could not calculate changeSpecification:", e)
@ -213,23 +213,24 @@
onDestroy( onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => { state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount numberOfCs = ud.csCount
}) }),
) )
} }
</script> </script>
{#if question !== undefined} {#if question !== undefined}
<div class="relative">
<form <form
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2" class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh" style="max-height: 75vh"
on:submit|preventDefault={() => onSave()} on:submit|preventDefault={() => onSave()}
> >
<label class="neutral-label"> <fieldset>
<div class="interactive sticky top-0 flex justify-between pt-1" style="z-index: 11">
<span class="font-bold"> <legend>
<div class="interactive sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} /> <SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
</span>
<slot name="upper-right" />
</div> </div>
{#if config.questionhint} {#if config.questionhint}
@ -243,6 +244,7 @@
/> />
</div> </div>
{/if} {/if}
</legend>
{#if config.mappings?.length >= 8} {#if config.mappings?.length >= 8}
<div class="sticky flex w-full" aria-hidden="true"> <div class="sticky flex w-full" aria-hidden="true">
@ -292,7 +294,7 @@
</TagRenderingMappingInput> </TagRenderingMappingInput>
{/each} {/each}
{#if config.freeform?.key} {#if config.freeform?.key}
<label class="flex"> <label class="flex gap-x-1">
<input <input
type="radio" type="radio"
bind:group={selectedMapping} bind:group={selectedMapping}
@ -336,7 +338,7 @@
</TagRenderingMappingInput> </TagRenderingMappingInput>
{/each} {/each}
{#if config.freeform?.key} {#if config.freeform?.key}
<label class="flex"> <label class="flex gap-x-1">
<input <input
type="checkbox" type="checkbox"
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length} name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
@ -357,8 +359,6 @@
{/if} {/if}
</div> </div>
{/if} {/if}
</label>
<LoginToggle {state}> <LoginToggle {state}>
<Loading slot="loading" /> <Loading slot="loading" />
<SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}> <SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}>
@ -400,5 +400,9 @@
{/if} {/if}
<slot name="under-buttons" /> <slot name="under-buttons" />
</LoginToggle> </LoginToggle>
</fieldset>
</form> </form>
</div>
{/if} {/if}

View file

@ -98,7 +98,7 @@ export interface SpecialVisualization {
readonly funcName: string readonly funcName: string
readonly docs: string | BaseUIElement readonly docs: string | BaseUIElement
readonly example?: string readonly example?: string
readonly needsUrls: string[] | ((args: string[]) => string) readonly needsUrls?: string[] | ((args: string[]) => string)
/** /**
* 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

View file

@ -102,6 +102,7 @@ class NearbyImageVis implements SpecialVisualization {
"A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature"
funcName = "nearby_images" funcName = "nearby_images"
needsUrls = NearbyImagesSearch.apiUrls needsUrls = NearbyImagesSearch.apiUrls
svelteBased = true
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
@ -141,6 +142,7 @@ class StealViz implements SpecialVisualization {
}, },
] ]
needsUrls = [] needsUrls = []
svelteBased = true
constr(state: SpecialVisualizationState, featureTags, args) { constr(state: SpecialVisualizationState, featureTags, args) {
const [featureIdKey, layerAndtagRenderingIds] = args const [featureIdKey, layerAndtagRenderingIds] = args
@ -213,6 +215,7 @@ export class QuestionViz implements SpecialVisualization {
doc: "One or more ';'-separated labels of questions which should _not_ be included", doc: "One or more ';'-separated labels of questions which should _not_ be included",
}, },
] ]
svelteBased = true
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
@ -236,7 +239,7 @@ export class QuestionViz implements SpecialVisualization {
state, state,
onlyForLabels: labels, onlyForLabels: labels,
notForLabels: blacklist, notForLabels: blacklist,
}) }).SetClass("w-full")
} }
} }
@ -437,7 +440,7 @@ export default class SpecialVisualizations {
funcName: "add_new_point", funcName: "add_new_point",
docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`", docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`",
args: [], args: [],
needsUrls: [],
constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement { constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement {
let [lon, lat] = GeoOperations.centerpointCoordinates(feature) let [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(AddNewPoint, { return new SvelteUIElement(AddNewPoint, {
@ -449,7 +452,7 @@ export default class SpecialVisualizations {
{ {
funcName: "user_profile", funcName: "user_profile",
args: [], args: [],
needsUrls: [],
docs: "A component showing information about the currently logged in user (username, profile description, profile picture + link to edit them). Mostly meant to be used in the 'user-settings'", docs: "A component showing information about the currently logged in user (username, profile description, profile picture + link to edit them). Mostly meant to be used in the 'user-settings'",
constr(state: SpecialVisualizationState): BaseUIElement { constr(state: SpecialVisualizationState): BaseUIElement {
return new SvelteUIElement(UserProfile, { return new SvelteUIElement(UserProfile, {
@ -460,7 +463,6 @@ export default class SpecialVisualizations {
{ {
funcName: "language_picker", funcName: "language_picker",
args: [], args: [],
needsUrls: [],
docs: "A component to set the language of the user interface", docs: "A component to set the language of the user interface",
constr(state: SpecialVisualizationState): BaseUIElement { constr(state: SpecialVisualizationState): BaseUIElement {
return new SvelteUIElement(LanguagePicker, { return new SvelteUIElement(LanguagePicker, {
@ -477,6 +479,7 @@ export default class SpecialVisualizations {
args: [], args: [],
needsUrls: [Constants.osmAuthConfig.url], needsUrls: [Constants.osmAuthConfig.url],
docs: "Shows a button where the user can log out", docs: "Shows a button where the user can log out",
constr(state: SpecialVisualizationState): BaseUIElement { constr(state: SpecialVisualizationState): BaseUIElement {
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
}, },
@ -488,7 +491,7 @@ export default class SpecialVisualizations {
funcName: "split_button", funcName: "split_button",
docs: "Adds a button which allows to split a way", docs: "Adds a button which allows to split a way",
args: [], args: [],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>> tagSource: UIEventSource<Record<string, string>>
@ -509,7 +512,7 @@ export default class SpecialVisualizations {
funcName: "move_button", funcName: "move_button",
docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config",
args: [], args: [],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -532,7 +535,7 @@ export default class SpecialVisualizations {
funcName: "delete_button", funcName: "delete_button",
docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config",
args: [], args: [],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -597,6 +600,7 @@ export default class SpecialVisualizations {
}, },
], ],
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls], needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
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: (_, tagsSource, args) => { constr: (_, tagsSource, args) => {
@ -650,7 +654,6 @@ export default class SpecialVisualizations {
funcName: "all_tags", funcName: "all_tags",
docs: "Prints all key-value pairs of the object - used for debugging", docs: "Prints all key-value pairs of the object - used for debugging",
args: [], args: [],
needsUrls: [],
constr: (state, tags: UIEventSource<any>) => constr: (state, tags: UIEventSource<any>) =>
new SvelteUIElement(AllTagsPanel, { tags, state }), new SvelteUIElement(AllTagsPanel, { tags, state }),
}, },
@ -820,7 +823,7 @@ export default class SpecialVisualizations {
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
}, },
], ],
needsUrls: [],
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: (state, tagSource: UIEventSource<any>, args) => { constr: (state, tagSource: UIEventSource<any>, args) => {
@ -848,14 +851,13 @@ export default class SpecialVisualizations {
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
}, },
], ],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig
): BaseUIElement { ): 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]
@ -870,7 +872,7 @@ export default class SpecialVisualizations {
}, },
{ {
funcName: "canonical", funcName: "canonical",
needsUrls: [],
docs: "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. ", docs: "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. ",
example: example:
"If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...", "If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...",
@ -910,7 +912,7 @@ export default class SpecialVisualizations {
funcName: "export_as_geojson", funcName: "export_as_geojson",
docs: "Exports the selected feature as GeoJson-file", docs: "Exports the selected feature as GeoJson-file",
args: [], args: [],
needsUrls: [],
constr: (state, tagSource, tagsSource, feature, layer) => { constr: (state, tagSource, tagsSource, feature, layer) => {
const t = Translations.t.general.download const t = Translations.t.general.download
@ -942,7 +944,7 @@ export default class SpecialVisualizations {
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: [],
needsUrls: [],
constr: (state, feature) => { constr: (state, feature) => {
return new SvelteUIElement(OpenIdEditor, { return new SvelteUIElement(OpenIdEditor, {
mapProperties: state.mapProperties, mapProperties: state.mapProperties,
@ -964,7 +966,7 @@ export default class SpecialVisualizations {
funcName: "clear_location_history", funcName: "clear_location_history",
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: [],
needsUrls: [],
constr: (state) => { constr: (state) => {
return new SubtleButton( return new SubtleButton(
Svg.delete_icon_svg().SetStyle("height: 1.5rem"), Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
@ -1023,6 +1025,7 @@ export default class SpecialVisualizations {
}, },
], ],
needsUrls: [Imgur.apiUrl], needsUrls: [Imgur.apiUrl],
constr: (state, tags, args) => { constr: (state, tags, args) => {
const id = tags.data[args[0] ?? "id"] const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id) tags = state.featureProperties.getStore(id)
@ -1033,7 +1036,7 @@ export default class SpecialVisualizations {
{ {
funcName: "title", funcName: "title",
args: [], args: [],
needsUrls: [],
docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'",
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`.",
@ -1145,6 +1148,7 @@ export default class SpecialVisualizations {
defaultValue: "mr_taskId", defaultValue: "mr_taskId",
}, },
], ],
constr: (state, tagsSource, args) => { constr: (state, tagsSource, args) => {
let [message, image, message_closed, statusToSet, maproulette_id_key] = args let [message, image, message_closed, statusToSet, maproulette_id_key] = args
if (image === "") { if (image === "") {
@ -1168,7 +1172,7 @@ export default class SpecialVisualizations {
funcName: "statistics", funcName: "statistics",
docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer",
args: [], args: [],
needsUrls: [],
constr: (state) => { constr: (state) => {
return new Combine( return new Combine(
state.layout.layers state.layout.layers
@ -1211,7 +1215,6 @@ export default class SpecialVisualizations {
required: true, required: true,
}, },
], ],
needsUrls: [],
constr(__, tags, args) { constr(__, tags, args) {
return new SvelteUIElement(SendEmail, { args, tags }) return new SvelteUIElement(SendEmail, { args, tags })
@ -1244,7 +1247,7 @@ export default class SpecialVisualizations {
doc: "If set, this text will be used as aria-label", doc: "If set, this text will be used as aria-label",
}, },
], ],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -1273,7 +1276,7 @@ export default class SpecialVisualizations {
{ {
funcName: "multi", funcName: "multi",
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering", docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
needsUrls: [],
example: example:
"```json\n" + "```json\n" +
JSON.stringify( JSON.stringify(
@ -1327,7 +1330,7 @@ export default class SpecialVisualizations {
{ {
funcName: "translated", funcName: "translated",
docs: "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes", docs: "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes",
needsUrls: [],
args: [ args: [
{ {
name: "key", name: "key",
@ -1366,7 +1369,7 @@ export default class SpecialVisualizations {
required: true, required: true,
}, },
], ],
needsUrls: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -1396,7 +1399,7 @@ export default class SpecialVisualizations {
{ {
funcName: "braced", funcName: "braced",
docs: "Show a literal text within braces", docs: "Show a literal text within braces",
needsUrls: [],
args: [ args: [
{ {
name: "text", name: "text",
@ -1417,7 +1420,7 @@ export default class SpecialVisualizations {
{ {
funcName: "tags", funcName: "tags",
docs: "Shows a (json of) tags in a human-readable way + links to the wiki", docs: "Shows a (json of) tags in a human-readable way + links to the wiki",
needsUrls: [],
args: [ args: [
{ {
name: "key", name: "key",
@ -1468,6 +1471,7 @@ export default class SpecialVisualizations {
], ],
docs: "Shows events that are happening based on a Giggity URL", docs: "Shows events that are happening based on a Giggity URL",
needsUrls: (args) => args[0], needsUrls: (args) => args[0],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -1481,7 +1485,7 @@ export default class SpecialVisualizations {
}, },
{ {
funcName: "gps_all_tags", funcName: "gps_all_tags",
needsUrls: [],
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( constr(
@ -1507,9 +1511,10 @@ export default class SpecialVisualizations {
}, },
{ {
funcName: "favourite_status", funcName: "favourite_status",
needsUrls: [],
docs: "A button that allows a (logged in) contributor to mark a location as a favourite location", docs: "A button that allows a (logged in) contributor to mark a location as a favourite location",
args: [], args: [],
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
@ -1527,7 +1532,7 @@ export default class SpecialVisualizations {
}, },
{ {
funcName: "favourite_icon", funcName: "favourite_icon",
needsUrls: [],
docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon", docs: "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( constr(
@ -1542,13 +1547,13 @@ export default class SpecialVisualizations {
state, state,
layer, layer,
feature, feature,
}) }).SetClass("w-full h-full")
}, },
}, },
{ {
funcName: "direction_indicator", funcName: "direction_indicator",
args: [], args: [],
needsUrls: [],
docs: "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object", docs: "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object",
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
@ -1563,7 +1568,7 @@ export default class SpecialVisualizations {
{ {
funcName: "qr_code", funcName: "qr_code",
args: [], args: [],
needsUrls: [],
docs: "Generates a QR-code to share the selected object", docs: "Generates a QR-code to share the selected object",
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
@ -1598,6 +1603,41 @@ export default class SpecialVisualizations {
) )
}, },
}, },
{
funcName: "direction_absolute",
docs: "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'",
args: [
{
name: "key",
doc: "The attribute containing the degrees",
defaultValue: "_direction:centerpoint",
},
],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
return new VariableUiElement(
tagSource
.map((tags) => {
console.log("Direction value", tags[key], key)
return tags[key]
})
.mapD((value) => {
const dir = GeoOperations.bearingToHuman(
GeoOperations.parseBearing(value)
)
console.log("Human dir", dir)
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
})
)
},
},
] ]
specialVisualizations.push(new AutoApplyButton(specialVisualizations)) specialVisualizations.push(new AutoApplyButton(specialVisualizations))

View file

@ -24,7 +24,7 @@
export let backToStudio: () => void export let backToStudio: () => void
let messages = state.messages let messages = state.messages
let hasErrors = messages.mapD( let hasErrors = messages.mapD(
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length (m: ConversionMessage[]) => m.filter((m) => m.level === "error").length,
) )
const configuration = state.configuration const configuration = state.configuration
@ -73,6 +73,7 @@
}) })
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
function deleteLayer() { function deleteLayer() {
state.delete() state.delete()
backToStudio() backToStudio()
@ -93,6 +94,22 @@
{:else if $hasErrors > 0} {:else if $hasErrors > 0}
<div class="alert">{$hasErrors} errors detected</div> <div class="alert">{$hasErrors} errors detected</div>
{:else} {:else}
<div class="flex">
<a
class="button small"
href={baseUrl + state.server.layerUrl(title.data) + "&test=true"}
target="_blank"
rel="noopener"
>
<div class="flex flex-col">
<b>
Test in safe mode
</b>
<div>No changes are recoded to OSM</div>
</div>
<ChevronRightIcon class="h-6 w-6 shrink-0" />
</a>
<a <a
class="primary button" class="primary button"
href={baseUrl + state.server.layerUrl(title.data)} href={baseUrl + state.server.layerUrl(title.data)}
@ -102,6 +119,7 @@
Try it out Try it out
<ChevronRightIcon class="h-6 w-6 shrink-0" /> <ChevronRightIcon class="h-6 w-6 shrink-0" />
</a> </a>
</div>
{/if} {/if}
</div> </div>
@ -120,7 +138,8 @@
<Region {state} configs={perRegion["Basic"]} /> <Region {state} configs={perRegion["Basic"]} />
<div class="mt-12"> <div class="mt-12">
<button on:click={() => deleteLayer()} class="small"> <button on:click={() => deleteLayer()} class="small">
<TrashIcon class="h-6 w-6" /> Delete this layer <TrashIcon class="h-6 w-6" />
Delete this layer
</button> </button>
</div> </div>
</div> </div>

View file

@ -8,14 +8,13 @@
import EditLayerState from "./EditLayerState" import EditLayerState from "./EditLayerState"
import { onDestroy } from "svelte" import { onDestroy } from "svelte"
import type { JsonSchemaType } from "./jsonSchema" import type { JsonSchemaType } from "./jsonSchema"
import { ConfigMetaUtils } from "./configMeta.ts" import { ConfigMetaUtils } from "./configMeta"
import ShowConversionMessage from "./ShowConversionMessage.svelte" import ShowConversionMessage from "./ShowConversionMessage.svelte"
export let state: EditLayerState export let state: EditLayerState
export let path: (string | number)[] = [] export let path: (string | number)[] = []
export let schema: ConfigMeta export let schema: ConfigMeta
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset
let value = new UIEventSource<string | any>(undefined)
const isTranslation = const isTranslation =
schema.hints?.typehint === "translation" || schema.hints?.typehint === "translation" ||
@ -144,10 +143,10 @@
path, path,
tags.map((tgs) => { tags.map((tgs) => {
const v = tgs["value"] const v = tgs["value"]
if (typeof v !== "string") { if (typeof v === "object") {
return { ...v } return { ...<object>v }
} }
if (schema.type === "boolan") { if (schema.type === "boolean") {
return v === "true" || v === "yes" || v === "1" return v === "true" || v === "yes" || v === "1"
} }
if (mightBeBoolean(schema.type)) { if (mightBeBoolean(schema.type)) {
@ -159,7 +158,7 @@
} }
} }
if (schema.type === "number") { if (schema.type === "number") {
if (v === "") { if (v === "" || v === null || isNaN(Number(v))) {
return undefined return undefined
} }
return Number(v) return Number(v)

View file

@ -95,7 +95,7 @@
const version = meta.version const version = meta.version
async function editLayer(event: Event) { async function editLayer(event: Event) {
const layerId: { owner: number; id: string } = event.detail const layerId: { owner: number; id: string } = event["detail"]
state = "loading" state = "loading"
editLayerState.startSavingUpdates(false) editLayerState.startSavingUpdates(false)
editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner)) editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner))
@ -104,7 +104,7 @@
} }
async function editTheme(event: Event) { async function editTheme(event: Event) {
const id: { id: string; owner: number } = event.detail const id: { id: string; owner: number } = event["detail"]
state = "loading" state = "loading"
editThemeState.startSavingUpdates(false) editThemeState.startSavingUpdates(false)
editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner)) editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner))

View file

@ -13,13 +13,7 @@
import type { MapProperties } from "../Models/MapProperties" import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte" import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations" import Translations from "./i18n/Translations"
import { import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
CogIcon,
EyeIcon,
HeartIcon,
MenuIcon,
XCircleIcon,
} from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte" import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte" import FloatOver from "./Base/FloatOver.svelte"
@ -72,14 +66,12 @@
import FilterPanel from "./BigComponents/FilterPanel.svelte" import FilterPanel from "./BigComponents/FilterPanel.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte" import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte"
import { BBox } from "../Logic/BBox" import { BBox } from "../Logic/BBox"
import { MapLibreAdaptor } from "./Map/MapLibreAdaptor.js"
import { QueryParameters } from "../Logic/Web/QueryParameters"
export let state: ThemeViewState export let state: ThemeViewState
let layout = state.layout let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined) let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined)
selectedElement.addCallbackAndRun(se => console.log("Selected element", se))
let compass = Orientation.singleton.alpha let compass = Orientation.singleton.alpha
let compassLoaded = Orientation.singleton.gotMeasurement let compassLoaded = Orientation.singleton.gotMeasurement
Orientation.singleton.startMeasurements() Orientation.singleton.startMeasurements()
@ -100,8 +92,12 @@
}) })
}) })
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => {
state.layout.getMatchingLayer(element.properties) if (element.properties.id.startsWith("current_view")) {
return currentViewLayer
}
return state.layout.getMatchingLayer(element.properties)
},
) )
let currentZoom = state.mapProperties.zoom let currentZoom = state.mapProperties.zoom
let showCrosshair = state.userRelatedState.showCrosshair let showCrosshair = state.userRelatedState.showCrosshair
@ -109,6 +105,7 @@
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined) let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
let mapproperties: MapProperties = state.mapProperties let mapproperties: MapProperties = state.mapProperties
state.mapProperties.installCustomKeyboardHandler(viewport) state.mapProperties.installCustomKeyboardHandler(viewport)
function updateViewport() { function updateViewport() {
const rect = viewport.data?.getBoundingClientRect() const rect = viewport.data?.getBoundingClientRect()
if (!rect) { if (!rect) {
@ -142,7 +139,7 @@
onDestroy( onDestroy(
rasterLayer.addCallbackAndRunD((l) => { rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name rasterLayerName = l.properties.name
}) }),
) )
let previewedImage = state.previewedImage let previewedImage = state.previewedImage
@ -189,9 +186,11 @@
<div class="pointer-events-auto float-right mt-1 flex flex-col px-1 max-[480px]:w-full sm:m-2"> <div class="pointer-events-auto float-right mt-1 flex flex-col px-1 max-[480px]:w-full sm:m-2">
<If condition={state.visualFeedback}> <If condition={state.visualFeedback}>
{#if $selectedElement === undefined}
<div class="w-fit"> <div class="w-fit">
<VisualFeedbackPanel {state} /> <VisualFeedbackPanel {state} />
</div> </div>
{/if}
</If> </If>
<If condition={state.featureSwitches.featureSwitchSearch}> <If condition={state.featureSwitches.featureSwitchSearch}>
<Geosearch <Geosearch
@ -226,7 +225,7 @@
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
<MapControlButton <MapControlButton
on:click={() => { on:click={() => {
selectedElement.setData(state.currentView.features?.data?.[0]) state.selectedElement.setData(state.currentView.features?.data?.[0])
}} }}
on:keydown={forwardEventToMap} on:keydown={forwardEventToMap}
> >

View file

@ -1448,7 +1448,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
d.setUTCMinutes(0) d.setUTCMinutes(0)
} }
public static scrollIntoView(element: HTMLBaseElement) { public static scrollIntoView(element: HTMLBaseElement | HTMLDivElement) {
// Is the element completely in the view? // Is the element completely in the view?
const parentRect = Utils.findParentWithScrolling(element).getBoundingClientRect() const parentRect = Utils.findParentWithScrolling(element).getBoundingClientRect()
const elementRect = element.getBoundingClientRect() const elementRect = element.getBoundingClientRect()
@ -1680,7 +1680,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
}) })
} }
private static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement { private static findParentWithScrolling(
element: HTMLBaseElement | HTMLDivElement
): HTMLBaseElement | HTMLDivElement {
// Check if the element itself has scrolling // Check if the element itself has scrolling
if (element.scrollHeight > element.clientHeight) { if (element.scrollHeight > element.clientHeight) {
return element return element

File diff suppressed because one or more lines are too long

View file

@ -121,16 +121,6 @@ input[type=text] {
width: 100%; width: 100%;
} }
.debug input, .debug textarea {
border: 6px solid red
}
.debug label input, .debug label textarea {
border: 1px solid grey;
}
/************************* BIG CATEGORIES ********************************/ /************************* BIG CATEGORIES ********************************/
/** /**

View file

@ -14,7 +14,7 @@
<body> <body>
<div id="main">Loading statistics...</div> <div id="main">Loading statistics...</div>
<script src="./src/UI/StatisticsGUI.ts" type="module"></script> <script src="./src/UI/StatisticsGUI.ts" type="module"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
</body> </body>
</html> </html>

View file

@ -14,7 +14,7 @@
<body> <body>
<div id="main" class="h-full">Initing studio...</div> <div id="main" class="h-full">Initing studio...</div>
<script src="./src/UI/StudioGui.ts" type="module"></script> <script src="./src/UI/StudioGui.ts" type="module"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
</body> </body>
</html> </html>

View file

@ -1,15 +1,17 @@
import { exec } from "child_process" import { exec } from "child_process"
import { describe, it } from "vitest" import { describe, expect, it, test } from "vitest"
import { webcrypto } from "node:crypto"
import { parse as parse_html } from "node-html-parser" import { parse as parse_html } from "node-html-parser"
import { readFileSync } from "fs" import { readFileSync } from "fs"
import ScriptUtils from "../scripts/ScriptUtils" import ScriptUtils from "../scripts/ScriptUtils"
function detectInCode(forbidden: string, reason: string) { function detectInCode(forbidden: string, reason: string) {
return wrap(detectInCodeUnwrapped(forbidden, reason)) return wrap(detectInCodeUnwrapped(forbidden, reason))
} }
/** /**
* *
* @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot * @param forbidden a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot
* @param reason * @param reason
* @private * @private
*/ */
@ -64,13 +66,23 @@ function wrap(promise: Promise<void>): (done: () => void) => void {
} }
} }
function validateScriptIntegrityOf(path: string) { function _arrayBufferToBase64(buffer) {
var binary = ""
var bytes = new Uint8Array(buffer)
var len = bytes.byteLength
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
async function validateScriptIntegrityOf(path: string): Promise<void> {
const htmlContents = readFileSync(path, "utf8") const htmlContents = readFileSync(path, "utf8")
const doc = parse_html(htmlContents) const doc = parse_html(htmlContents)
// @ts-ignore // @ts-ignore
const scripts = Array.from(doc.getElementsByTagName("script")) const scripts = Array.from(doc.getElementsByTagName("script"))
for (const script of scripts) { for (const script of scripts) {
const src = script.getAttribute("src") let src = script.getAttribute("src")
if (src === undefined) { if (src === undefined) {
continue continue
} }
@ -87,6 +99,18 @@ function validateScriptIntegrityOf(path: string) {
if (crossorigin !== "anonymous") { if (crossorigin !== "anonymous") {
throw new Error(ctx + " has crossorigin missing or not set to 'anonymous'") throw new Error(ctx + " has crossorigin missing or not set to 'anonymous'")
} }
if (src.startsWith("//")) {
src = "https:" + src
}
// Using 'scriptUtils' actually fetches data from the internet, it is not prohibited by the testHooks
const data: string = (await ScriptUtils.Download(src))["content"]
const hashed = await webcrypto.subtle.digest("SHA-384", new TextEncoder().encode(data))
const hashedStr = _arrayBufferToBase64(hashed)
console.log(src, hashedStr, integrity)
expect(integrity).to.equal(
"sha384-" + hashedStr,
"Loading a script from '" + src + "' in the file " + path + " has a mismatched checksum"
)
} }
} }
@ -112,10 +136,10 @@ describe("Code quality", () => {
) )
) )
it("scripts with external sources should have an integrity hash", () => { test("scripts with external sources should have an integrity hash", async () => {
const htmlFiles = ScriptUtils.readDirRecSync(".", 1).filter((f) => f.endsWith(".html")) const htmlFiles = ScriptUtils.readDirRecSync(".", 1).filter((f) => f.endsWith(".html"))
for (const htmlFile of htmlFiles) { for (const htmlFile of htmlFiles) {
validateScriptIntegrityOf(htmlFile) await validateScriptIntegrityOf(htmlFile)
} }
}) })
/* /*

View file

@ -76,7 +76,7 @@
<script src="./src/UI/RemoveOtherLanguages.js"></script> <script src="./src/UI/RemoveOtherLanguages.js"></script>
<script async src="./src/InstallServiceWorker.ts" type="module"></script> <script async src="./src/InstallServiceWorker.ts" type="module"></script>
<script defer src="./src/index.ts" type="module"></script> <script defer src="./src/index.ts" type="module"></script>
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>

View file

@ -5,6 +5,8 @@ export default defineConfig({
plugins: [svelte({ hot: !process.env.VITEST, preprocess: [autoPreprocess()] })], plugins: [svelte({ hot: !process.env.VITEST, preprocess: [autoPreprocess()] })],
test: { test: {
globals: true, globals: true,
maxThreads: 16,
minThreads: 1,
setupFiles: ["./test/testhooks.ts"], setupFiles: ["./test/testhooks.ts"],
include: ["./test/*.spec.ts", "./test/**/*.spec.ts", "./*.doctest.ts", "./**/*.doctest.ts"], include: ["./test/*.spec.ts", "./test/**/*.spec.ts", "./*.doctest.ts", "./**/*.doctest.ts"],
}, },