Merge branch 'develop' into theme/infrastructure

This commit is contained in:
Robin van der Linde 2025-07-28 15:36:15 +02:00
commit 1e0fdc5802
60 changed files with 659 additions and 600 deletions

@ -1 +1 @@
Subproject commit b7b29d20e40bde9144c719a2b59484c04cc79b9f
Subproject commit a48aaffec4ca59a2129834207e72ee3df85d2cd6

View file

@ -68,6 +68,7 @@
"ja": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です",
"nl": "De klimroutes zijn gemiddeld <b>{canonical(climbing:length)}</b> lang"
},
"icon": "./assets/themes/climbing/height.svg",
"freeform": {
"key": "climbing:length",
"type": "pfloat"
@ -426,6 +427,7 @@
"it": "Le vie di arrampicata sportiva qui hanno al massimo {climbing:bolts:max} spit. <div class='subtle'>Questo è senza le soste e indica quanti rinvii servono a un arrampicatore.</div>",
"nl": "De sportklimroutes hebben maximum {climbing:bolts:max} haken. <div class='subtle'>Hierbij worden standplaatsen niet meegeteld. Dit geeft aan hoeveel setjes een klimmer nodig heeft.</div>"
},
"icon": "./assets/themes/climbing/carabiner.svg",
"freeform": {
"key": "climbing:bolts:max",
"type": "pnat",

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

View file

@ -1,5 +1,5 @@
{
"id": "tree_node",
"id": "tree",
"name": {
"en": "Tree",
"ca": "Arbre",
@ -96,7 +96,7 @@
},
{
"icon": {
"render": "./assets/layers/tree_node/unknown.svg",
"render": "./assets/layers/tree/unknown.svg",
"mappings": [
{
"if": {
@ -104,7 +104,7 @@
"leaf_type=broadleaved"
]
},
"then": "./assets/layers/tree_node/broadleaved.svg"
"then": "./assets/layers/tree/broadleaved.svg"
},
{
"if": {
@ -112,7 +112,7 @@
"leaf_type=needleleaved"
]
},
"then": "./assets/layers/tree_node/needleleaved.svg"
"then": "./assets/layers/tree/needleleaved.svg"
},
{
"if": {
@ -120,7 +120,7 @@
"leaf_type=palm"
]
},
"then": "./assets/layers/tree_node/palm.svg"
"then": "./assets/layers/tree/palm.svg"
}
]
}
@ -272,6 +272,7 @@
"freeform": {
"key": "species:wikidata",
"type": "wikidata",
"inline": false,
"helperArgs": [
"species",
{
@ -578,7 +579,7 @@
},
"icon": {
"class": "small",
"path": "./assets/layers/tree_node/broadleaved.svg"
"path": "./assets/layers/tree/broadleaved.svg"
}
},
{
@ -601,7 +602,7 @@
},
"icon": {
"class": "small",
"path": "./assets/layers/tree_node/needleleaved.svg"
"path": "./assets/layers/tree/needleleaved.svg"
}
},
{
@ -626,7 +627,7 @@
"hideInAnswer": true,
"icon": {
"class": "small",
"path": "./assets/layers/tree_node/leafless.svg"
"path": "./assets/layers/tree/leafless.svg"
}
},
{
@ -714,7 +715,7 @@
}
},
{
"id": "tree_node-name",
"id": "tree-name",
"question": {
"en": "Does the tree have a name?",
"ca": "Té nom aquest arbre?",
@ -819,7 +820,7 @@
},
"icon": {
"class": "small",
"path": "./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg"
"path": "./assets/layers/tree/Onroerend_Erfgoed_logo_without_text.svg"
}
},
{
@ -915,7 +916,7 @@
}
},
{
"id": "tree_node-ref:OnroerendErfgoed",
"id": "tree-ref:OnroerendErfgoed",
"question": {
"en": "What is the ID issued by Onroerend Erfgoed Flanders?",
"ca": "Quina és la identificació emesa per Onroerend Erfgoed Flanders?",
@ -929,17 +930,25 @@
"pt": "Qual é o ID emitido por Onroerend Erfgoed Flanders?"
},
"render": {
"en": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"ca": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/>Identifiació Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"cs": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"da": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"de": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed Kennung: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"es": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> ID de Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"fr": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Identifiant Onroerend Erfgoed : <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"it": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> ID Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"nl": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed-ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>",
"ru": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"before": {
"en": "Onroerend Erfgoed ID: ",
"ca": "Identifiació Onroerend Erfgoed: ",
"cs": "Onroerend Erfgoed ID: ",
"da": "Onroerend Erfgoed ID: ",
"de": "Onroerend Erfgoed Kennung: ",
"es": "ID de Onroerend Erfgoed: ",
"fr": "Identifiant Onroerend Erfgoed : ",
"it": "ID Onroerend Erfgoed: ",
"nl": "Onroerend Erfgoed-ID: ",
"ru": "Onroerend Erfgoed ID:"
},
"special": {
"type": "link",
"href": "https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}",
"text": "{ref:OnroerendErfgoed}"
}
},
"icon": "./assets/layers/tree/Onroerend_Erfgoed_logo_without_text.svg",
"freeform": {
"key": "ref:OnroerendErfgoed",
"type": "nat"
@ -966,16 +975,9 @@
"pt": "Qual é o ID do Wikidata para esta árvore?"
},
"render": {
"en": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"ca": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/>Wikidata<a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"cs": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"da": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"de": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"es": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"fr": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata : <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"it": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"nl": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>",
"ru": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"special": {
"type": "wikipedia"
}
},
"freeform": {
"key": "wikidata",
@ -990,9 +992,6 @@
}
}
],
"deletion": {
"minNeededChangesets": 5
},
"allowMove": {
"enableRelocation": false,
"enableImproveAccuracy": true

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

@ -1,9 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1">
<g id="surface1">
<path style="fill:none;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(97.254902%,100%,96.078432%);stroke-opacity:1;stroke-miterlimit:4;" d="M -177.48351 -16.993714 C -177.484166 101.48875 -226.288922 214.739751 -312.411923 296.10684 C -398.528411 377.467771 -514.363074 419.770216 -632.651731 413.060164 " transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)"/>
<path style="fill:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 743.478328 134.561833 L 430.253662 430.253662 L 117.002105 134.508051 " transform="matrix(0.435789,0,0,0.435789,0,0)"/>
<path style="fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M -177.48351 -16.993714 C -177.484166 101.48875 -226.288922 214.739751 -312.411923 296.10684 C -398.528411 377.467771 -514.363074 419.770216 -632.651731 413.060164 " transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)"/>
<path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 743.478328 134.561833 L 430.253662 430.253662 L 117.002105 134.508051 " transform="matrix(0.435789,0,0,0.435789,0,0)"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="375px"
height="375px"
viewBox="0 0 375 375"
version="1.1"
id="svg4"
sodipodi:docname="direction_stroke.svg"
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4" />
<sodipodi:namedview
id="namedview4"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.87158493"
inkscape:cx="-23.52037"
inkscape:cy="19.504697"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<g
id="surface1"
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-opacity:1"
transform="translate(0,4)">
<path
style="color:#000000;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;-inkscape-stroke:none"
d="m -177.48437,-20.435547 a 3.4420288,3.4420288 0 0 0 -3.44141,3.441406 c -6.5e-4,117.537141 -48.41377,229.881731 -133.84961,310.599611 -85.42934,80.71172 -200.33664,122.6741 -317.68164,116.01758 a 3.4420288,3.4420288 0 0 0 -3.63086,3.24218 3.4420288,3.4420288 0 0 0 3.24219,3.63086 c 119.23207,6.76357 235.9934,-35.87674 322.79687,-117.88672 86.80999,-82.01614 136.00715,-196.17596 136.00781,-315.603511 a 3.4420288,3.4420288 0 0 0 -3.44335,-3.441406 z"
id="path1"
transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)" />
<path
style="color:#000000;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1;-inkscape-stroke:none"
d="m 119.36523,132.00586 -4.72656,5.00586 315.61524,297.97461 315.58789,-297.92188 -4.72657,-5.00586 -310.86132,293.46094 z"
id="path2"
transform="scale(0.435789)" />
<path
style="color:#000000;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;-inkscape-stroke:none"
d="m -177.48437,-20.435547 a 3.4420288,3.4420288 0 0 0 -3.44141,3.441406 c -6.5e-4,117.537141 -48.41377,229.881731 -133.84961,310.599611 -85.42934,80.71172 -200.33664,122.6741 -317.68164,116.01758 a 3.4420288,3.4420288 0 0 0 -3.63086,3.24218 3.4420288,3.4420288 0 0 0 3.24219,3.63086 c 119.23207,6.76357 235.9934,-35.87674 322.79687,-117.88672 86.80999,-82.01614 136.00715,-196.17596 136.00781,-315.603511 a 3.4420288,3.4420288 0 0 0 -3.44335,-3.441406 z"
id="path3"
transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)" />
<path
style="color:#000000;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1;-inkscape-stroke:none"
d="m 119.36523,132.00586 -4.72656,5.00586 315.61524,297.97461 315.58789,-297.92188 -4.72657,-5.00586 -310.86132,293.46094 z"
id="path4"
transform="scale(0.435789)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

@ -78,7 +78,7 @@
}
},
{
"builtin": "tree_node",
"builtin": "tree",
"override": {
"minzoom": 18,
"isCounted": false,

View file

@ -74,7 +74,7 @@
"Midgard"
],
"layers": [
"tree_node"
"tree"
],
"osmApiTileSize": 18,
"widenFactor": 0.2

View file

@ -12584,7 +12584,7 @@
"render": "Parada de transport públic"
}
},
"tree_node": {
"tree": {
"description": "Una capa que mostra arbres",
"name": "Arbre",
"presets": {
@ -12685,10 +12685,7 @@
},
"question": "És un arbre de fulla ampla o d'agulla?"
},
"tree-species-wikidata": {
"question": "De quina espècie és aquest arbre?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "L'arbre no té nom."
@ -12697,13 +12694,17 @@
"question": "Té nom aquest arbre?",
"render": "Nom : {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Quina és la identificació emesa per Onroerend Erfgoed Flanders?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/>Identifiació Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Identifiació Onroerend Erfgoed: "
}
},
"tree-species-wikidata": {
"question": "De quina espècie és aquest arbre?"
},
"tree_node-wikidata": {
"question": "Quin és l'identificador de Wikidata d'aquest arbre?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/>Wikidata<a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Quin és l'identificador de Wikidata d'aquest arbre?"
}
},
"title": {

View file

@ -13370,7 +13370,7 @@
"render": "Tranzitní zastávka"
}
},
"tree_node": {
"tree": {
"description": "Vrstva zobrazující stromy",
"name": "Strom",
"presets": {
@ -13477,10 +13477,7 @@
},
"question": "Jedná se o listnatý nebo jehličnatý strom?"
},
"tree-species-wikidata": {
"question": "Jaký druh je tento strom?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "Strom nemá jméno."
@ -13489,13 +13486,17 @@
"question": "Má strom nějaké jméno?",
"render": "Jméno: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Jaké je ID vydané společností Onroerend Erfgoed Flanders?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Onroerend Erfgoed ID: "
}
},
"tree-species-wikidata": {
"question": "Jaký druh je tento strom?"
},
"tree_node-wikidata": {
"question": "Jaké je ID Wikidata pro tento strom?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Jaké je ID Wikidata pro tento strom?"
}
},
"title": {

View file

@ -835,7 +835,7 @@
}
}
},
"tree_node": {
"tree": {
"name": "Coeden",
"tagRenderings": {
"tree-decidouous": {

View file

@ -3510,7 +3510,7 @@
"render": "Stoppested"
}
},
"tree_node": {
"tree": {
"description": "Et lag, der viser træer",
"name": "Træ",
"presets": {
@ -3597,10 +3597,7 @@
}
}
},
"tree-species-wikidata": {
"question": "Hvilken art er dette træ?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "Træet har ikke et navn."
@ -3609,13 +3606,17 @@
"question": "Har træet et navn?",
"render": "Navn: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Hvad er ID udstedt af Onroerend Erfgoed Flanders?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Onroerend Erfgoed ID: "
}
},
"tree-species-wikidata": {
"question": "Hvilken art er dette træ?"
},
"tree_node-wikidata": {
"question": "Hvad er Wikidata-id'et for dette træ?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Hvad er Wikidata-id'et for dette træ?"
}
},
"title": {

View file

@ -12573,7 +12573,7 @@
"render": "Haltestelle"
}
},
"tree_node": {
"tree": {
"description": "Eine Ebene, die Bäume zeigt",
"name": "Bäume",
"presets": {
@ -12674,10 +12674,7 @@
},
"question": "Ist dies ein Laub- oder Nadelbaum?"
},
"tree-species-wikidata": {
"question": "Um welche Baumart handelt es sich?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "Der Baum hat keinen Namen."
@ -12686,13 +12683,17 @@
"question": "Hat der Baum einen Namen?",
"render": "Name: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Wie lautet die Kennung der Onroerend Erfgoed Flanders?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed Kennung: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Onroerend Erfgoed Kennung: "
}
},
"tree-species-wikidata": {
"question": "Um welche Baumart handelt es sich?"
},
"tree_node-wikidata": {
"question": "Was ist das passende Wikidata Element zu diesem Baum?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Was ist das passende Wikidata Element zu diesem Baum?"
}
},
"title": {

View file

@ -13690,7 +13690,7 @@
"render": "Transit Stop"
}
},
"tree_node": {
"tree": {
"description": "A layer showing trees",
"name": "Tree",
"presets": {
@ -13797,10 +13797,7 @@
},
"question": "Is this a broadleaved or needleleaved tree?"
},
"tree-species-wikidata": {
"question": "What species is this tree?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "The tree does not have a name."
@ -13809,13 +13806,17 @@
"question": "Does the tree have a name?",
"render": "Name: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "What is the ID issued by Onroerend Erfgoed Flanders?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Onroerend Erfgoed ID: "
}
},
"tree-species-wikidata": {
"question": "What species is this tree?"
},
"tree_node-wikidata": {
"question": "What is the Wikidata ID for this tree?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "What is the Wikidata ID for this tree?"
}
},
"title": {

View file

@ -211,9 +211,9 @@
}
}
},
"tree_node": {
"tree": {
"tagRenderings": {
"tree_node-name": {
"tree-name": {
"render": "Nomo: {name}"
}
},

View file

@ -11452,7 +11452,7 @@
"render": "Parada de transporte"
}
},
"tree_node": {
"tree": {
"description": "Una capa que muestra árboles",
"name": "Árbol",
"presets": {
@ -11553,10 +11553,7 @@
},
"question": "¿Es este un árbol de hoja ancha o acicular?"
},
"tree-species-wikidata": {
"question": "¿De qué especie es este árbol?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "El árbol no tiene nombre."
@ -11565,13 +11562,17 @@
"question": "¿Tiene el árbol un nombre?",
"render": "Nombre: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "¿Cuál es el ID emitido por Onroerend Erfgoed Flandes?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> ID de Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "ID de Onroerend Erfgoed: "
}
},
"tree-species-wikidata": {
"question": "¿De qué especie es este árbol?"
},
"tree_node-wikidata": {
"question": "¿Cuál es el ID de Wikidata para este árbol?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "¿Cuál es el ID de Wikidata para este árbol?"
}
},
"title": {

View file

@ -6930,7 +6930,7 @@
}
}
},
"tree_node": {
"tree": {
"description": "Une couche montrant les arbres",
"name": "Arbre",
"presets": {
@ -7031,10 +7031,7 @@
},
"question": "Cet arbre est-il un feuillu ou un résineux ?"
},
"tree-species-wikidata": {
"question": "Quelle est l'espèce de cet arbre ?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "L'arbre n'a pas de nom."
@ -7043,13 +7040,17 @@
"question": "L'arbre a-t-il un nom ?",
"render": "Nom : {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Quel est son identifiant donné par Onroerend Erfgoed ?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Identifiant Onroerend Erfgoed : <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Identifiant Onroerend Erfgoed : "
}
},
"tree-species-wikidata": {
"question": "Quelle est l'espèce de cet arbre ?"
},
"tree_node-wikidata": {
"question": "Quel est l'identifiant Wikidata de cet arbre ?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata : <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Quel est l'identifiant Wikidata de cet arbre ?"
}
},
"title": {

View file

@ -764,7 +764,7 @@
}
}
},
"tree_node": {
"tree": {
"presets": {
"2": {
"title": "Pohon"
@ -804,10 +804,7 @@
}
}
},
"tree-species-wikidata": {
"question": "Spesies pohon apa ini?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "Pohon ini tidak memiliki nama."
@ -815,6 +812,9 @@
},
"question": "Apakah pohon ini memiliki nama?",
"render": "Nama: {name}"
},
"tree-species-wikidata": {
"question": "Spesies pohon apa ini?"
}
}
},

View file

@ -13269,7 +13269,7 @@
"render": "Fermata dei mezzi pubblici"
}
},
"tree_node": {
"tree": {
"description": "Un livello che mostra gli alberi",
"name": "Albero",
"presets": {
@ -13376,10 +13376,7 @@
},
"question": "Questo è un albero latifoglie o aghifoglie?"
},
"tree-species-wikidata": {
"question": "Di che specie è questo albero?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "L'albero non ha un nome."
@ -13388,13 +13385,17 @@
"question": "L'albero ha un nome?",
"render": "Nome: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Qual è l'ID rilasciato da Onroerend Erfgoed Fiandre?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> ID Onroerend Erfgoed: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "ID Onroerend Erfgoed: "
}
},
"tree-species-wikidata": {
"question": "Di che specie è questo albero?"
},
"tree_node-wikidata": {
"question": "Qual è l'ID Wikidata per questo albero?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Qual è l'ID Wikidata per questo albero?"
}
},
"title": {

View file

@ -10726,7 +10726,7 @@
"render": "OV-halte"
}
},
"tree_node": {
"tree": {
"description": "Een laag die bomen toont",
"name": "Boom",
"presets": {
@ -10833,10 +10833,7 @@
},
"question": "Is dit een naald- of loofboom?"
},
"tree-species-wikidata": {
"question": "Wat is de boomsoort?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "De boom heeft geen naam."
@ -10845,13 +10842,17 @@
"question": "Heeft de boom een naam?",
"render": "Naam: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Wat is het ID uitgegeven door Onroerend Erfgoed Vlaanderen?",
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed-ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
"render": {
"before": "Onroerend Erfgoed-ID: "
}
},
"tree-species-wikidata": {
"question": "Wat is de boomsoort?"
},
"tree_node-wikidata": {
"question": "Wat is het Wikidata-ID van deze boom?",
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"question": "Wat is het Wikidata-ID van deze boom?"
}
},
"title": {

View file

@ -3583,7 +3583,7 @@
"render": "Ścieżka"
}
},
"tree_node": {
"tree": {
"tagRenderings": {
"tree-leaf_type": {
"mappings": {

View file

@ -1777,7 +1777,7 @@
}
}
},
"tree_node": {
"tree": {
"name": "Árvore",
"presets": {
"0": {
@ -1872,10 +1872,7 @@
},
"question": "Esta é uma árvore de folhas largas ou acículas?"
},
"tree-species-wikidata": {
"question": "Que espécie é esta árvore?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "A árvore não tem nome."
@ -1884,9 +1881,12 @@
"question": "A árvore tem nome?",
"render": "Nome: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"tree-ref:OnroerendErfgoed": {
"question": "Qual é o ID emitido por Onroerend Erfgoed Flanders?"
},
"tree-species-wikidata": {
"question": "Que espécie é esta árvore?"
},
"tree_node-wikidata": {
"question": "Qual é o ID do Wikidata para esta árvore?"
}

View file

@ -1611,7 +1611,7 @@
}
}
},
"tree_node": {
"tree": {
"name": "Árvore",
"presets": {
"2": {

View file

@ -1977,7 +1977,7 @@
}
}
},
"tree_node": {
"tree": {
"name": "Дерево",
"presets": {
"0": {
@ -2004,7 +2004,7 @@
},
"question": "Это дерево вечнозелёное или листопадное?"
},
"tree_node-name": {
"tree-name": {
"mappings": {
"0": {
"then": "У этого дерева нет названия."
@ -2013,11 +2013,10 @@
"question": "Есть ли у этого дерева название?",
"render": "Название: {name}"
},
"tree_node-ref:OnroerendErfgoed": {
"render": "<img src=\"./assets/layers/tree_node/Onroerend_Erfgoed_logo_without_text.svg\" style=\"width:0.85em;height:1em;vertical-align:middle\" alt=\"\"/> Onroerend Erfgoed ID: <a href=\"https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}\">{ref:OnroerendErfgoed}</a>"
},
"tree_node-wikidata": {
"render": "<img src=\"./assets/svg/wikidata.svg\" style=\"width:1em;height:0.56em;vertical-align:middle\" alt=\"\"/> Wikidata: <a href=\"http://www.wikidata.org/entity/{wikidata}\">{wikidata}</a>"
"tree-ref:OnroerendErfgoed": {
"render": {
"before": "Onroerend Erfgoed ID:"
}
}
},
"title": {

View file

@ -2998,7 +2998,7 @@
}
}
},
"tree_node": {
"tree": {
"tagRenderings": {
"tree-species-wikidata": {
"question": "Якого виду це дерево?"

View file

@ -2209,6 +2209,10 @@ input[type="range"].range-lg::-moz-range-thumb {
min-width: 8rem;
}
.min-w-48 {
min-width: 12rem;
}
.min-w-6 {
min-width: 1.5rem;
}

View file

@ -22,6 +22,7 @@ import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRender
import MarkdownUtils from "../../Utils/MarkdownUtils"
import { And } from "../../Logic/Tags/And"
import OsmWiki from "../../Logic/Osm/OsmWiki"
import { UnitUtils } from "../UnitUtils"
export default class LayerConfig extends WithContextLoader {
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
@ -312,7 +313,7 @@ export default class LayerConfig extends WithContextLoader {
)
}
this.units = (json.units ?? []).flatMap((unitJson, i) =>
Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`)
UnitUtils.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`)
)
{
let filter = json.filter

View file

@ -1,14 +1,9 @@
import BaseUIElement from "../UI/BaseUIElement"
import { Denomination } from "./Denomination"
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
import unit from "../../assets/layers/unit/unit.json"
import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig"
import Validators, { ValidatorType } from "../UI/InputElement/Validators"
import { Validator } from "../UI/InputElement/Validator"
import FloatValidator from "../UI/InputElement/Validators/FloatValidator"
export class Unit {
private static allUnits = this.initUnits()
public readonly appliesToKeys: Set<string>
public readonly denominations: Denomination[]
public readonly denominationsSorted: Denomination[]
@ -85,226 +80,7 @@ export class Unit {
}
}
static fromJson(
json:
| UnitConfigJson
| Record<
string,
string | { quantity: string; denominations: string[]; inverted?: boolean }
>,
tagRenderings: TagRenderingConfig[],
ctx: string
): Unit[] {
const types: Record<string, ValidatorType> = {}
for (const tagRendering of tagRenderings) {
if (tagRendering.freeform?.type) {
types[tagRendering.freeform.key] = tagRendering.freeform.type
}
}
if (!json.appliesToKey && !json.quantity) {
return this.loadFromLibrary(<any>json, types, ctx)
}
return this.parse(<UnitConfigJson>json, types, ctx)
}
private static parseDenomination(
json: UnitConfigJson,
validator: Validator,
appliesToKey: string,
ctx: string
): Unit {
const applicable = json.applicableUnits.map((u, i) =>
Denomination.fromJson(u, validator, `${ctx}.units[${i}]`)
)
if (
json.defaultInput &&
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
) {
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
json.defaultInput
}', but the available denominations are ${applicable
.map((denom) => denom.canonical)
.join(", ")}`
}
return new Unit(
json.quantity ?? "",
appliesToKey === undefined ? undefined : [appliesToKey],
applicable,
json.eraseInvalidValues ?? false,
validator
)
}
/**
*
* // Should detect invalid defaultInput
* let threwError = false
* try{
* Unit.parse({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* human: "meter"
* }
* ]
* },"test")
* }catch(e){
* threwError = true
* }
* threwError // => true
*
* // Should work
* Unit.parse({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* humen: "meter"
* },
* {
* canonicalDenomination: "cm",
* human: "centimeter"
* }
* ]
* }, "test")
*/
private static parse(
json: UnitConfigJson,
types: Record<string, ValidatorType>,
ctx: string
): Unit[] {
const appliesTo = json.appliesToKey
for (let i = 0; i < (appliesTo ?? []).length; i++) {
const key = appliesTo[i]
if (key.trim() !== key) {
throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace`
}
}
if ((json.applicableUnits ?? []).length === 0) {
throw `${ctx}: define at least one applicable unit`
}
// Some keys do have unit handling
const units: Unit[] = []
if (appliesTo === undefined) {
units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx))
}
for (const key of appliesTo ?? []) {
const validator = Validators.get(types[key] ?? "float")
units.push(this.parseDenomination(json, validator, undefined, ctx))
}
return units
}
private static initUnits(): Map<string, Unit> {
const m = new Map<string, Unit>()
const units = (<UnitConfigJson[]>unit.units).flatMap((json, i) =>
this.parse(json, {}, "unit.json.units." + i)
)
for (const unit of units) {
m.set(unit.quantity, unit)
}
return m
}
private static getFromLibrary(name: string, ctx: string): Unit {
const loaded = this.allUnits.get(name)
if (loaded === undefined) {
throw (
"No unit with quantity name " +
name +
" found (at " +
ctx +
"). Try one of: " +
Array.from(this.allUnits.keys()).join(", ")
)
}
return loaded
}
private static loadFromLibrary(
spec: Record<
string,
| string
| { quantity: string; denominations: string[]; canonical?: string; inverted?: boolean }
>,
types: Record<string, ValidatorType>,
ctx: string
): Unit[] {
const units: Unit[] = []
for (const key in spec) {
const toLoad = spec[key]
const validator = Validators.get(types[key] ?? "float")
if (typeof toLoad === "string") {
const loaded = this.getFromLibrary(toLoad, ctx)
units.push(
new Unit(
loaded.quantity,
[key],
loaded.denominations,
loaded.eraseInvalid,
validator,
toLoad["inverted"]
)
)
continue
}
const loaded = this.getFromLibrary(toLoad.quantity, ctx)
const quantity = toLoad.quantity
const fetchDenom = (d: string): Denomination => {
const found = loaded.denominations.find(
(denom) => denom.canonical.toLowerCase() === d
)
if (!found) {
throw (
`Could not find a denomination \`${d}\`for quantity ${quantity} at ${ctx}. Perhaps you meant to use on of ` +
loaded.denominations.map((d) => d.canonical).join(", ")
)
}
return found
}
if (!Array.isArray(toLoad.denominations)) {
throw (
"toLoad is not an array. Did you forget the [ and ] around the denominations at " +
ctx +
"?"
)
}
const denoms = toLoad.denominations
.map((d) => d.toLowerCase())
.map((d) => fetchDenom(d))
.map((d) => d.withValidator(validator))
if (toLoad.canonical) {
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
denoms.unshift(canonical.withBlankCanonical())
}
units.push(
new Unit(
loaded.quantity,
[key],
denoms,
loaded.eraseInvalid,
validator,
toLoad["inverted"]
)
)
}
return units
}
isApplicableToKey(key: string | undefined): boolean {
if (key === undefined) {

232
src/Models/UnitUtils.ts Normal file
View file

@ -0,0 +1,232 @@
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig"
import Validators, { ValidatorType } from "../UI/InputElement/Validators"
import { Validator } from "../UI/InputElement/Validator"
import { Denomination } from "./Denomination"
import unit from "../../assets/layers/unit/unit.json"
import { Unit } from "./Unit"
export class UnitUtils {
private static allUnits = this.initUnits()
static fromJson(
json:
| UnitConfigJson
| Record<
string,
string | { quantity: string; denominations: string[]; inverted?: boolean }
>,
tagRenderings: TagRenderingConfig[],
ctx: string,
): Unit[] {
const types: Record<string, ValidatorType> = {}
for (const tagRendering of tagRenderings) {
if (tagRendering.freeform?.type) {
types[tagRendering.freeform.key] = tagRendering.freeform.type
}
}
if (!json.appliesToKey && !json.quantity) {
return this.loadFromLibrary(<any>json, types, ctx)
}
return this.parse(<UnitConfigJson>json, types, ctx)
}
private static parseDenomination(
json: UnitConfigJson,
validator: Validator,
appliesToKey: string,
ctx: string,
): Unit {
const applicable = json.applicableUnits.map((u, i) =>
Denomination.fromJson(u, validator, `${ctx}.units[${i}]`),
)
if (
json.defaultInput &&
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
) {
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
json.defaultInput
}', but the available denominations are ${applicable
.map((denom) => denom.canonical)
.join(", ")}`
}
return new Unit(
json.quantity ?? "",
appliesToKey === undefined ? undefined : [appliesToKey],
applicable,
json.eraseInvalidValues ?? false,
validator,
)
}
/**
*
* // Should detect invalid defaultInput
* let threwError = false
* try{
* Unit.parse({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* human: "meter"
* }
* ]
* },"test")
* }catch(e){
* threwError = true
* }
* threwError // => true
*
* // Should work
* Unit.parse({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* humen: "meter"
* },
* {
* canonicalDenomination: "cm",
* human: "centimeter"
* }
* ]
* }, "test")
*/
private static parse(
json: UnitConfigJson,
types: Record<string, ValidatorType>,
ctx: string,
): Unit[] {
const appliesTo = json.appliesToKey
for (let i = 0; i < (appliesTo ?? []).length; i++) {
const key = appliesTo[i]
if (key.trim() !== key) {
throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace`
}
}
if ((json.applicableUnits ?? []).length === 0) {
throw `${ctx}: define at least one applicable unit`
}
// Some keys do have unit handling
const units: Unit[] = []
if (appliesTo === undefined) {
units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx))
}
for (const key of appliesTo ?? []) {
const validator = Validators.get(types[key] ?? "float")
units.push(this.parseDenomination(json, validator, undefined, ctx))
}
return units
}
private static initUnits(): Map<string, Unit> {
const m = new Map<string, Unit>()
const units = (<UnitConfigJson[]>unit.units).flatMap((json, i) =>
this.parse(json, {}, "unit.json.units." + i),
)
for (const unit of units) {
m.set(unit.quantity, unit)
}
return m
}
private static getFromLibrary(name: string, ctx: string): Unit {
const loaded = this.allUnits.get(name)
if (loaded === undefined) {
throw (
"No unit with quantity name " +
name +
" found (at " +
ctx +
"). Try one of: " +
Array.from(this.allUnits.keys()).join(", ")
)
}
return loaded
}
private static loadFromLibrary(
spec: Record<
string,
| string
| { quantity: string; denominations: string[]; canonical?: string; inverted?: boolean }
>,
types: Record<string, ValidatorType>,
ctx: string,
): Unit[] {
const units: Unit[] = []
for (const key in spec) {
const toLoad = spec[key]
const validator = Validators.get(types[key] ?? "float")
if (typeof toLoad === "string") {
const loaded = this.getFromLibrary(toLoad, ctx)
units.push(
new Unit(
loaded.quantity,
[key],
loaded.denominations,
loaded.eraseInvalid,
validator,
toLoad["inverted"],
),
)
continue
}
const loaded = this.getFromLibrary(toLoad.quantity, ctx)
const quantity = toLoad.quantity
const fetchDenom = (d: string): Denomination => {
const found = loaded.denominations.find(
(denom) => denom.canonical.toLowerCase() === d,
)
if (!found) {
throw (
`Could not find a denomination \`${d}\`for quantity ${quantity} at ${ctx}. Perhaps you meant to use on of ` +
loaded.denominations.map((d) => d.canonical).join(", ")
)
}
return found
}
if (!Array.isArray(toLoad.denominations)) {
throw (
"toLoad is not an array. Did you forget the [ and ] around the denominations at " +
ctx +
"?"
)
}
const denoms = toLoad.denominations
.map((d) => d.toLowerCase())
.map((d) => fetchDenom(d))
.map((d) => d.withValidator(validator))
if (toLoad.canonical) {
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
denoms.unshift(canonical.withBlankCanonical())
}
units.push(
new Unit(
loaded.quantity,
[key],
denoms,
loaded.eraseInvalid,
validator,
toLoad["inverted"],
),
)
}
return units
}
}

View file

@ -6,25 +6,35 @@
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import Direction_stroke from "../../../assets/svg/Direction_stroke.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import type Feature from "geojson"
import { GeoOperations } from "../../../Logic/GeoOperations"
/**
* A visualisation to pick a direction on a map background.
*/
export let value: UIEventSource<undefined | string>
export let state: SpecialVisualizationState = undefined
export let mapProperties: Partial<MapProperties> & {
readonly location: UIEventSource<{ lon: number; lat: number }>
export let args: any[] = []
export let feature: Feature
let [lon, lat] = GeoOperations.centerpointCoordinates(feature)
let mapProperties: MapProperties = {
location: new UIEventSource({ lon, lat }),
zoom: new UIEventSource(args[0] !== undefined ? Number(args[0]) : 17),
rasterLayer: state.mapProperties.rasterLayer,
rotation: state.mapProperties.rotation,
}
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mla = new MapLibreAdaptor(map, mapProperties)
mla.allowMoving.setData(false)
mla.allowZooming.setData(false)
state?.mapProperties?.rasterLayer?.addCallbackAndRunD((l) => mla.rasterLayer.set(l))
let rotation = new UIEventSource(value.data)
rotation.addCallbackD(rotation => {
const r = (rotation + mapProperties.rotation.data + 360) % 360
console.log("Setting value to", r)
value.setData(""+Math.floor(r))
}, [mapProperties.rotation])
let directionElem: HTMLElement | undefined
$: value.addCallbackAndRunD((degrees) => {
if (directionElem === undefined) {
$: rotation.addCallbackAndRunD((degrees) => {
if (!directionElem?.style) {
return
}
directionElem.style.rotate = degrees + "deg"
@ -32,13 +42,14 @@
let mainElem: HTMLElement
function onPosChange(x: number, y: number) {
const rect = mainElem.getBoundingClientRect()
const dx = -(rect.left + rect.right) / 2 + x
const dy = (rect.top + rect.bottom) / 2 - y
const angle = (180 * Math.atan2(dy, dx)) / Math.PI
const angleGeo = Math.floor((450 - angle) % 360)
value.setData("" + angleGeo)
rotation.setData(angleGeo)
}
let isDown = false
@ -46,7 +57,7 @@
<div
bind:this={mainElem}
class="relative h-48 w-48 cursor-pointer overflow-hidden"
class="relative h-48 min-w-48 w-full cursor-pointer overflow-hidden rounded-xl"
on:click={(e) => onPosChange(e.x, e.y)}
on:mousedown={(e) => {
isDown = true
@ -71,7 +82,7 @@
<MaplibreMap mapProperties={mla} {map} />
</div>
<div bind:this={directionElem} class="absolute left-0 top-0 h-full w-full">
<div bind:this={directionElem} class="absolute left-0 top-0 h-full w-full p-1">
<Direction_stroke />
</div>
</div>

View file

@ -1,67 +1,25 @@
<script lang="ts">
/**
* Constructs an input helper element for the given type.
* Note that all values are stringified
*/
import { UIEventSource } from "../../Logic/UIEventSource"
import type { ValidatorType } from "./Validators"
import InputHelpers from "./InputHelpers"
import Validators from "./Validators"
import type { Feature } from "geojson"
import ImageHelper from "./Helpers/ImageHelper.svelte"
import TranslationInput from "./Helpers/TranslationInput.svelte"
import TagInput from "./Helpers/TagInput.svelte"
import SimpleTagInput from "./Helpers/SimpleTagInput.svelte"
import DirectionInput from "./Helpers/DirectionInput.svelte"
import DateInput from "./Helpers/DateInput.svelte"
import ColorInput from "./Helpers/ColorInput.svelte"
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
import SlopeInput from "./Helpers/SlopeInput.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import WikidataInputHelper from "./Helpers/WikidataInputHelper.svelte"
import type { Validator } from "./Validator"
import DistanceInput from "./Helpers/DistanceInput.svelte"
import TimeInput from "./Helpers/TimeInput.svelte"
import CollectionTimes from "./Helpers/CollectionTimes/CollectionTimes.svelte"
export let type: ValidatorType
export let value: UIEventSource<string | object>
export let feature: Feature = undefined
export let args: (string | number | boolean)[] | any = undefined
export let state: SpecialVisualizationState = undefined
let validator = Validators.get(type)
let validatorHelper: Validator = validator.inputHelper
</script>
{#if type === "translation"}
<TranslationInput {value} on:submit {args} />
{:else if type === "direction"}
<DirectionInput
{value}
{state}
mapProperties={InputHelpers.constructMapProperties({ feature, args: args ?? [] })}
/>
{:else if type === "date"}
<DateInput {value} />
{:else if type === "time"}
<TimeInput {value} />
{:else if type === "points_in_time"}
<CollectionTimes {value} />
{:else if type === "color"}
<ColorInput {value} />
{:else if type === "image"}
<ImageHelper {value} />
{:else if type === "tag"}
<TagInput {value} on:submit />
{:else if type === "simple_tag"}
<SimpleTagInput {value} {args} on:submit />
{:else if type === "opening_hours"}
<OpeningHoursInput {value} {args} />
{:else if type === "slope"}
<SlopeInput {value} {feature} {state} />
{:else if type === "wikidata"}
<WikidataInputHelper {value} {feature} {state} {args} />
{:else if type === "distance"}
<DistanceInput {value} {state} {feature} {args} />
{:else}
<slot name="fallback" />
{#if type === "distance"}
<DistanceInput {value} {feature} {state} {args} />
{:else if validatorHelper !== undefined}
<svelte:component this={validatorHelper} {value} {feature} {state} {args} on:submit />
{/if}

View file

@ -1,67 +0,0 @@
import { UIEventSource } from "../../Logic/UIEventSource"
import { MapProperties } from "../../Models/MapProperties"
import { Feature } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
import { ValidatorType } from "./Validators"
export interface InputHelperProperties {
/**
* Extra arguments which might be used by the helper component
*/
args?: (string | number | boolean)[]
/**
* Used for map-based helpers, such as 'direction'
*/
mapProperties?: Partial<MapProperties> & {
readonly location: UIEventSource<{ lon: number; lat: number }>
}
/**
* The feature that this question is about
* Used by the wikidata-input to read properties, which in turn is used to read the name to pre-populate the text field.
* Additionally, used for direction input to set the default location if no mapProperties with location are given
*/
feature?: Feature
}
export default class InputHelpers {
public static hideInputField: ValidatorType[] = ["translation", "simple_tag", "tag","time"]
/**
* Constructs a mapProperties-object for the given properties.
* Assumes that the first helper-args contains the desired zoom-level
* Used for the 'direction' input helper
* @param properties
* @private
*/
public static constructMapProperties(
properties: InputHelperProperties
): Partial<MapProperties> {
let location = properties?.mapProperties?.location
if (!location) {
const [lon, lat] = GeoOperations.centerpointCoordinates(properties.feature)
location = new UIEventSource<{ lon: number; lat: number }>({ lon, lat })
}
let mapProperties: Partial<MapProperties> = properties?.mapProperties ?? { location }
if (!mapProperties.location) {
mapProperties = { ...mapProperties, location }
}
let zoom = 17
if (properties?.args?.[0] !== undefined) {
zoom = Number(properties.args[0])
if (isNaN(zoom)) {
throw "Invalid zoom level for argument at 'length'-input"
}
}
if (!mapProperties.zoom) {
mapProperties = { ...mapProperties, zoom: new UIEventSource<number>(zoom) }
}
if (!mapProperties.rasterLayer) {
/* mapProperties = {
...mapProperties, rasterLayer: properties?.mapProperties?.rasterLayer
}*/
}
return mapProperties
}
}

View file

@ -1,6 +1,7 @@
import { Translation } from "../i18n/Translation"
import Translations from "../i18n/Translations"
import { HTMLInputTypeAttribute } from "svelte/elements"
import { ComponentType } from "svelte/types/runtime/internal/dev"
/**
* A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback.
@ -21,6 +22,8 @@ export abstract class Validator {
public readonly textArea: boolean
public readonly isMeta?: boolean
public readonly inputHelper : ComponentType = undefined
public readonly hideInputField: boolean = false
constructor(
name: string,
@ -80,4 +83,5 @@ export abstract class Validator {
public validateArguments(args: string): undefined | string {
return undefined
}
}

View file

@ -1,40 +1,37 @@
import { Validator } from "./Validator"
import FloatValidator from "./Validators/FloatValidator"
import StringValidator from "./Validators/StringValidator"
import PFloatValidator from "./Validators/PFloatValidator"
import TextValidator from "./Validators/TextValidator"
import DateValidator from "./Validators/DateValidator"
import { TimeValidator } from "./Validators/TimeValidator"
import NatValidator from "./Validators/NatValidator"
import IntValidator from "./Validators/IntValidator"
import DistanceValidator from "./Validators/DistanceValidator"
import PNatValidator from "./Validators/PNatValidator"
import DirectionValidator from "./Validators/DirectionValidator"
import WikidataValidator from "./Validators/WikidataValidator"
import PNatValidator from "./Validators/PNatValidator"
import FloatValidator from "./Validators/FloatValidator"
import PFloatValidator from "./Validators/PFloatValidator"
import EmailValidator from "./Validators/EmailValidator"
import UrlValidator from "./Validators/UrlValidator"
import PhoneValidator from "./Validators/PhoneValidator"
import OpeningHoursValidator from "./Validators/OpeningHoursValidator"
import PhoneValidator from "./Validators/PhoneValidator"
import ColorValidator from "./Validators/ColorValidator"
import SimpleTagValidator from "./Validators/SimpleTagValidator"
import ImageUrlValidator from "./Validators/ImageUrlValidator"
import TagKeyValidator from "./Validators/TagKeyValidator"
import TranslationValidator from "./Validators/TranslationValidator"
import FediverseValidator from "./Validators/FediverseValidator"
import IconValidator from "./Validators/IconValidator"
import TagValidator from "./Validators/TagValidator"
import IdValidator from "./Validators/IdValidator"
import SlopeValidator from "./Validators/SlopeValidator"
import CollectionTimesValidator from "./Validators/CollectionTimesValidator"
import IdValidator from "./Validators/IdValidator"
import FediverseValidator from "./Validators/FediverseValidator"
import SimpleTagValidator from "./Validators/SimpleTagValidator"
import VeloparkValidator from "./Validators/VeloparkValidator"
import NameSuggestionIndexValidator from "./Validators/NameSuggestionIndexValidator"
import CurrencyValidator from "./Validators/CurrencyValidator"
import RegexValidator from "./Validators/RegexValidator"
import { TimeValidator } from "./Validators/TimeValidator"
import CollectionTimesValidator from "./Validators/CollectionTimesValidator"
import CurrencyValidator from "./Validators/CurrencyValidator"
import TagValidator from "./Validators/TagValidator"
import TranslationValidator from "./Validators/TranslationValidator"
import DistanceValidator from "./Validators/DistanceValidator"
export type ValidatorType = (typeof Validators.availableTypes)[number]
export default class Validators {
public static readonly availableTypes = [
export const availableValidators = [
"color",
"currency",
"date",
@ -67,39 +64,51 @@ export default class Validators {
"velopark",
"wikidata",
] as const
export type ValidatorType = (typeof availableValidators)[number]
export default class Validators {
public static readonly availableTypes = availableValidators
public static readonly AllValidators: ReadonlyArray<Validator> = [
new StringValidator(),
new TextValidator(),
new DateValidator(),
new TimeValidator(),
new NatValidator(),
new IntValidator(),
new DistanceValidator(),
new DirectionValidator(),
new WikidataValidator(),
new PNatValidator(),
new FloatValidator(),
new PFloatValidator(),
new EmailValidator(),
new UrlValidator(),
new PhoneValidator(),
new OpeningHoursValidator(),
new TextValidator(),
new NatValidator(),
new PNatValidator(),
new IntValidator(),
new DateValidator(),
new TimeValidator(),
new ColorValidator(),
new ImageUrlValidator(),
new SimpleTagValidator(),
new TagValidator(),
new TagKeyValidator(),
new TranslationValidator(),
new IconValidator(),
new FediverseValidator(),
new IdValidator(),
new DirectionValidator(),
new SlopeValidator(),
new VeloparkValidator(),
new NameSuggestionIndexValidator(),
new UrlValidator(),
new EmailValidator(),
new PhoneValidator(),
new FediverseValidator(),
new ImageUrlValidator(),
new OpeningHoursValidator(),
new CollectionTimesValidator(),
new CurrencyValidator(),
new WikidataValidator(),
new TagKeyValidator(),
new IconValidator(),
new VeloparkValidator(),
new IdValidator(),
new RegexValidator(),
new CollectionTimesValidator()
new SimpleTagValidator(),
new TranslationValidator(),
new TagValidator(),
new NameSuggestionIndexValidator(),
new DistanceValidator(),
]
private static _byType = Validators._byTypeConstructor()

View file

@ -1,7 +1,12 @@
import StringValidator from "./StringValidator"
import { ComponentType } from "svelte/types/runtime/internal/dev"
import CollectionTimes from "../Helpers/CollectionTimes/CollectionTimes.svelte"
export default class CollectionTimesValidator extends StringValidator{
public readonly inputHelper: ComponentType = CollectionTimes
constructor() {
super("points_in_time", "'Points in time' are points according to a fixed schedule, e.g. 'every monday at 10:00'. They are typically used for postbox collection times or times of mass at a place of worship")
}
}

View file

@ -1,7 +1,11 @@
import { Validator } from "../Validator"
import ColorInput from "../Helpers/ColorInput.svelte"
export default class ColorValidator extends Validator {
inputHelper = ColorInput
constructor() {
super("color", "Shows a color picker")
}
}

View file

@ -1,6 +1,10 @@
import { Validator } from "../Validator"
import DateInput from "../Helpers/DateInput.svelte"
export default class DateValidator extends Validator {
public readonly inputHelper = DateInput
public readonly hideInputField = true
constructor() {
super("date", "A date with date picker")
}
@ -25,4 +29,5 @@ export default class DateValidator extends Validator {
return [year, month, day].join("-")
}
}

View file

@ -1,6 +1,9 @@
import IntValidator from "./IntValidator"
import DirectionInput from "../Helpers/DirectionInput.svelte"
export default class DirectionValidator extends IntValidator {
public readonly inputHelper = DirectionInput
constructor() {
super(
"direction",

View file

@ -3,6 +3,7 @@ import { Utils } from "../../../Utils"
import { eliCategory } from "../../../Models/RasterLayerProperties"
export default class DistanceValidator extends Validator {
private readonly docs: string = [
"#### Helper-arguments",
"Options are:",
@ -58,4 +59,5 @@ export default class DistanceValidator extends Validator {
}
return undefined
}
}

View file

@ -1,9 +1,11 @@
import UrlValidator from "./UrlValidator"
import { Translation } from "../../i18n/Translation"
import ImageHelper from "../Helpers/ImageHelper.svelte"
export default class ImageUrlValidator extends UrlValidator {
private static readonly allowedExtensions = ["jpg", "jpeg", "svg", "png"]
public readonly isMeta = true
public readonly inputHelper = ImageHelper
constructor() {
super(
@ -37,4 +39,6 @@ export default class ImageUrlValidator extends UrlValidator {
}
return ImageUrlValidator.hasValidExternsion(str)
}
}

View file

@ -1,7 +1,12 @@
import { Validator } from "../Validator"
import MarkdownUtils from "../../../Utils/MarkdownUtils"
import { ComponentType } from "svelte/types/runtime/internal/dev"
import OpeningHoursInput from "../Helpers/OpeningHoursInput.svelte"
export default class OpeningHoursValidator extends Validator {
public readonly inputHelper= OpeningHoursInput
constructor() {
super(
"opening_hours",
@ -39,4 +44,6 @@ export default class OpeningHoursValidator extends Validator {
].join("\n")
)
}
}

View file

@ -2,12 +2,15 @@ import { Validator } from "../Validator"
import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations"
import TagKeyValidator from "./TagKeyValidator"
import SimpleTagInput from "../Helpers/SimpleTagInput.svelte"
/**
* Checks that the input conforms `key=value`, where `key` and `value` don't have too much weird characters
*/
export default class SimpleTagValidator extends Validator {
private static readonly KeyValidator = new TagKeyValidator()
public readonly inputHelper = SimpleTagInput
public readonly hideInputField = true
public readonly isMeta = true
constructor() {
@ -50,4 +53,6 @@ export default class SimpleTagValidator extends Validator {
isValid(tag: string, _): boolean {
return this.getFeedback(tag, _) === undefined
}
}

View file

@ -1,6 +1,9 @@
import FloatValidator from "./FloatValidator"
import SlopeInput from "../Helpers/SlopeInput.svelte"
export default class SlopeValidator extends FloatValidator {
public readonly inputHelper =SlopeInput
constructor() {
super(
"slope",
@ -40,4 +43,5 @@ export default class SlopeValidator extends FloatValidator {
}
return super.reformat(str) + lastChar
}
}

View file

@ -1,11 +1,14 @@
import { Validator } from "../Validator"
import { Translation } from "../../i18n/Translation"
import TagInput from "../Helpers/TagInput.svelte"
/**
* Checks that the input conforms a JSON-encoded tag expression or a simpleTag`key=value`,
*/
export default class TagValidator extends Validator {
public readonly isMeta = true
public readonly inputHelper = TagInput
public readonly hideInputField = true
constructor() {
super("tag", "A simple tag of the format `key=value` OR a tagExpression")
@ -18,4 +21,5 @@ export default class TagValidator extends Validator {
isValid(tag: string, _): boolean {
return this.getFeedback(tag, _) === undefined
}
}

View file

@ -1,11 +1,15 @@
import { Validator } from "../Validator"
import TimeInput from "../Helpers/TimeInput.svelte"
export class TimeValidator extends Validator {
inputmode = "time"
public readonly inputmode = "time"
public readonly inputHelper = TimeInput
public readonly hideInputField = true
constructor() {
super("time", "A time picker")
}
}

View file

@ -1,7 +1,10 @@
import { Validator } from "../Validator"
import TranslationInput from "../Helpers/TranslationInput.svelte"
export default class TranslationValidator extends Validator {
public readonly inputHelper = TranslationInput
public readonly isMeta = true
public readonly hideInputField = true
constructor() {
super("translation", "Makes sure the the string is of format `Record<string, string>` ")
}
@ -14,4 +17,5 @@ export default class TranslationValidator extends Validator {
return false
}
}
}

View file

@ -3,9 +3,11 @@ import { Validator } from "../Validator"
import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations"
import MarkdownUtils from "../../../Utils/MarkdownUtils"
import WikidataInputHelper from "../Helpers/WikidataInputHelper.svelte"
export default class WikidataValidator extends Validator {
public static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>()
public readonly inputHelper = WikidataInputHelper
private static docs = [
"#### Helper arguments",
@ -104,6 +106,10 @@ Another example is to search for species and trees:
if (str === undefined) {
return false
}
if (str.length === 0) {
// Don't show an exclamation mark if empty, the other code will prevent saving
return true
}
if (str.length == 1) {
return false
}
@ -182,4 +188,5 @@ Another example is to search for species and trees:
}
return clipped
}
}

View file

@ -8,8 +8,8 @@
import InputHelper from "../../InputElement/InputHelper.svelte"
import type { Feature } from "geojson"
import { Unit } from "../../../Models/Unit"
import InputHelpers from "../../InputElement/InputHelpers"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Validators from "../../InputElement/Validators"
export let value: UIEventSource<string>
export let unvalidatedText: UIEventSource<string> = new UIEventSource<string>(value.data)
@ -29,11 +29,12 @@
}
const dispatch = createEventDispatcher<{ selected }>()
let hideInput = Validators.get(config.freeform.type).hideInputField
export let feedback: UIEventSource<Translation>
onDestroy(
value.addCallbackD(() => {
dispatch("selected")
})
}),
)
function getCountry() {
@ -44,6 +45,16 @@
<div class="inline-flex w-full flex-col">
{#if inline}
<Inline key={config.freeform.key} {tags} template={config.render}>
{#if hideInput}
<InputHelper
args={config.freeform.args}
{feature}
type={config.freeform.type}
{value}
{state}
on:submit
/>
{:else}
<ValidatedInput
{feedback}
{getCountry}
@ -55,8 +66,9 @@
{value}
range={config.freeform.range}
/>
{/if}
</Inline>
{:else if InputHelpers.hideInputField.indexOf(config.freeform.type) < 0}
{:else if !hideInput}
<ValidatedInput
{feedback}
{getCountry}
@ -71,6 +83,7 @@
/>
{/if}
{#if !(inline && hideInput)}
<InputHelper
args={config.freeform.args}
{feature}
@ -79,4 +92,5 @@
{state}
on:submit
/>
{/if}
</div>

View file

@ -1,4 +1,4 @@
<script>
export let color = "#000000"
</script>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> <g id="surface1"> <path style="fill: none !important;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(97.254902%,100%,96.078432%);stroke-opacity:1;stroke-miterlimit:4;" d="M -177.48351 -16.993714 C -177.484166 101.48875 -226.288922 214.739751 -312.411923 296.10684 C -398.528411 377.467771 -514.363074 419.770216 -632.651731 413.060164 " transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)"/> <path style="fill: none !important;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 743.478328 134.561833 L 430.253662 430.253662 L 117.002105 134.508051 " transform="matrix(0.435789,0,0,0.435789,0,0)"/> <path style="fill: none !important;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke:{color};stroke-opacity:1;stroke-miterlimit:4;" d="M -177.48351 -16.993714 C -177.484166 101.48875 -226.288922 214.739751 -312.411923 296.10684 C -398.528411 377.467771 -514.363074 419.770216 -632.651731 413.060164 " transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)"/> <path style="fill: none !important;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:{color};stroke-opacity:1;stroke-miterlimit:4;" d="M 743.478328 134.561833 L 430.253662 430.253662 L 117.002105 134.508051 " transform="matrix(0.435789,0,0,0.435789,0,0)"/> </g> </svg>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus width="375px" height="375px" viewBox="0 0 375 375" version="1.1" id="svg4" sodipodi:docname="direction_stroke.svg" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs id="defs4" /> <sodipodi:namedview id="namedview4" pagecolor="#ffffff" bordercolor="#999999" borderopacity="1" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:zoom="0.87158493" inkscape:cx="-23.52037" inkscape:cy="19.504697" inkscape:window-width="1920" inkscape:window-height="1005" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> <g id="surface1" style="fill:{color};fill-opacity:1;stroke:#ffffff;stroke-width:3;stroke-opacity:1" transform="translate(0,4)"> <path style="color:{color};fill:{color};fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;-inkscape-stroke:none" d="m -177.48437,-20.435547 a 3.4420288,3.4420288 0 0 0 -3.44141,3.441406 c -6.5e-4,117.537141 -48.41377,229.881731 -133.84961,310.599611 -85.42934,80.71172 -200.33664,122.6741 -317.68164,116.01758 a 3.4420288,3.4420288 0 0 0 -3.63086,3.24218 3.4420288,3.4420288 0 0 0 3.24219,3.63086 c 119.23207,6.76357 235.9934,-35.87674 322.79687,-117.88672 86.80999,-82.01614 136.00715,-196.17596 136.00781,-315.603511 a 3.4420288,3.4420288 0 0 0 -3.44335,-3.441406 z" id="path1" transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)" /> <path style="color:{color};fill:{color};fill-opacity:1;stroke:#ffffff;stroke-opacity:1;-inkscape-stroke:none" d="m 119.36523,132.00586 -4.72656,5.00586 315.61524,297.97461 315.58789,-297.92188 -4.72657,-5.00586 -310.86132,293.46094 z" id="path2" transform="scale(0.435789)" /> <path style="color:{color};fill:{color};fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;-inkscape-stroke:none" d="m -177.48437,-20.435547 a 3.4420288,3.4420288 0 0 0 -3.44141,3.441406 c -6.5e-4,117.537141 -48.41377,229.881731 -133.84961,310.599611 -85.42934,80.71172 -200.33664,122.6741 -317.68164,116.01758 a 3.4420288,3.4420288 0 0 0 -3.63086,3.24218 3.4420288,3.4420288 0 0 0 3.24219,3.63086 c 119.23207,6.76357 235.9934,-35.87674 322.79687,-117.88672 86.80999,-82.01614 136.00715,-196.17596 136.00781,-315.603511 a 3.4420288,3.4420288 0 0 0 -3.44335,-3.441406 z" id="path3" transform="matrix(-0.316636,-0.299423,0.299423,-0.316636,0,0)" /> <path style="color:{color};fill:{color};fill-opacity:1;stroke:#ffffff;stroke-opacity:1;-inkscape-stroke:none" d="m 119.36523,132.00586 -4.72656,5.00586 315.61524,297.97461 315.58789,-297.92188 -4.72657,-5.00586 -310.86132,293.46094 z" id="path4" transform="scale(0.435789)" /> </g> </svg>