forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
2001ef09bf
50 changed files with 1443 additions and 1341 deletions
|
@ -6,22 +6,22 @@ Hi! Thanks for checking out how to contribute to MapComplete!
|
||||||
There are multiple ways to contribute:
|
There are multiple ways to contribute:
|
||||||
|
|
||||||
- Translating MapComplete to your own language can be done
|
- Translating MapComplete to your own language can be done
|
||||||
on [this website](https://hosted.weblate.org/projects/mapcomplete/)
|
on [the Weblate website](https://hosted.weblate.org/projects/mapcomplete/)
|
||||||
- If you encounter a bug, the [issue tracker](https://github.com/pietervdvn/MapComplete/issues) is the place to be
|
- If you encounter a bug, the [issue tracker](https://github.com/pietervdvn/MapComplete/issues) is the place to be
|
||||||
- A good start to contribute is to create a single map layer showing features which interest you. Read more about [making your own theme](/Docs/Making_Your_Own_Theme.md).
|
- A good start to contribute is to create a single map layer showing features which interest you. Read more about [making your own theme](/Docs/Making_Your_Own_Theme.md).
|
||||||
- If you want to improve a theme, create a new theme, spot a typo in the repo... the best way is to open a pull request.
|
- If you want to improve a theme, create a new theme, spot a typo in the repo... the best way is to open a pull request.
|
||||||
|
|
||||||
People who stick around and contribute in a meaningful way, _might_ be granted write access to the repository (except . This is
|
People who stick around and contribute in a meaningful way, _might_ be granted write access to the repository (except the branches *master* and *develop*). This is
|
||||||
done on a purely subjective basis, e.g. after a few pull requests and if you are a member of the OSM community.
|
done on a purely subjective basis, e.g. after a few pull requests and if you are a member of the OSM community.
|
||||||
|
|
||||||
Rights of contributors
|
Rights of contributors
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
If you have write access to the repository, you can make a fork of an already existing branch and push this new branch
|
If you have write access to the repository, you can make a fork of an already existing branch and push this new branch
|
||||||
to github. This means that this branch will be _automatically built_ and be **deployed**
|
to GitHub. This means that this branch will be _automatically built_ and be **deployed**
|
||||||
to `https://pietervdvn.github.io/mc/<branchname>`. You can see the deploy process
|
to `https://pietervdvn.github.io/mc/<branchname>`. You can see the deploy process
|
||||||
on [Github Actions](https://github.com/pietervdvn/MapComplete/actions). Don't worry about pushing too much. These
|
on [GitHub Actions](https://github.com/pietervdvn/MapComplete/actions). Don't worry about pushing too much. These
|
||||||
deploys are free and totally automatic. They might fail if something is wrong, but this will hinder no-one.
|
deploys are free and totally automatic. They might fail if something is wrong, but this will hinder no one.
|
||||||
|
|
||||||
Additionaly, some other maintainer might step in and merge the latest develop with your branch, making later pull
|
Additionaly, some other maintainer might step in and merge the latest develop with your branch, making later pull
|
||||||
requests easier.
|
requests easier.
|
||||||
|
@ -58,6 +58,6 @@ again to start fresh.
|
||||||
What not to contribute
|
What not to contribute
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
I'm currently _not_ accepting files for integration with some editor. There are hundreds of editors out there, if every single one of them needs a file in the repo, this ends up as a mess.
|
I'm currently _not_ accepting files for integration with some text editor. There are hundreds of editors out there, if every single one of them needs a file in the repo, this ends up as a mess.
|
||||||
Furthermore, MapComplete doesn't want to encourage or discourage some editors.
|
Furthermore, MapComplete doesn't want to encourage or discourage some text editors.
|
||||||
At last, these files are hard to maintain and are hard to detect if they have fallen out of use.
|
At last, these files are hard to maintain and are hard to detect if they have fallen out of use.
|
||||||
|
|
53
assets/layers/guidepost/guidepost.json
Normal file
53
assets/layers/guidepost/guidepost.json
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"id": "guidepost",
|
||||||
|
"name": {
|
||||||
|
"en": "Guideposts"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"en": "Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"osmTags": "information=guidepost"
|
||||||
|
},
|
||||||
|
"minzoom": 14,
|
||||||
|
"presets": [
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"en": "a guidepost"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"information=guidepost",
|
||||||
|
"tourism=information"
|
||||||
|
],
|
||||||
|
"description": {
|
||||||
|
"en": "A guidepost (also known as fingerpost) is often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
|
||||||
|
},
|
||||||
|
"exampleImages": [
|
||||||
|
"./assets/layers/guidepost/guidepost_example.jpg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deletion": true,
|
||||||
|
"allowMove": {
|
||||||
|
"enableImproveAccuracy": "true",
|
||||||
|
"enableRelocation": "false"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"render": {
|
||||||
|
"en": "Guidepost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mapRendering": [
|
||||||
|
{
|
||||||
|
"location": [
|
||||||
|
"centroid",
|
||||||
|
"point"
|
||||||
|
],
|
||||||
|
"icon": "./assets/layers/guidepost/guidepost.svg",
|
||||||
|
"anchor": "bottom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tagRenderings": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
}
|
61
assets/layers/guidepost/guidepost.svg
Normal file
61
assets/layers/guidepost/guidepost.svg
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
width="500"
|
||||||
|
height="500"
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:docname="guidepost.svg"
|
||||||
|
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||||
|
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"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.4932029"
|
||||||
|
inkscape:cx="111.51597"
|
||||||
|
inkscape:cy="276.76236"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="995"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<metadata
|
||||||
|
id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs6" />
|
||||||
|
<rect
|
||||||
|
width="499.41919"
|
||||||
|
height="499.41919"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
id="canvas"
|
||||||
|
style="visibility:hidden;fill:none;stroke:none;stroke-width:35.6728" />
|
||||||
|
<path
|
||||||
|
d="m 249.7096,8.9181999 c -13.3773,0 -26.7546,8.9182001 -26.7546,26.7546001 v 463.74639 h 53.5092 V 35.6728 c 0,-17.8364 -13.3773,-26.7546001 -26.7546,-26.7546001 z M 71.345599,35.6728 0,89.181999 71.345599,142.6912 H 214.0368 V 35.6728 Z M 285.3824,142.6912 v 107.0184 h 142.6912 l 71.34559,-53.5092 -71.34559,-53.5092 z"
|
||||||
|
id="guidepost"
|
||||||
|
style="fill:#555555;fill-opacity:1;stroke:none;stroke-width:35.6728" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2 KiB |
2
assets/layers/guidepost/guidepost.svg.license
Normal file
2
assets/layers/guidepost/guidepost.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: OSM Carto
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
BIN
assets/layers/guidepost/guidepost_example.jpg
Normal file
BIN
assets/layers/guidepost/guidepost_example.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
2
assets/layers/guidepost/guidepost_example.jpg.license
Normal file
2
assets/layers/guidepost/guidepost_example.jpg.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: Mschaeuble
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
22
assets/layers/guidepost/license_info.json
Normal file
22
assets/layers/guidepost/license_info.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"path": "guidepost.svg",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"authors": [
|
||||||
|
"OSM Carto"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"https://wiki.openstreetmap.org/wiki/File:Guidepost-14.svg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "guidepost_example.jpg",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"authors": [
|
||||||
|
"Mschaeuble"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"https://wiki.openstreetmap.org/wiki/File:Signpost.jpg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
2
assets/layers/guidepost/signpost_example.jpg.license
Normal file
2
assets/layers/guidepost/signpost_example.jpg.license
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: Mschaeuble
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
|
@ -20,7 +20,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"title": {
|
"title": {
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Ticket Machine",
|
"en": "Ticket Machine",
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": "amenity=ticket_validator"
|
"osmTags": "amenity=ticket_validator"
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"title": {
|
"title": {
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Ticket Validator",
|
"en": "Ticket Validator",
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
"osmTags": "building~*",
|
"osmTags": "building~*",
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"calculatedTags": [
|
"calculatedTags": [
|
||||||
"_surface:strict:=feat(get)('_surface')"
|
"_surface:strict:=feat(get)('_surface')"
|
||||||
],
|
],
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
},
|
},
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"label": {
|
"label": {
|
||||||
|
@ -194,7 +194,7 @@
|
||||||
"osmTags": "identificatie~*",
|
"osmTags": "identificatie~*",
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"calculatedTags": [
|
"calculatedTags": [
|
||||||
"_overlaps_with_buildings=overlapWith(feat)('osm:buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
|
"_overlaps_with_buildings=overlapWith(feat)('osm:buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
|
||||||
"_overlaps_with=feat(get)('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )",
|
"_overlaps_with=feat(get)('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )",
|
||||||
|
@ -379,7 +379,7 @@
|
||||||
"osmTags": "identificatie~*",
|
"osmTags": "identificatie~*",
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"calculatedTags": [
|
"calculatedTags": [
|
||||||
"_closed_osm_addr:=closest(feat)('osm:adresses').properties",
|
"_closed_osm_addr:=closest(feat)('osm:adresses').properties",
|
||||||
"_bag_obj:addr:housenumber=`${feat.properties.huisnummer}${feat.properties.huisletter}${(feat.properties.toevoeging != '') ? '-' : ''}${feat.properties.toevoeging}`",
|
"_bag_obj:addr:housenumber=`${feat.properties.huisnummer}${feat.properties.huisletter}${(feat.properties.toevoeging != '') ? '-' : ''}${feat.properties.toevoeging}`",
|
||||||
|
@ -427,4 +427,4 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hideFromOverview": true
|
"hideFromOverview": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "kerbs",
|
"builtin": "kerbs",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"iconBadges": [
|
"iconBadges": [
|
||||||
|
@ -112,4 +112,4 @@
|
||||||
},
|
},
|
||||||
"stairs"
|
"stairs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -464,10 +464,14 @@
|
||||||
{
|
{
|
||||||
"builtin": [
|
"builtin": [
|
||||||
"toilet",
|
"toilet",
|
||||||
"drinking_water"
|
"drinking_water",
|
||||||
|
"guidepost"
|
||||||
],
|
],
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 15
|
"minzoom": 15,
|
||||||
|
"mapRendering": [{
|
||||||
|
"iconSize": "30,30"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
14
assets/themes/guideposts/guideposts.json
Normal file
14
assets/themes/guideposts/guideposts.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"id": "guideposts",
|
||||||
|
"title": {
|
||||||
|
"en": "Guideposts"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"en": "Guideposts (also known as fingerposts or finger posts) are often found along official hiking, cycling, skiing or horseback riding routes to indicate the directions to different destinations. Additionally, they are often named after a region or place and show the altitude.\n\nThe position of a signpost can be used by a hiker/biker/rider/skier as a confirmation of the current position, especially if they use a printed map without a GPS receiver. "
|
||||||
|
},
|
||||||
|
"icon": "./assets/layers/guidepost/guidepost.svg",
|
||||||
|
"startZoom": 2,
|
||||||
|
"layers": [
|
||||||
|
"guidepost"
|
||||||
|
]
|
||||||
|
}
|
|
@ -111,8 +111,8 @@
|
||||||
"=presets": [],
|
"=presets": [],
|
||||||
"=name": null,
|
"=name": null,
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19
|
"minzoom": 18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "indoors",
|
"builtin": "indoors",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"name": null,
|
"name": null,
|
||||||
"passAllFeatures": true
|
"passAllFeatures": true
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"name": null,
|
"name": null,
|
||||||
"tagRendering": null,
|
"tagRendering": null,
|
||||||
"title": "null",
|
"title": "null",
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"shownByDefault": false
|
"shownByDefault": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "entrance",
|
"builtin": "entrance",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"icon": "circle:white;./assets/themes/onwheels/entrance.svg"
|
"icon": "circle:white;./assets/themes/onwheels/entrance.svg"
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "kerbs",
|
"builtin": "kerbs",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"syncSelection": "theme-only",
|
"syncSelection": "theme-only",
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "toilet",
|
"builtin": "toilet",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"syncSelection": "theme-only",
|
"syncSelection": "theme-only",
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
|
@ -349,7 +349,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "reception_desk",
|
"builtin": "reception_desk",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"syncSelection": "theme-only"
|
"syncSelection": "theme-only"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -357,7 +357,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "elevator",
|
"builtin": "elevator",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"syncSelection": "theme-only",
|
"syncSelection": "theme-only",
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
|
@ -524,4 +524,4 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"enableDownload": true
|
"enableDownload": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "food",
|
"builtin": "food",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"filter": null,
|
"filter": null,
|
||||||
"name": null
|
"name": null
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "shops",
|
"builtin": "shops",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"filter": null,
|
"filter": null,
|
||||||
"presets": [
|
"presets": [
|
||||||
{
|
{
|
||||||
|
@ -220,4 +220,4 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"credits": "Niels Elgaard Larsen"
|
"credits": "Niels Elgaard Larsen"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "indoors",
|
"builtin": "indoors",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"passAllFeatures": true,
|
"passAllFeatures": true,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{},
|
{},
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "stairs",
|
"builtin": "stairs",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19
|
"minzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19
|
"minzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"icon": "circle:white;./assets/themes/stations/bicycle_parking.svg"
|
"icon": "circle:white;./assets/themes/stations/bicycle_parking.svg"
|
||||||
|
@ -161,7 +161,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"icon": "circle:white;./assets/themes/stations/rental_bicycle.svg"
|
"icon": "circle:white;./assets/themes/stations/rental_bicycle.svg"
|
||||||
|
@ -179,7 +179,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19
|
"minzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering+": [
|
"mapRendering+": [
|
||||||
{
|
{
|
||||||
"color": "#00f",
|
"color": "#00f",
|
||||||
|
@ -214,7 +214,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"presets": null,
|
"presets": null,
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering+": [
|
"mapRendering+": [
|
||||||
{
|
{
|
||||||
"color": "yellow",
|
"color": "yellow",
|
||||||
|
@ -235,13 +235,13 @@
|
||||||
"clock"
|
"clock"
|
||||||
],
|
],
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19
|
"minzoom": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"builtin": "bench",
|
"builtin": "bench",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"icon": "./assets/themes/stations/bench.svg"
|
"icon": "./assets/themes/stations/bench.svg"
|
||||||
|
@ -252,7 +252,7 @@
|
||||||
{
|
{
|
||||||
"builtin": "drinking_water",
|
"builtin": "drinking_water",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"icon": "circle:white;./assets/themes/stations/drinking_water.svg"
|
"icon": "circle:white;./assets/themes/stations/drinking_water.svg"
|
||||||
|
@ -293,7 +293,7 @@
|
||||||
"zh_Hant": "時刻表"
|
"zh_Hant": "時刻表"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"and": [
|
"and": [
|
||||||
|
@ -412,4 +412,4 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,7 +221,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"title": {
|
"title": {
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Street",
|
"en": "Street",
|
||||||
|
@ -361,4 +361,4 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"credits": "Robin van der Linde"
|
"credits": "Robin van der Linde"
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,22 +36,19 @@
|
||||||
{
|
{
|
||||||
"builtin": "bike_parking",
|
"builtin": "bike_parking",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18
|
||||||
"minzoomVisible": 19
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"builtin": "parking",
|
"builtin": "parking",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18
|
||||||
"minzoomVisible": 19
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"builtin": "shelter",
|
"builtin": "shelter",
|
||||||
"override": {
|
"override": {
|
||||||
"minzoom": 19,
|
"minzoom": 18,
|
||||||
"minzoomVisible": 19,
|
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"and": [
|
"and": [
|
||||||
|
@ -67,4 +64,4 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"credits": "Robin van der Linde"
|
"credits": "Robin van der Linde"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
51.190748429411705
|
51.190748429411705
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"defaultBackgroundId": "CartoDB.DarkMatterNoLabels",
|
"defaultBackgroundId": "alidade.smooth_dark",
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"id": "street_with_width",
|
"id": "street_with_width",
|
||||||
|
@ -271,4 +271,4 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
"pleaseLogin": "Please log in to add a new feature",
|
"pleaseLogin": "Please log in to add a new feature",
|
||||||
"presetInfo": "The new POI will have {tags}",
|
"presetInfo": "The new POI will have {tags}",
|
||||||
"stillLoading": "The data is still loading. Please wait a bit before you add a new feature.",
|
"stillLoading": "The data is still loading. Please wait a bit before you add a new feature.",
|
||||||
"title": "Add a new feature?",
|
"title": "Add a new feature",
|
||||||
"warnVisibleForEveryone": "Your addition will be visible for everyone",
|
"warnVisibleForEveryone": "Your addition will be visible for everyone",
|
||||||
"wrongType": "This feature is not a node or a way and can not be imported",
|
"wrongType": "This feature is not a node or a way and can not be imported",
|
||||||
"zoomInFurther": "Zoom in further to add a feature.",
|
"zoomInFurther": "Zoom in further to add a feature.",
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
"pleaseLogin": "Gelieve je aan te melden om een object toe te voegen",
|
"pleaseLogin": "Gelieve je aan te melden om een object toe te voegen",
|
||||||
"presetInfo": "Het nieuwe object krijgt de attributen {tags}",
|
"presetInfo": "Het nieuwe object krijgt de attributen {tags}",
|
||||||
"stillLoading": "De data worden nog geladen. Nog even geduld en dan kan je een object toevoegen.",
|
"stillLoading": "De data worden nog geladen. Nog even geduld en dan kan je een object toevoegen.",
|
||||||
"title": "Nieuw object toevoegen?",
|
"title": "Nieuw object toevoegen",
|
||||||
"warnVisibleForEveryone": "Je toevoeging is voor iedereen zichtbaar",
|
"warnVisibleForEveryone": "Je toevoeging is voor iedereen zichtbaar",
|
||||||
"wrongType": "Dit object is geen punt of lijn en kan daarom niet geïmporteerd worden",
|
"wrongType": "Dit object is geen punt of lijn en kan daarom niet geïmporteerd worden",
|
||||||
"zoomInFurther": "Gelieve verder in te zoomen om een object toe te voegen.",
|
"zoomInFurther": "Gelieve verder in te zoomen om een object toe te voegen.",
|
||||||
|
|
12
package-lock.json
generated
12
package-lock.json
generated
|
@ -9612,9 +9612,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.26",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
"integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==",
|
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -20529,9 +20529,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.4.26",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
"integrity": "sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==",
|
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.33.7",
|
"version": "0.33.8",
|
||||||
"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",
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run generate:layeroverview && npm run strt",
|
"start": "npm run generate:layeroverview && npm run strt",
|
||||||
"strt": "vite --host",
|
"strt": "vite --host | sed 's/localhost:/127.0.0.1:/g'",
|
||||||
"strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html assets/templates/*.svg assets/templates/fonts/*.ttf",
|
"strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html assets/templates/*.svg assets/templates/fonts/*.ttf",
|
||||||
"watch:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css --watch",
|
"watch:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css --watch",
|
||||||
"generate:css": "tailwindcss -i src/index.css -o public/css/index-tailwind-output.css",
|
"generate:css": "tailwindcss -i src/index.css -o public/css/index-tailwind-output.css",
|
||||||
|
|
|
@ -8,8 +8,7 @@ rm -rf dist/*
|
||||||
rm -rf .cache
|
rm -rf .cache
|
||||||
mkdir dist 2> /dev/null
|
mkdir dist 2> /dev/null
|
||||||
mkdir dist/assets 2> /dev/null
|
mkdir dist/assets 2> /dev/null
|
||||||
mkdir dist/assets/langs 2> /dev/null
|
|
||||||
mkdir dist/assets/langs/layers 2> /dev/null
|
|
||||||
|
|
||||||
export NODE_OPTIONS="--max-old-space-size=8192"
|
export NODE_OPTIONS="--max-old-space-size=8192"
|
||||||
|
|
||||||
|
@ -48,11 +47,12 @@ fi
|
||||||
|
|
||||||
export NODE_OPTIONS=--max-old-space-size=7000
|
export NODE_OPTIONS=--max-old-space-size=7000
|
||||||
vite build $SRC_MAPS
|
vite build $SRC_MAPS
|
||||||
|
|
||||||
|
|
||||||
# Copy the layer files, as these might contain assets (e.g. svgs)
|
# Copy the layer files, as these might contain assets (e.g. svgs)
|
||||||
cp -r assets/layers/ dist/assets/layers/
|
cp -r assets/layers/ dist/assets/layers/
|
||||||
cp -r assets/themes/ dist/assets/themes/
|
cp -r assets/themes/ dist/assets/themes/
|
||||||
cp -r assets/svg/ dist/assets/svg/
|
cp -r assets/svg/ dist/assets/svg/
|
||||||
cp -r langs/layers/ dist/assets/langs/layers/
|
mkdir dist/assets/langs
|
||||||
|
mkdir dist/assets/langs/layers
|
||||||
|
cp -r langs/layers/ dist/assets/langs/
|
||||||
|
ls dist/assets/langs/layers/
|
||||||
export NODE_OPTIONS=""
|
export NODE_OPTIONS=""
|
||||||
|
|
|
@ -61,9 +61,9 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P
|
||||||
if (!layout.icon.endsWith(".svg")) {
|
if (!layout.icon.endsWith(".svg")) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Not creating a social image for " +
|
"Not creating a social image for " +
|
||||||
layout.id +
|
layout.id +
|
||||||
" as it is _not_ a .svg: " +
|
" as it is _not_ a .svg: " +
|
||||||
layout.icon
|
layout.icon,
|
||||||
)
|
)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P
|
||||||
delete svg["defs"]
|
delete svg["defs"]
|
||||||
delete svg["$"]
|
delete svg["$"]
|
||||||
let templateSvg = await ScriptUtils.ReadSvg(
|
let templateSvg = await ScriptUtils.ReadSvg(
|
||||||
"./public/assets/SocialImageTemplate" + template + ".svg"
|
"./public/assets/SocialImageTemplate" + template + ".svg",
|
||||||
)
|
)
|
||||||
templateSvg = Utils.WalkJson(
|
templateSvg = Utils.WalkJson(
|
||||||
templateSvg,
|
templateSvg,
|
||||||
|
@ -104,7 +104,7 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return mightBeTokenToReplace.circle[0]?.$?.style?.indexOf("fill:#ff00ff") >= 0
|
return mightBeTokenToReplace.circle[0]?.$?.style?.indexOf("fill:#ff00ff") >= 0
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const builder = new xml2js.Builder()
|
const builder = new xml2js.Builder()
|
||||||
|
@ -116,7 +116,7 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P
|
||||||
|
|
||||||
async function createManifest(
|
async function createManifest(
|
||||||
layout: LayoutConfig,
|
layout: LayoutConfig,
|
||||||
alreadyWritten: string[]
|
alreadyWritten: string[],
|
||||||
): Promise<{
|
): Promise<{
|
||||||
manifest: any
|
manifest: any
|
||||||
whiteIcons: string[]
|
whiteIcons: string[]
|
||||||
|
@ -210,19 +210,17 @@ function asLangSpan(t: Translation, tag = "span"): string {
|
||||||
let previousSrc: Set<string> = new Set<string>()
|
let previousSrc: Set<string> = new Set<string>()
|
||||||
|
|
||||||
let eliUrlsCached: string[]
|
let eliUrlsCached: string[]
|
||||||
function eliUrls(): string[] {
|
|
||||||
|
async function eliUrls(): Promise<string[]> {
|
||||||
if (eliUrlsCached) {
|
if (eliUrlsCached) {
|
||||||
return eliUrlsCached
|
return eliUrlsCached
|
||||||
}
|
}
|
||||||
const urls: string[] = []
|
const urls: string[] = []
|
||||||
const regex = /{switch:([^}]+)}/
|
const regex = /{switch:([^}]+)}/
|
||||||
const rasterLayers = [
|
const rasterLayers = [AvailableRasterLayers.maptilerDefaultLayer, ...eli.features, ...eli_global.layers.map(properties => ({ properties }))]
|
||||||
...AvailableRasterLayers.vectorLayers,
|
|
||||||
...eli.features,
|
|
||||||
...eli_global.layers.map((properties) => ({ properties })),
|
|
||||||
]
|
|
||||||
for (const feature of rasterLayers) {
|
for (const feature of rasterLayers) {
|
||||||
const url = (<RasterLayerPolygon>feature).properties.url
|
const f = <RasterLayerPolygon>feature
|
||||||
|
const url = f.properties.url
|
||||||
const match = url.match(regex)
|
const match = url.match(regex)
|
||||||
if (match) {
|
if (match) {
|
||||||
const domains = match[1].split(",")
|
const domains = match[1].split(",")
|
||||||
|
@ -231,17 +229,43 @@ function eliUrls(): string[] {
|
||||||
} else {
|
} else {
|
||||||
urls.push(url)
|
urls.push(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (f.properties.type === "vector") {
|
||||||
|
// We also need to whitelist eventual sources
|
||||||
|
const styleSpec = await Utils.downloadJsonCached(f.properties.url, 1000 * 120)
|
||||||
|
for (const key of Object.keys(styleSpec.sources)) {
|
||||||
|
const url = styleSpec.sources[key].url
|
||||||
|
if(!url){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let urlClipped = url
|
||||||
|
if(url.indexOf("?") > 0){
|
||||||
|
urlClipped = url?.substring(0, url.indexOf("?"))
|
||||||
|
}
|
||||||
|
console.log("Source url ",key,url)
|
||||||
|
urls.push(url)
|
||||||
|
if(urlClipped.endsWith(".json")){
|
||||||
|
const tileInfo = await Utils.downloadJsonCached(url, 1000*120)
|
||||||
|
urls.push(tileInfo["tiles"] ?? [])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
urls.push(...(styleSpec["tiles"] ?? []))
|
||||||
|
urls.push(styleSpec["sprite"])
|
||||||
|
urls.push(styleSpec["glyphs"])
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
eliUrlsCached = urls
|
eliUrlsCached = urls
|
||||||
return urls
|
return Utils.NoNull(urls).sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCsp(
|
async function generateCsp(
|
||||||
layout: LayoutConfig,
|
layout: LayoutConfig,
|
||||||
options: {
|
options: {
|
||||||
scriptSrcs: string[]
|
scriptSrcs: string[]
|
||||||
}
|
},
|
||||||
): string {
|
): Promise<string> {
|
||||||
const apiUrls: string[] = [
|
const apiUrls: string[] = [
|
||||||
"'self'",
|
"'self'",
|
||||||
...Constants.defaultOverpassUrls,
|
...Constants.defaultOverpassUrls,
|
||||||
|
@ -251,12 +275,12 @@ function generateCsp(
|
||||||
"https://pietervdvn.goatcounter.com",
|
"https://pietervdvn.goatcounter.com",
|
||||||
]
|
]
|
||||||
.concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls))
|
.concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls))
|
||||||
.concat(...eliUrls())
|
.concat(...await eliUrls())
|
||||||
|
|
||||||
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
||||||
const hosts = new Set<string>()
|
const hosts = new Set<string>()
|
||||||
const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt(
|
const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt(
|
||||||
new ImmutableStore({ lon: 0, lat: 0 })
|
new ImmutableStore({ lon: 0, lat: 0 }),
|
||||||
).data
|
).data
|
||||||
const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector")
|
const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector")
|
||||||
const vectorSources = vectorLayers.map((l) => l.properties.url)
|
const vectorSources = vectorLayers.map((l) => l.properties.url)
|
||||||
|
@ -283,14 +307,14 @@ function generateCsp(
|
||||||
"connect-src items for theme",
|
"connect-src items for theme",
|
||||||
layout.id,
|
layout.id,
|
||||||
"(extra sources: ",
|
"(extra sources: ",
|
||||||
newSrcs.join(" ") + ")"
|
newSrcs.join(" ") + ")",
|
||||||
)
|
)
|
||||||
previousSrc = hosts
|
previousSrc = hosts
|
||||||
|
|
||||||
const csp: Record<string, string> = {
|
const csp: Record<string, string> = {
|
||||||
"default-src": "'self'",
|
"default-src": "'self'",
|
||||||
"script-src": ["'self'", "https://gc.zgo.at/count.js", ...(options?.scriptSrcs ?? [])].join(
|
"script-src": ["'self'", "https://gc.zgo.at/count.js", ...(options?.scriptSrcs ?? [])].join(
|
||||||
" "
|
" ",
|
||||||
),
|
),
|
||||||
"img-src": "* data:", // maplibre depends on 'data:' to load
|
"img-src": "* data:", // maplibre depends on 'data:' to load
|
||||||
"connect-src": connectSrc.join(" "),
|
"connect-src": connectSrc.join(" "),
|
||||||
|
@ -320,12 +344,12 @@ const removeOtherLanguagesHash = crypto
|
||||||
async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) {
|
async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) {
|
||||||
Locale.language.setData(layout.language[0])
|
Locale.language.setData(layout.language[0])
|
||||||
const targetLanguage = layout.language[0]
|
const targetLanguage = layout.language[0]
|
||||||
const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"')
|
const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, "\\\"")
|
||||||
const ogDescr = Translations.T(
|
const ogDescr = Translations.T(
|
||||||
layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap"
|
layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap",
|
||||||
)
|
)
|
||||||
.textFor(targetLanguage)
|
.textFor(targetLanguage)
|
||||||
.replace(/"/g, '\\"')
|
.replace(/"/g, "\\\"")
|
||||||
let ogImage = layout.socialImage
|
let ogImage = layout.socialImage
|
||||||
let twitterImage = ogImage
|
let twitterImage = ogImage
|
||||||
if (ogImage === LayoutConfig.defaultSocialImage && layout.official) {
|
if (ogImage === LayoutConfig.defaultSocialImage && layout.official) {
|
||||||
|
@ -390,34 +414,34 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
|
||||||
const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title })
|
const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title })
|
||||||
const templateLines = template.split("\n")
|
const templateLines = template.split("\n")
|
||||||
const removeOtherLanguagesReference = templateLines.find(
|
const removeOtherLanguagesReference = templateLines.find(
|
||||||
(line) => line.indexOf("./src/UI/RemoveOtherLanguages.js") >= 0
|
(line) => line.indexOf("./src/UI/RemoveOtherLanguages.js") >= 0,
|
||||||
)
|
)
|
||||||
let output = template
|
let output = template
|
||||||
.replace("Loading MapComplete, hang on...", asLangSpan(loadingText, "h1"))
|
.replace("Loading MapComplete, hang on...", asLangSpan(loadingText, "h1"))
|
||||||
.replace(
|
.replace(
|
||||||
"Made with OpenStreetMap",
|
"Made with OpenStreetMap",
|
||||||
Translations.t.general.poweredByOsm.textFor(targetLanguage)
|
Translations.t.general.poweredByOsm.textFor(targetLanguage),
|
||||||
)
|
)
|
||||||
.replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
|
.replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
|
||||||
.replace(
|
.replace(
|
||||||
/<!-- CSP -->/,
|
/<!-- CSP -->/,
|
||||||
generateCsp(layout, {
|
await generateCsp(layout, {
|
||||||
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
|
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.replace(removeOtherLanguagesReference, "<script>" + removeOtherLanguages + "</script>")
|
.replace(removeOtherLanguagesReference, "<script>" + removeOtherLanguages + "</script>")
|
||||||
.replace(
|
.replace(
|
||||||
/<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s,
|
/<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s,
|
||||||
asLangSpan(layout.shortDescription)
|
asLangSpan(layout.shortDescription),
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
/<!-- IMAGE-START -->.*<!-- IMAGE-END -->/s,
|
/<!-- IMAGE-START -->.*<!-- IMAGE-END -->/s,
|
||||||
"<img class='p-8 h-32 w-32 self-start' src='" + icon + "' />"
|
"<img class='p-8 h-32 w-32 self-start' src='" + icon + "' />",
|
||||||
)
|
)
|
||||||
|
|
||||||
.replace(
|
.replace(
|
||||||
/.*\/src\/index\.ts.*/,
|
/.*\/src\/index\.ts.*/,
|
||||||
`<script type="module" src="./index_${layout.id}.ts"></script>`
|
`<script type="module" src="./index_${layout.id}.ts"></script>`,
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
@ -508,13 +532,14 @@ async function main(): Promise<void> {
|
||||||
title: { en: "MapComplete" },
|
title: { en: "MapComplete" },
|
||||||
description: { en: "A thematic map viewer and editor based on OpenStreetMap" },
|
description: { en: "A thematic map viewer and editor based on OpenStreetMap" },
|
||||||
}),
|
}),
|
||||||
alreadyWritten
|
alreadyWritten,
|
||||||
)
|
)
|
||||||
|
|
||||||
const manif = JSON.stringify(manifest, undefined, 2)
|
const manif = JSON.stringify(manifest, undefined, 2)
|
||||||
writeFileSync("public/index.webmanifest", manif)
|
writeFileSync("public/index.webmanifest", manif)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScriptUtils.fixUtils()
|
||||||
main().then(() => {
|
main().then(() => {
|
||||||
console.log("All done!")
|
console.log("All done!")
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,3 +22,9 @@ report.mapcomplete.org {
|
||||||
to http://127.0.0.1:2600
|
to http://127.0.0.1:2600
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
studio.mapcomplete.org {
|
||||||
|
reverse_proxy {
|
||||||
|
to http://127.0.0.1:1235
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,68 @@
|
||||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||||
import { WritableFeatureSource } from "../FeatureSource"
|
import { WritableFeatureSource } from "../FeatureSource";
|
||||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
|
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource";
|
||||||
import { Feature, Point } from "geojson"
|
import { Feature, Point } from "geojson";
|
||||||
import { TagUtils } from "../../Tags/TagUtils"
|
import { TagUtils } from "../../Tags/TagUtils";
|
||||||
import BaseUIElement from "../../../UI/BaseUIElement"
|
import BaseUIElement from "../../../UI/BaseUIElement";
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils";
|
||||||
|
import { OsmTags } from "../../../Models/OsmFeature";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highly specialized feature source.
|
* Highly specialized feature source.
|
||||||
* Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties
|
* Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties
|
||||||
*/
|
*/
|
||||||
export class LastClickFeatureSource implements WritableFeatureSource {
|
export class LastClickFeatureSource implements WritableFeatureSource {
|
||||||
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]);
|
||||||
|
public readonly hasNoteLayer: boolean;
|
||||||
|
public readonly renderings: string[];
|
||||||
|
public readonly hasPresets: boolean;
|
||||||
|
private i: number = 0;
|
||||||
|
|
||||||
constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) {
|
constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) {
|
||||||
const allPresets: BaseUIElement[] = []
|
this.hasNoteLayer = layout.layers.some((l) => l.id === "note");
|
||||||
|
this.hasPresets = layout.layers.some((l) => l.presets?.length > 0);
|
||||||
|
const allPresets: BaseUIElement[] = [];
|
||||||
for (const layer of layout.layers)
|
for (const layer of layout.layers)
|
||||||
for (let i = 0; i < (layer.presets ?? []).length; i++) {
|
for (let i = 0; i < (layer.presets ?? []).length; i++) {
|
||||||
const preset = layer.presets[i]
|
const preset = layer.presets[i];
|
||||||
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags))
|
const tags = new ImmutableStore(TagUtils.KVtoProperties(preset.tags));
|
||||||
const { html } = layer.mapRendering[0].RenderIcon(tags, false, {
|
const { html } = layer.mapRendering[0].RenderIcon(tags, false, {
|
||||||
noSize: true,
|
noSize: true,
|
||||||
includeBadges: false,
|
includeBadges: false
|
||||||
})
|
});
|
||||||
allPresets.push(html)
|
allPresets.push(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderings = Utils.Dedup(
|
this.renderings = Utils.Dedup(
|
||||||
allPresets.map((uiElem) =>
|
allPresets.map((uiElem) =>
|
||||||
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
|
Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|
||||||
let i = 0
|
|
||||||
|
|
||||||
location.addCallbackAndRunD(({ lon, lat }) => {
|
location.addCallbackAndRunD(({ lon, lat }) => {
|
||||||
const properties = {
|
this.features.setData([this.createFeature(lon, lat)]);
|
||||||
lastclick: "yes",
|
});
|
||||||
id: "last_click_" + i,
|
}
|
||||||
has_note_layer: layout.layers.some((l) => l.id === "note") ? "yes" : "no",
|
|
||||||
has_presets: layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no",
|
|
||||||
renderings: renderings.join(""),
|
|
||||||
number_of_presets: "" + renderings.length,
|
|
||||||
first_preset: renderings[0],
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
const point = <Feature<Point>>{
|
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
|
||||||
type: "Feature",
|
const properties: OsmTags = {
|
||||||
properties,
|
lastclick: "yes",
|
||||||
geometry: {
|
id: "last_click_" + this.i,
|
||||||
type: "Point",
|
has_note_layer: this.hasNoteLayer ? "yes" : "no",
|
||||||
coordinates: [lon, lat],
|
has_presets: this.hasPresets ? "yes" : "no",
|
||||||
},
|
renderings: this.renderings.join(""),
|
||||||
|
number_of_presets: "" + this.renderings.length,
|
||||||
|
first_preset: this.renderings[0]
|
||||||
|
};
|
||||||
|
this.i++;
|
||||||
|
|
||||||
|
return <Feature<Point, OsmTags>>{
|
||||||
|
type: "Feature",
|
||||||
|
properties,
|
||||||
|
geometry: {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [lon, lat]
|
||||||
}
|
}
|
||||||
this.features.setData([point])
|
};
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
)
|
)
|
||||||
this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-community-index",
|
"fs-community-index",
|
||||||
true,
|
this.featureSwitchEnableLogin.data,
|
||||||
"Disables/enables the button to get in touch with the community"
|
"Disables/enables the button to get in touch with the community"
|
||||||
)
|
)
|
||||||
this.featureSwitchExtraLinkEnabled = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchExtraLinkEnabled = FeatureSwitchUtils.initSwitch(
|
||||||
|
|
|
@ -1,42 +1,14 @@
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||||
export class ThemeMetaTagging {
|
export class ThemeMetaTagging {
|
||||||
public static readonly themeName = "usersettings"
|
public static readonly themeName = "usersettings"
|
||||||
|
|
||||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||||
feat.properties._description
|
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||||
?.at(1)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||||
)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||||
Utils.AddLazyProperty(
|
feat.properties['__current_backgroun'] = 'initial_value'
|
||||||
feat.properties,
|
}
|
||||||
"_d",
|
}
|
||||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? ""
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
|
|
||||||
((feat) => {
|
|
||||||
const e = document.createElement("div")
|
|
||||||
e.innerHTML = feat.properties._d
|
|
||||||
return Array.from(e.getElementsByTagName("a")).filter(
|
|
||||||
(a) => a.href.match(/mastodon|en.osm.town/) !== null
|
|
||||||
)[0]?.href
|
|
||||||
})(feat)
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
|
|
||||||
((feat) => {
|
|
||||||
const e = document.createElement("div")
|
|
||||||
e.innerHTML = feat.properties._d
|
|
||||||
return Array.from(e.getElementsByTagName("a")).filter(
|
|
||||||
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
|
|
||||||
)[0]?.href
|
|
||||||
})(feat)
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(
|
|
||||||
feat.properties,
|
|
||||||
"_mastodon_candidate",
|
|
||||||
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
|
|
||||||
)
|
|
||||||
feat.properties["__current_backgroun"] = "initial_value"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -175,7 +175,6 @@ export default class ThemeViewStateHashActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private back() {
|
private back() {
|
||||||
console.log("Got a back event")
|
|
||||||
const state = this._state
|
const state = this._state
|
||||||
// history.pushState(null, null, window.location.pathname);
|
// history.pushState(null, null, window.location.pathname);
|
||||||
if (state.selectedElement.data) {
|
if (state.selectedElement.data) {
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class Constants {
|
||||||
|
|
||||||
importHelperUnlock: 5000,
|
importHelperUnlock: 5000,
|
||||||
}
|
}
|
||||||
static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 18 : 19
|
static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 17 : 18
|
||||||
/**
|
/**
|
||||||
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
|
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
|
||||||
* (Note that pendingChanges might upload sooner if the popup is closed or similar)
|
* (Note that pendingChanges might upload sooner if the popup is closed or similar)
|
||||||
|
|
|
@ -53,61 +53,6 @@ export class AvailableRasterLayers {
|
||||||
geometry: BBox.global.asGeometry(),
|
geometry: BBox.global.asGeometry(),
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly maptilerCarto: RasterLayerPolygon = {
|
|
||||||
type: "Feature",
|
|
||||||
properties: {
|
|
||||||
name: "MapTiler Carto",
|
|
||||||
url: "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy",
|
|
||||||
category: "osmbasedmap",
|
|
||||||
id: "maptiler.carto",
|
|
||||||
type: "vector",
|
|
||||||
attribution: {
|
|
||||||
text: "Maptiler",
|
|
||||||
url: "https://www.maptiler.com/copyright/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
geometry: BBox.global.asGeometry(),
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly maptilerBackdrop: RasterLayerPolygon = {
|
|
||||||
type: "Feature",
|
|
||||||
properties: {
|
|
||||||
name: "MapTiler Backdrop",
|
|
||||||
url: "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy",
|
|
||||||
category: "osmbasedmap",
|
|
||||||
id: "maptiler.backdrop",
|
|
||||||
type: "vector",
|
|
||||||
attribution: {
|
|
||||||
text: "Maptiler",
|
|
||||||
url: "https://www.maptiler.com/copyright/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
geometry: BBox.global.asGeometry(),
|
|
||||||
}
|
|
||||||
public static readonly americana: RasterLayerPolygon = {
|
|
||||||
type: "Feature",
|
|
||||||
properties: {
|
|
||||||
name: "Americana",
|
|
||||||
url: "https://zelonewolf.github.io/openstreetmap-americana/style.json",
|
|
||||||
category: "osmbasedmap",
|
|
||||||
id: "americana",
|
|
||||||
type: "vector",
|
|
||||||
attribution: {
|
|
||||||
text: "Americana",
|
|
||||||
url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
geometry: BBox.global.asGeometry(),
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly vectorLayers = [
|
|
||||||
AvailableRasterLayers.maptilerDefaultLayer,
|
|
||||||
AvailableRasterLayers.osmCarto,
|
|
||||||
AvailableRasterLayers.maptilerCarto,
|
|
||||||
AvailableRasterLayers.maptilerBackdrop,
|
|
||||||
AvailableRasterLayers.americana,
|
|
||||||
]
|
|
||||||
|
|
||||||
public static layersAvailableAt(
|
public static layersAvailableAt(
|
||||||
location: Store<{ lon: number; lat: number }>
|
location: Store<{ lon: number; lat: number }>
|
||||||
): Store<RasterLayerPolygon[]> {
|
): Store<RasterLayerPolygon[]> {
|
||||||
|
@ -119,7 +64,7 @@ export class AvailableRasterLayers {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const available = Stores.ListStabilized(
|
return Stores.ListStabilized(
|
||||||
availableLayersBboxes.map((eliPolygons) => {
|
availableLayersBboxes.map((eliPolygons) => {
|
||||||
const loc = location.data
|
const loc = location.data
|
||||||
const lonlat: [number, number] = [loc.lon, loc.lat]
|
const lonlat: [number, number] = [loc.lon, loc.lat]
|
||||||
|
@ -129,12 +74,11 @@ export class AvailableRasterLayers {
|
||||||
}
|
}
|
||||||
return GeoOperations.inside(lonlat, eliPolygon)
|
return GeoOperations.inside(lonlat, eliPolygon)
|
||||||
})
|
})
|
||||||
|
matching.push(AvailableRasterLayers.maptilerDefaultLayer)
|
||||||
matching.push(...AvailableRasterLayers.globalLayers)
|
matching.push(...AvailableRasterLayers.globalLayers)
|
||||||
matching.unshift(...AvailableRasterLayers.vectorLayers)
|
|
||||||
return matching
|
return matching
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return available
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,9 @@ export default class LayoutConfig implements LayoutInformation {
|
||||||
}
|
}
|
||||||
const context = this.id
|
const context = this.id
|
||||||
this.credits = json.credits
|
this.credits = json.credits
|
||||||
|
if(!json.title){
|
||||||
|
throw `The theme ${json.id} does not have a title defined.`
|
||||||
|
}
|
||||||
this.language = json.mustHaveLanguage ?? Object.keys(json.title)
|
this.language = json.mustHaveLanguage ?? Object.keys(json.title)
|
||||||
this.usedImages = Array.from(
|
this.usedImages = Array.from(
|
||||||
new ExtractImages(official, undefined)
|
new ExtractImages(official, undefined)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,28 @@
|
||||||
let id = Math.random() * 1000000000 + ""
|
let id = Math.random() * 1000000000 + ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form>
|
<form on:change|preventDefault={() => {
|
||||||
|
drawAttention = false
|
||||||
|
dispatcher("submit", inputElement.files)
|
||||||
|
}}
|
||||||
|
on:dragend={() => {
|
||||||
|
console.log("Drag end")
|
||||||
|
drawAttention = false
|
||||||
|
}}
|
||||||
|
on:dragenter|preventDefault|stopPropagation={(e) => {
|
||||||
|
console.log("Dragging enter")
|
||||||
|
drawAttention = true
|
||||||
|
e.dataTransfer.drop = "copy"
|
||||||
|
}}
|
||||||
|
on:dragstart={() => {
|
||||||
|
console.log("DragStart")
|
||||||
|
drawAttention = false
|
||||||
|
}}
|
||||||
|
on:drop|preventDefault|stopPropagation={(e) => {
|
||||||
|
console.log("Got a 'drop'")
|
||||||
|
drawAttention = false
|
||||||
|
dispatcher("submit", e.dataTransfer.files)
|
||||||
|
}}>
|
||||||
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
|
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
|
||||||
<slot />
|
<slot />
|
||||||
</label>
|
</label>
|
||||||
|
@ -23,26 +44,7 @@
|
||||||
id={"fileinput" + id}
|
id={"fileinput" + id}
|
||||||
{multiple}
|
{multiple}
|
||||||
name="file-input"
|
name="file-input"
|
||||||
on:change|preventDefault={() => {
|
|
||||||
drawAttention = false
|
|
||||||
dispatcher("submit", inputElement.files)
|
|
||||||
}}
|
|
||||||
on:dragend={() => {
|
|
||||||
drawAttention = false
|
|
||||||
}}
|
|
||||||
on:dragover|preventDefault|stopPropagation={(e) => {
|
|
||||||
console.log("Dragging over!")
|
|
||||||
drawAttention = true
|
|
||||||
e.dataTransfer.drop = "copy"
|
|
||||||
}}
|
|
||||||
on:dragstart={() => {
|
|
||||||
drawAttention = false
|
|
||||||
}}
|
|
||||||
on:drop|preventDefault|stopPropagation={(e) => {
|
|
||||||
console.log("Got a 'drop'")
|
|
||||||
drawAttention = false
|
|
||||||
dispatcher("submit", e.dataTransfer.files)
|
|
||||||
}}
|
|
||||||
type="file"
|
type="file"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -11,8 +11,9 @@
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 right-0 h-screen w-screen p-4 md:p-6"
|
class="absolute top-0 right-0 h-screen w-screen p-4 md:p-6"
|
||||||
style="background-color: #00000088"
|
style="background-color: #00000088"
|
||||||
|
on:click={() => {dispatch("close")}}
|
||||||
>
|
>
|
||||||
<div class="content normal-background">
|
<div class="content normal-background" on:click|stopPropagation={() => {}}>
|
||||||
<div class="h-full rounded-xl">
|
<div class="h-full rounded-xl">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,14 @@ export default class Hotkeys {
|
||||||
}[]
|
}[]
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
private static textElementSelected(): boolean {
|
private static textElementSelected(event: KeyboardEvent): boolean {
|
||||||
|
if(event.ctrlKey || event.altKey){
|
||||||
|
// This is an event with a modifier-key, lets not ignore it
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(event.key === "Escape"){
|
||||||
|
return false // Another not-printable character that should not be ignored
|
||||||
|
}
|
||||||
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
|
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
|
||||||
}
|
}
|
||||||
public static RegisterHotkey(
|
public static RegisterHotkey(
|
||||||
|
@ -68,7 +75,7 @@ export default class Hotkeys {
|
||||||
})
|
})
|
||||||
} else if (key["shift"] !== undefined) {
|
} else if (key["shift"] !== undefined) {
|
||||||
document.addEventListener(type, function (event) {
|
document.addEventListener(type, function (event) {
|
||||||
if (Hotkeys.textElementSelected()) {
|
if (Hotkeys.textElementSelected(event)) {
|
||||||
// A text element is selected, we don't do anything special
|
// A text element is selected, we don't do anything special
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +93,7 @@ export default class Hotkeys {
|
||||||
})
|
})
|
||||||
} else if (key["nomod"] !== undefined) {
|
} else if (key["nomod"] !== undefined) {
|
||||||
document.addEventListener(type, function (event) {
|
document.addEventListener(type, function (event) {
|
||||||
if (Hotkeys.textElementSelected()) {
|
if (Hotkeys.textElementSelected(event)) {
|
||||||
// A text element is selected, we don't do anything special
|
// A text element is selected, we don't do anything special
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
/**
|
||||||
* Thin wrapper around 'TabGroup' which binds the state
|
* Thin wrapper around 'TabGroup' which binds the state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
|
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge";
|
||||||
|
|
||||||
export let tab: UIEventSource<number>
|
/**
|
||||||
let tabElements: HTMLElement[] = []
|
* If a condition is given for a certain tab, it will only be shown if this condition is true.
|
||||||
$: tabElements[$tab]?.click()
|
* E.g.
|
||||||
$: {
|
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
|
||||||
if (tabElements[tab.data]) {
|
*/
|
||||||
window.setTimeout(() => tabElements[tab.data].click(), 50)
|
let tr = new ImmutableStore(true)
|
||||||
|
export let condition0: Store<boolean> = tr
|
||||||
|
export let condition1: Store<boolean> = tr
|
||||||
|
export let condition2: Store<boolean> = tr
|
||||||
|
export let condition3: Store<boolean> = tr
|
||||||
|
export let condition4: Store<boolean> = tr
|
||||||
|
|
||||||
|
export let tab: UIEventSource<number>;
|
||||||
|
let tabElements: HTMLElement[] = [];
|
||||||
|
$: tabElements[$tab]?.click();
|
||||||
|
$: {
|
||||||
|
if (tabElements[tab.data]) {
|
||||||
|
window.setTimeout(() => tabElements[tab.data].click(), 50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="tabbedgroup flex h-full w-full">
|
<div class="tabbedgroup flex h-full w-full">
|
||||||
|
@ -29,41 +41,31 @@
|
||||||
>
|
>
|
||||||
<div class="interactive sticky top-0 flex items-center justify-between">
|
<div class="interactive sticky top-0 flex items-center justify-between">
|
||||||
<TabList class="flex flex-wrap">
|
<TabList class="flex flex-wrap">
|
||||||
{#if $$slots.title1}
|
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}>
|
||||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
<div bind:this={tabElements[0]} class="flex">
|
||||||
<div bind:this={tabElements[0]} class="flex">
|
<slot name="title0">Tab 0</slot>
|
||||||
<slot name="title0">Tab 0</slot>
|
</div>
|
||||||
</div>
|
</Tab>
|
||||||
</Tab>
|
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}>
|
||||||
{/if}
|
<div bind:this={tabElements[1]} class="flex">
|
||||||
{#if $$slots.title1}
|
<slot name="title1" />
|
||||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
</div>
|
||||||
<div bind:this={tabElements[1]} class="flex">
|
</Tab>
|
||||||
<slot name="title1" />
|
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}>
|
||||||
</div>
|
<div bind:this={tabElements[2]} class="flex">
|
||||||
</Tab>
|
<slot name="title2" />
|
||||||
{/if}
|
</div>
|
||||||
{#if $$slots.title2}
|
</Tab>
|
||||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}>
|
||||||
<div bind:this={tabElements[2]} class="flex">
|
<div bind:this={tabElements[3]} class="flex">
|
||||||
<slot name="title2" />
|
<slot name="title3" />
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
{/if}
|
<Tab class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}>
|
||||||
{#if $$slots.title3}
|
<div bind:this={tabElements[4]} class="flex">
|
||||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
<slot name="title4" />
|
||||||
<div bind:this={tabElements[3]} class="flex">
|
</div>
|
||||||
<slot name="title3" />
|
</Tab>
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
{/if}
|
|
||||||
{#if $$slots.title4}
|
|
||||||
<Tab class={({ selected }) => twJoin("tab", selected && "primary")}>
|
|
||||||
<div bind:this={tabElements[4]} class="flex">
|
|
||||||
<slot name="title4" />
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
{/if}
|
|
||||||
</TabList>
|
</TabList>
|
||||||
<slot name="post-tablist" />
|
<slot name="post-tablist" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,16 +77,24 @@
|
||||||
</slot>
|
</slot>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel class="tabpanel">
|
<TabPanel class="tabpanel">
|
||||||
<slot name="content1" />
|
<slot name="content1">
|
||||||
|
<div />
|
||||||
|
</slot>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel class="tabpanel">
|
<TabPanel class="tabpanel">
|
||||||
<slot name="content2" />
|
<slot name="content2">
|
||||||
|
<div />
|
||||||
|
</slot>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel class="tabpanel">
|
<TabPanel class="tabpanel">
|
||||||
<slot name="content3" />
|
<slot name="content3">
|
||||||
|
<div />
|
||||||
|
</slot>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel class="tabpanel">
|
<TabPanel class="tabpanel">
|
||||||
<slot name="content4" />
|
<slot name="content4">
|
||||||
|
<div />
|
||||||
|
</slot>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,44 +102,44 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tabbedgroup {
|
.tabbedgroup {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tabpanel) {
|
:global(.tabpanel) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tabpanels) {
|
:global(.tabpanels) {
|
||||||
height: calc(100% - 2rem);
|
height: calc(100% - 2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tab) {
|
:global(.tab) {
|
||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
padding-right: 0.75rem;
|
padding-right: 0.75rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tab .flex) {
|
:global(.tab .flex) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tab span|div) {
|
:global(.tab span|div) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tab-selected svg) {
|
:global(.tab-selected svg) {
|
||||||
fill: var(--catch-detail-color-contrast);
|
fill: var(--catch-detail-color-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.tab-unselected) {
|
:global(.tab-unselected) {
|
||||||
background-color: var(--background-color) !important;
|
background-color: var(--background-color) !important;
|
||||||
color: var(--foreground-color) !important;
|
color: var(--foreground-color) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,47 +1,48 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations";
|
||||||
import Svg from "../../Svg"
|
import Svg from "../../Svg";
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte";
|
||||||
import NextButton from "../Base/NextButton.svelte"
|
import NextButton from "../Base/NextButton.svelte";
|
||||||
import Geosearch from "./Geosearch.svelte"
|
import Geosearch from "./Geosearch.svelte";
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import ToSvelte from "../Base/ToSvelte.svelte";
|
||||||
import ThemeViewState from "../../Models/ThemeViewState"
|
import ThemeViewState from "../../Models/ThemeViewState";
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge";
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils";
|
||||||
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
|
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
|
||||||
|
import If from "../Base/If.svelte";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The theme introduction panel
|
* The theme introduction panel
|
||||||
*/
|
*/
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState;
|
||||||
let layout = state.layout
|
let layout = state.layout;
|
||||||
let selectedElement = state.selectedElement
|
let selectedElement = state.selectedElement;
|
||||||
let selectedLayer = state.selectedLayer
|
let selectedLayer = state.selectedLayer;
|
||||||
|
|
||||||
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined);
|
||||||
let searchEnabled = false
|
let searchEnabled = false;
|
||||||
|
|
||||||
let geopermission: Store<GeolocationPermissionState> =
|
let geopermission: Store<GeolocationPermissionState> =
|
||||||
state.geolocation.geolocationState.permission
|
state.geolocation.geolocationState.permission;
|
||||||
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation
|
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation;
|
||||||
|
|
||||||
geopermission.addCallback((perm) => console.log(">>>> Permission", perm))
|
geopermission.addCallback((perm) => console.log(">>>> Permission", perm));
|
||||||
|
|
||||||
function jumpToCurrentLocation() {
|
function jumpToCurrentLocation() {
|
||||||
const glstate = state.geolocation.geolocationState
|
const glstate = state.geolocation.geolocationState;
|
||||||
if (glstate.currentGPSLocation.data !== undefined) {
|
if (glstate.currentGPSLocation.data !== undefined) {
|
||||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
|
const c: GeolocationCoordinates = glstate.currentGPSLocation.data;
|
||||||
state.guistate.themeIsOpened.setData(false)
|
state.guistate.themeIsOpened.setData(false);
|
||||||
const coor = { lon: c.longitude, lat: c.latitude }
|
const coor = { lon: c.longitude, lat: c.latitude };
|
||||||
state.mapProperties.location.setData(coor)
|
state.mapProperties.location.setData(coor);
|
||||||
|
}
|
||||||
|
if (glstate.permission.data !== "granted") {
|
||||||
|
glstate.requestPermission();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (glstate.permission.data !== "granted") {
|
|
||||||
glstate.requestPermission()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col justify-between">
|
<div class="flex h-full flex-col justify-between">
|
||||||
|
@ -62,61 +63,67 @@
|
||||||
</div>
|
</div>
|
||||||
</NextButton>
|
</NextButton>
|
||||||
|
|
||||||
<div class="flex w-full flex-wrap sm:flex-nowrap">
|
|
||||||
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
|
|
||||||
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
|
||||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
|
|
||||||
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
|
|
||||||
</button>
|
|
||||||
<!-- No geolocation granted - we don't show the button -->
|
|
||||||
{:else if $geopermission === "requested"}
|
|
||||||
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
|
||||||
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
|
||||||
<ToSvelte
|
|
||||||
construct={Svg.crosshair_svg()
|
|
||||||
.SetClass("w-8 h-8")
|
|
||||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
|
||||||
/>
|
|
||||||
<Tr t={Translations.t.general.waitingForGeopermission} />
|
|
||||||
</button>
|
|
||||||
{:else if $geopermission === "denied"}
|
|
||||||
<button class="disabled flex w-full items-center gap-x-2">
|
|
||||||
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
|
|
||||||
<Tr t={Translations.t.general.geopermissionDenied} />
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button class="disabled flex w-full items-center gap-x-2">
|
|
||||||
<ToSvelte
|
|
||||||
construct={Svg.crosshair_svg()
|
|
||||||
.SetClass("w-8 h-8")
|
|
||||||
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
|
||||||
/>
|
|
||||||
<Tr t={Translations.t.general.waitingForLocation} />
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
|
<div class="flex w-full flex-wrap sm:flex-nowrap">
|
||||||
<div class="w-full">
|
<If condition={state.featureSwitches.featureSwitchGeolocation}>
|
||||||
<Geosearch
|
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
|
||||||
bounds={state.mapProperties.bounds}
|
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
|
||||||
on:searchIsValid={(isValid) => {
|
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
|
||||||
|
</button>
|
||||||
|
<!-- No geolocation granted - we don't show the button -->
|
||||||
|
{:else if $geopermission === "requested"}
|
||||||
|
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||||
|
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
||||||
|
<ToSvelte
|
||||||
|
construct={Svg.crosshair_svg()
|
||||||
|
.SetClass("w-8 h-8")
|
||||||
|
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||||
|
/>
|
||||||
|
<Tr t={Translations.t.general.waitingForGeopermission} />
|
||||||
|
</button>
|
||||||
|
{:else if $geopermission === "denied"}
|
||||||
|
<button class="disabled flex w-full items-center gap-x-2">
|
||||||
|
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
|
||||||
|
<Tr t={Translations.t.general.geopermissionDenied} />
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button class="disabled flex w-full items-center gap-x-2">
|
||||||
|
<ToSvelte
|
||||||
|
construct={Svg.crosshair_svg()
|
||||||
|
.SetClass("w-8 h-8")
|
||||||
|
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
|
||||||
|
/>
|
||||||
|
<Tr t={Translations.t.general.waitingForLocation} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</If>
|
||||||
|
|
||||||
|
|
||||||
|
<If condition={state.featureSwitches.featureSwitchSearch}>
|
||||||
|
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
|
||||||
|
<div class="w-full">
|
||||||
|
<Geosearch
|
||||||
|
bounds={state.mapProperties.bounds}
|
||||||
|
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
||||||
|
on:searchIsValid={(isValid) => {
|
||||||
searchEnabled = isValid
|
searchEnabled = isValid
|
||||||
}}
|
}}
|
||||||
perLayer={state.perLayer}
|
perLayer={state.perLayer}
|
||||||
{selectedElement}
|
{selectedElement}
|
||||||
{selectedLayer}
|
{selectedLayer}
|
||||||
{triggerSearch}
|
{triggerSearch}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
|
||||||
|
on:click={() => triggerSearch.ping()}
|
||||||
|
>
|
||||||
|
<Tr t={Translations.t.general.search.searchShort} />
|
||||||
|
<SearchIcon class="h-6 w-6" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</If>
|
||||||
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
|
|
||||||
on:click={() => triggerSearch.ping()}
|
|
||||||
>
|
|
||||||
<Tr t={Translations.t.general.search.searchShort} />
|
|
||||||
<SearchIcon class="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
import { InputElement } from "./InputElement"
|
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import InputElementMap from "./InputElementMap"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export class CheckBox extends InputElementMap<number[], boolean> {
|
|
||||||
constructor(el: BaseUIElement | string, defaultValue?: boolean) {
|
|
||||||
super(
|
|
||||||
new CheckBoxes([Translations.W(el)]),
|
|
||||||
(x0, x1) => x0 === x1,
|
|
||||||
(t) => t.length > 0,
|
|
||||||
(x) => (x ? [0] : [])
|
|
||||||
)
|
|
||||||
if (defaultValue !== undefined) {
|
|
||||||
this.GetValue().setData(defaultValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of individual checkboxes
|
|
||||||
* The value will contain the indexes of the selected checkboxes
|
|
||||||
*/
|
|
||||||
export default class CheckBoxes extends InputElement<number[]> {
|
|
||||||
private static _nextId = 0
|
|
||||||
private readonly value: UIEventSource<number[]>
|
|
||||||
private readonly _elements: BaseUIElement[]
|
|
||||||
|
|
||||||
constructor(elements: BaseUIElement[], value = new UIEventSource<number[]>([])) {
|
|
||||||
super()
|
|
||||||
this.value = value
|
|
||||||
this._elements = Utils.NoNull(elements)
|
|
||||||
this.SetClass("flex flex-col")
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(ts: number[]): boolean {
|
|
||||||
return ts !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<number[]> {
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
const formTag = document.createElement("form")
|
|
||||||
|
|
||||||
const value = this.value
|
|
||||||
const elements = this._elements
|
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i++) {
|
|
||||||
let inputI = elements[i]
|
|
||||||
const input = document.createElement("input")
|
|
||||||
const id = CheckBoxes._nextId
|
|
||||||
CheckBoxes._nextId++
|
|
||||||
input.id = "checkbox" + id
|
|
||||||
|
|
||||||
input.type = "checkbox"
|
|
||||||
input.classList.add("p-1", "cursor-pointer", "m-3", "pl-3", "mr-0")
|
|
||||||
|
|
||||||
const label = document.createElement("label")
|
|
||||||
label.htmlFor = input.id
|
|
||||||
label.appendChild(input)
|
|
||||||
label.appendChild(inputI.ConstructElement())
|
|
||||||
label.classList.add("block", "w-full", "p-2", "cursor-pointer")
|
|
||||||
|
|
||||||
formTag.appendChild(label)
|
|
||||||
|
|
||||||
value.addCallbackAndRunD((selectedValues) => {
|
|
||||||
input.checked = selectedValues.indexOf(i) >= 0
|
|
||||||
|
|
||||||
if (input.checked) {
|
|
||||||
label.classList.add("checked")
|
|
||||||
} else {
|
|
||||||
label.classList.remove("checked")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
input.onchange = () => {
|
|
||||||
// Index = index in the list of already checked items
|
|
||||||
const index = value.data.indexOf(i)
|
|
||||||
if (input.checked && index < 0) {
|
|
||||||
value.data.push(i)
|
|
||||||
value.ping()
|
|
||||||
} else if (index >= 0) {
|
|
||||||
value.data.splice(index, 1)
|
|
||||||
value.ping()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return formTag
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { InputElement } from "./InputElement"
|
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export default class InputElementMap<T, X> extends InputElement<X> {
|
|
||||||
private readonly _inputElement: InputElement<T>
|
|
||||||
private isSame: (x0: X, x1: X) => boolean
|
|
||||||
private readonly fromX: (x: X) => T
|
|
||||||
private readonly toX: (t: T) => X
|
|
||||||
private readonly _value: UIEventSource<X>
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
inputElement: InputElement<T>,
|
|
||||||
isSame: (x0: X, x1: X) => boolean,
|
|
||||||
toX: (t: T) => X,
|
|
||||||
fromX: (x: X) => T,
|
|
||||||
extraSources: Store<any>[] = []
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
this.isSame = isSame
|
|
||||||
this.fromX = fromX
|
|
||||||
this.toX = toX
|
|
||||||
this._inputElement = inputElement
|
|
||||||
const self = this
|
|
||||||
this._value = inputElement.GetValue().sync(
|
|
||||||
(t) => {
|
|
||||||
const newX = toX(t)
|
|
||||||
const currentX = self.GetValue()?.data
|
|
||||||
if (isSame(currentX, newX)) {
|
|
||||||
return currentX
|
|
||||||
}
|
|
||||||
return newX
|
|
||||||
},
|
|
||||||
extraSources,
|
|
||||||
(x) => {
|
|
||||||
return fromX(x)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<X> {
|
|
||||||
return this._value
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(x: X): boolean {
|
|
||||||
if (x === undefined) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const t = this.fromX(x)
|
|
||||||
if (t === undefined) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return this._inputElement.IsValid(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._inputElement.ConstructElement()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||||
|
|
||||||
export default class LanguagePicker extends Toggle {
|
export default class LanguagePicker extends Toggle {
|
||||||
constructor(languages: string[], assignTo: UIEventSource<string>) {
|
constructor(languages: string[], assignTo: UIEventSource<string>) {
|
||||||
console.log("Constructing a language pîcker for languages", languages)
|
console.log("Constructing a language picker for languages", languages)
|
||||||
if (
|
if (
|
||||||
languages === undefined ||
|
languages === undefined ||
|
||||||
languages.length <= 1 ||
|
languages.length <= 1 ||
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||||
import type { Map as MLMap } from "maplibre-gl"
|
import type { Map as MLMap } from "maplibre-gl";
|
||||||
import { Map as MlMap, SourceSpecification } from "maplibre-gl"
|
import { Map as MlMap, SourceSpecification } from "maplibre-gl";
|
||||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"
|
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers";
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils";
|
||||||
import { BBox } from "../../Logic/BBox"
|
import { BBox } from "../../Logic/BBox";
|
||||||
import { ExportableMap, MapProperties } from "../../Models/MapProperties"
|
import { ExportableMap, MapProperties } from "../../Models/MapProperties";
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
import SvelteUIElement from "../Base/SvelteUIElement";
|
||||||
import MaplibreMap from "./MaplibreMap.svelte"
|
import MaplibreMap from "./MaplibreMap.svelte";
|
||||||
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
import { RasterLayerProperties } from "../../Models/RasterLayerProperties";
|
||||||
import * as htmltoimage from "html-to-image"
|
import * as htmltoimage from "html-to-image";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
||||||
import FilteredLayer from "../../Models/FilteredLayer"
|
import FilteredLayer from "../../Models/FilteredLayer"
|
||||||
import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource"
|
import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource"
|
||||||
|
import { CLIENT_RENEG_LIMIT } from "tls";
|
||||||
|
|
||||||
class PointRenderingLayer {
|
class PointRenderingLayer {
|
||||||
private readonly _config: PointRenderingConfig
|
private readonly _config: PointRenderingConfig
|
||||||
|
@ -406,13 +407,10 @@ class LineRenderingLayer {
|
||||||
} else {
|
} else {
|
||||||
const tags = this._fetchStore(id)
|
const tags = this._fetchStore(id)
|
||||||
this._listenerInstalledOn.add(id)
|
this._listenerInstalledOn.add(id)
|
||||||
map.setFeatureState(
|
tags.addCallbackAndRunD((properties) => {
|
||||||
{ source: this._layername, id },
|
// Make sure to use 'getSource' here, the layer names are different!
|
||||||
this.calculatePropsFor(feature.properties)
|
if(map.getSource(this._layername) === undefined){
|
||||||
)
|
return true
|
||||||
tags.addCallbackD((properties) => {
|
|
||||||
if (!map.getLayer(this._layername)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
map.setFeatureState(
|
map.setFeatureState(
|
||||||
{ source: this._layername, id },
|
{ source: this._layername, id },
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
<LoginToggle ignoreLoading={true} {state}>
|
<LoginToggle ignoreLoading={true} {state}>
|
||||||
{#if currentState === "start"}
|
{#if currentState === "start"}
|
||||||
<button
|
<button
|
||||||
class="flex"
|
class="flex items-center"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
currentState = "confirm"
|
currentState = "confirm"
|
||||||
}}
|
}}
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
<button
|
<button
|
||||||
slot="save-button"
|
slot="save-button"
|
||||||
on:click={onDelete}
|
on:click={onDelete}
|
||||||
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600")}
|
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600 items-center")}
|
||||||
>
|
>
|
||||||
<TrashIcon
|
<TrashIcon
|
||||||
class={twJoin(
|
class={twJoin(
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
/>
|
/>
|
||||||
<Tr t={t.delete} />
|
<Tr t={t.delete} />
|
||||||
</button>
|
</button>
|
||||||
<button slot="cancel" 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
|
<XCircleIcon
|
||||||
|
|
|
@ -1,114 +1,114 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||||
import { Map as MlMap } from "maplibre-gl"
|
import { Map as MlMap } from "maplibre-gl";
|
||||||
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||||
import MapControlButton from "./Base/MapControlButton.svelte"
|
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||||
import ToSvelte from "./Base/ToSvelte.svelte"
|
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||||
import If from "./Base/If.svelte"
|
import If from "./Base/If.svelte";
|
||||||
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson";
|
||||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||||
import Filterview from "./BigComponents/Filterview.svelte"
|
import Filterview from "./BigComponents/Filterview.svelte";
|
||||||
import ThemeViewState from "../Models/ThemeViewState"
|
import ThemeViewState from "../Models/ThemeViewState";
|
||||||
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 { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { CogIcon, EyeIcon, 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";
|
||||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||||
import Constants from "../Models/Constants"
|
import Constants from "../Models/Constants";
|
||||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
||||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||||
import LoginButton from "./Base/LoginButton.svelte"
|
import LoginButton from "./Base/LoginButton.svelte";
|
||||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
||||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
||||||
import ModalRight from "./Base/ModalRight.svelte"
|
import ModalRight from "./Base/ModalRight.svelte";
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils";
|
||||||
import Hotkeys from "./Base/Hotkeys"
|
import Hotkeys from "./Base/Hotkeys";
|
||||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
import { VariableUiElement } from "./Base/VariableUIElement";
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
import SvelteUIElement from "./Base/SvelteUIElement";
|
||||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
||||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
||||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
||||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||||
import Svg from "../Svg"
|
import Svg from "../Svg";
|
||||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
||||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
||||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||||
import IfHidden from "./Base/IfHidden.svelte"
|
import IfHidden from "./Base/IfHidden.svelte";
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte";
|
||||||
import { OpenJosm } from "./BigComponents/OpenJosm"
|
import { OpenJosm } from "./BigComponents/OpenJosm";
|
||||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
||||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||||
import LanguagePicker from "./LanguagePicker"
|
import LanguagePicker from "./LanguagePicker";
|
||||||
import Locale from "./i18n/Locale"
|
import Locale from "./i18n/Locale";
|
||||||
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||||
|
|
||||||
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> = state.selectedElement
|
let selectedElement: UIEventSource<Feature> = state.selectedElement;
|
||||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer;
|
||||||
|
|
||||||
const selectedElementView = selectedElement.map(
|
const selectedElementView = selectedElement.map(
|
||||||
(selectedElement) => {
|
(selectedElement) => {
|
||||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||||
const layer = selectedLayer.data
|
const layer = selectedLayer.data;
|
||||||
if (selectedElement === undefined || layer === undefined) {
|
if (selectedElement === undefined || layer === undefined) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||||
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags })
|
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags });
|
||||||
},
|
},
|
||||||
[selectedLayer]
|
[selectedLayer]
|
||||||
)
|
);
|
||||||
|
|
||||||
const selectedElementTitle = selectedElement.map(
|
const selectedElementTitle = selectedElement.map(
|
||||||
(selectedElement) => {
|
(selectedElement) => {
|
||||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||||
const layer = selectedLayer.data
|
const layer = selectedLayer.data;
|
||||||
if (selectedElement === undefined || layer === undefined) {
|
if (selectedElement === undefined || layer === undefined) {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||||
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
|
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags });
|
||||||
},
|
},
|
||||||
[selectedLayer]
|
[selectedLayer]
|
||||||
)
|
);
|
||||||
|
|
||||||
let mapproperties: MapProperties = state.mapProperties
|
let mapproperties: MapProperties = state.mapProperties;
|
||||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
let featureSwitches: FeatureSwitchState = state.featureSwitches;
|
||||||
let availableLayers = state.availableLayers
|
let availableLayers = state.availableLayers;
|
||||||
let userdetails = state.osmConnection.userDetails
|
let userdetails = state.osmConnection.userDetails;
|
||||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
|
let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
|
||||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
|
||||||
let rasterLayerName =
|
let rasterLayerName =
|
||||||
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name
|
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
|
||||||
onDestroy(
|
onDestroy(
|
||||||
rasterLayer.addCallbackAndRunD((l) => {
|
rasterLayer.addCallbackAndRunD((l) => {
|
||||||
rasterLayerName = l.properties.name
|
rasterLayerName = l.properties.name;
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||||
|
@ -168,18 +168,39 @@
|
||||||
<div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen">
|
<div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen">
|
||||||
<!-- bottom controls -->
|
<!-- bottom controls -->
|
||||||
<div class="flex w-full items-end justify-between px-4">
|
<div class="flex w-full items-end justify-between px-4">
|
||||||
<div class="flex">
|
<div class="flex flex-col">
|
||||||
<!-- bottom left elements -->
|
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
{#if state.lastClickObject.hasPresets || state.lastClickObject.hasNoteLayer}
|
||||||
<a
|
<button class="w-fit pointer-events-auto" on:click={() => {state.openNewDialog()}}>
|
||||||
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
{#if state.lastClickObject.hasPresets}
|
||||||
on:click={() => {
|
<Tr t={Translations.t.general.add.title} />
|
||||||
|
{:else}
|
||||||
|
<Tr t={Translations.t.notes.addAComment} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<!-- bottom left elements -->
|
||||||
|
<If condition={state.featureSwitches.featureSwitchFilter}>
|
||||||
|
<MapControlButton on:click={() => state.guistate.openFilterView()}>
|
||||||
|
<ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} />
|
||||||
|
</MapControlButton>
|
||||||
|
</If>
|
||||||
|
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
||||||
|
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
||||||
|
</If>
|
||||||
|
<a
|
||||||
|
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
||||||
|
on:click={() => {
|
||||||
state.guistate.themeViewTab.setData("copyright")
|
state.guistate.themeViewTab.setData("copyright")
|
||||||
state.guistate.themeIsOpened.setData(true)
|
state.guistate.themeIsOpened.setData(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
|
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
|
@ -255,9 +276,9 @@
|
||||||
|
|
||||||
<If condition={state.guistate.themeIsOpened}>
|
<If condition={state.guistate.themeIsOpened}>
|
||||||
<!-- Theme menu -->
|
<!-- Theme menu -->
|
||||||
<FloatOver>
|
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
||||||
<span slot="close-button"><!-- Disable the close button --></span>
|
<span slot="close-button"><!-- Disable the close button --></span>
|
||||||
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
|
<TabbedGroup condition1={state.featureSwitches.featureSwitchFilter} tab={state.guistate.themeViewTabIndex}>
|
||||||
<div slot="post-tablist">
|
<div slot="post-tablist">
|
||||||
<XCircleIcon
|
<XCircleIcon
|
||||||
class="mr-2 h-8 w-8"
|
class="mr-2 h-8 w-8"
|
||||||
|
@ -275,10 +296,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex" slot="title1">
|
<div class="flex" slot="title1">
|
||||||
<If condition={state.featureSwitches.featureSwitchFilter}>
|
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
|
||||||
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
|
<Tr t={Translations.t.general.menu.filter} />
|
||||||
<Tr t={Translations.t.general.menu.filter} />
|
|
||||||
</If>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-2 flex flex-col" slot="content1">
|
<div class="m-2 flex flex-col" slot="content1">
|
||||||
|
@ -298,6 +317,7 @@
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex" slot="title2">
|
<div class="flex" slot="title2">
|
||||||
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
||||||
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} />
|
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} />
|
||||||
|
@ -314,7 +334,7 @@
|
||||||
|
|
||||||
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
|
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
|
||||||
|
|
||||||
<div slot="title4" class="flex">
|
<div class="flex" slot="title4">
|
||||||
<ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} />
|
<ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} />
|
||||||
<Tr t={Translations.t.general.sharescreen.title} />
|
<Tr t={Translations.t.general.sharescreen.title} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -327,7 +347,7 @@
|
||||||
|
|
||||||
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
||||||
<!-- background layer selector -->
|
<!-- background layer selector -->
|
||||||
<FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}>
|
<FloatOver on:close={() => {state.guistate.backgroundLayerSelectionIsOpened.setData(false)}}>
|
||||||
<div class="h-full p-2">
|
<div class="h-full p-2">
|
||||||
<RasterLayerOverview
|
<RasterLayerOverview
|
||||||
{availableLayers}
|
{availableLayers}
|
||||||
|
@ -342,9 +362,10 @@
|
||||||
|
|
||||||
<If condition={state.guistate.menuIsOpened}>
|
<If condition={state.guistate.menuIsOpened}>
|
||||||
<!-- Menu page -->
|
<!-- Menu page -->
|
||||||
<FloatOver>
|
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false) }>
|
||||||
<span slot="close-button"><!-- Hide the default close button --></span>
|
<span slot="close-button"><!-- Hide the default close button --></span>
|
||||||
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
|
<TabbedGroup condition1={featureSwitches.featureSwitchEnableLogin} condition2={state.featureSwitches. featureSwitchCommunityIndex}
|
||||||
|
tab={state.guistate.menuViewTabIndex}>
|
||||||
<div slot="post-tablist">
|
<div slot="post-tablist">
|
||||||
<XCircleIcon
|
<XCircleIcon
|
||||||
class="mr-2 h-8 w-8"
|
class="mr-2 h-8 w-8"
|
||||||
|
@ -419,7 +440,6 @@
|
||||||
<div class="m-2" slot="content2">
|
<div class="m-2" slot="content2">
|
||||||
<CommunityIndexView location={state.mapProperties.location} />
|
<CommunityIndexView location={state.mapProperties.location} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex" slot="title3">
|
<div class="flex" slot="title3">
|
||||||
<EyeIcon class="w-6" />
|
<EyeIcon class="w-6" />
|
||||||
<Tr t={Translations.t.privacy.title} />
|
<Tr t={Translations.t.privacy.title} />
|
||||||
|
@ -430,12 +450,15 @@
|
||||||
|
|
||||||
<Tr slot="title4" t={Translations.t.advanced.title} />
|
<Tr slot="title4" t={Translations.t.advanced.title} />
|
||||||
<div class="m-2 flex flex-col" slot="content4">
|
<div class="m-2 flex flex-col" slot="content4">
|
||||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||||
<ToSvelte
|
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||||
construct={() =>
|
<ToSvelte
|
||||||
|
construct={() =>
|
||||||
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
|
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
|
||||||
/>
|
/>
|
||||||
<MapillaryLink mapProperties={state.mapProperties} />
|
<MapillaryLink mapProperties={state.mapProperties} />
|
||||||
|
</If>
|
||||||
|
|
||||||
<ToSvelte construct={Hotkeys.generateDocumentationDynamic} />
|
<ToSvelte construct={Hotkeys.generateDocumentationDynamic} />
|
||||||
</div>
|
</div>
|
||||||
</TabbedGroup>
|
</TabbedGroup>
|
||||||
|
|
11
src/Utils.ts
11
src/Utils.ts
|
@ -1098,7 +1098,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
return { content: data }
|
return { content: data }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not parse ", data, "due to", e, "\n", e.stack)
|
console.error(
|
||||||
|
"Could not parse the response of",
|
||||||
|
url,
|
||||||
|
"which contains",
|
||||||
|
data,
|
||||||
|
"due to",
|
||||||
|
e,
|
||||||
|
"\n",
|
||||||
|
e.stack
|
||||||
|
)
|
||||||
return { error: "malformed", url }
|
return { error: "malformed", url }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,97 +1,169 @@
|
||||||
{
|
{
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"id": "Stamen.TonerLite",
|
"name": "Americana",
|
||||||
"name": "Toner Lite (by Stamen)",
|
"url": "https://zelonewolf.github.io/openstreetmap-americana/style.json",
|
||||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "americana",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
"text": "Americana",
|
||||||
},
|
"url": "https://github.com/ZeLonewolf/openstreetmap-americana/"
|
||||||
"min_zoom": 0,
|
}
|
||||||
"max_zoom": 20
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Stamen.TonerBackground",
|
"name": "MapTiler Backdrop",
|
||||||
"name": "Toner Background - no labels (by Stamen)",
|
"url": "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png",
|
"id": "maptiler.backdrop",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
"text": "Maptiler",
|
||||||
},
|
"url": "https://www.maptiler.com/copyright/"
|
||||||
"min_zoom": 0,
|
}
|
||||||
"max_zoom": 20
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Stamen.Watercolor",
|
"name": "MapTiler Carto",
|
||||||
"name": "Watercolor (by Stamen)",
|
"url": "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy",
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
"url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png",
|
"id": "maptiler.carto",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "Map tiles by <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — Map data {attribution.OpenStreetMap}"
|
"text": "Maptiler",
|
||||||
},
|
"url": "https://www.maptiler.com/copyright/"
|
||||||
"min_zoom": 0,
|
}
|
||||||
"max_zoom": 20
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.Positron",
|
"name": "Alidade Smooth",
|
||||||
"name": "Positron (by CartoDB)",
|
"url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
|
"category": "osmbasedmap",
|
||||||
|
"id": "alidade.smooth",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "Alidade",
|
||||||
},
|
"url": "https://stadiamaps.com/"
|
||||||
"max_zoom": 20,
|
}
|
||||||
"category": "osmbasedmap"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.PositronNoLabels",
|
"name": "Alidade Smooth Dark",
|
||||||
"name": "Positron - no labels (by CartoDB)",
|
"url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth_dark.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "alidade.smooth_dark",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "Alidade/Stadiamaps",
|
||||||
},
|
"url": "https://stadiamaps.com/"
|
||||||
"max_zoom": 20
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.Voyager",
|
"name": "Stamen Terrain",
|
||||||
"name": "Voyager (by CartoDB)",
|
"url": "https://tiles-eu.stadiamaps.com/styles/stamen_terrain.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "stamen.terrain",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "Stamen/Stadiamaps",
|
||||||
},
|
"url": "https://stadiamaps.com/"
|
||||||
"max_zoom": 20
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.VoyagerNoLabels",
|
"name": "Stamen Toner",
|
||||||
"name": "Voyager - no labels (by CartoDB)",
|
"url": "https://tiles-eu.stadiamaps.com/styles/stamen_toner.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "stamen.toner",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "Stamen/Stadiamaps",
|
||||||
},
|
"url": "https://stadiamaps.com/"
|
||||||
"max_zoom": 20
|
}
|
||||||
|
}, {
|
||||||
|
"name": "Stamen Watercolor",
|
||||||
|
"url": "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "stamen.watercolor",
|
||||||
|
"attribution": {
|
||||||
|
"text": "Stamen/Stadiamaps",
|
||||||
|
"url": "https://stadiamaps.com/"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.DarkMatter",
|
"url": "https://tiles-eu.stadiamaps.com/styles/osm_bright.json",
|
||||||
"name": "Dark Matter (by CartoDB)",
|
"name": "StadiaMaps OSM Bright",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "stadia.bright",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "Stadiamaps",
|
||||||
},
|
"url": "https://stadiamaps.com/"
|
||||||
"max_zoom": 20
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "CartoDB.DarkMatterNoLabels",
|
"url": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
"name": "Dark Matter - no labels (by CartoDB)",
|
"name": "Carto Positron",
|
||||||
"url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
|
|
||||||
"category": "osmbasedmap",
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.positron",
|
||||||
|
"type": "vector",
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
},
|
"url": "https://carto.com/"
|
||||||
"max_zoom": 20
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
|
"name": "Carto Dark Matter",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.dark_matter",
|
||||||
|
"type": "vector",
|
||||||
|
"attribution": {
|
||||||
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
|
"url": "https://carto.com/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
|
"name": "Carto Voyager",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.voyager",
|
||||||
|
"type": "vector",
|
||||||
|
"attribution": {
|
||||||
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
|
"url": "https://carto.com/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
|
"name": "Carto Positron (no labels)",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.positron_no_labels",
|
||||||
|
"type": "vector",
|
||||||
|
"attribution": {
|
||||||
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
|
"url": "https://carto.com/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
|
"name": "Carto Dark Matter (no labels)",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.dark_matter_no_labels",
|
||||||
|
"type": "vector",
|
||||||
|
"attribution": {
|
||||||
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
|
"url": "https://carto.com/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://basemaps.cartocdn.com/gl/voyager-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||||
|
"name": "Carto Voyager (no labels)",
|
||||||
|
"category": "osmbasedmap",
|
||||||
|
"id": "carto.voyager_no_labels",
|
||||||
|
"type": "vector",
|
||||||
|
"attribution": {
|
||||||
|
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||||
|
"url": "https://carto.com/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue