Chore: formatting

This commit is contained in:
Pieter Vander Vennet 2023-09-28 23:50:27 +02:00
parent 8ef9b48e2b
commit 8a3f7a012d
97 changed files with 3350 additions and 2136 deletions

View file

@ -55,7 +55,9 @@
+ [minimap](#minimap) + [minimap](#minimap)
+ [mastodon](#mastodon) + [mastodon](#mastodon)
+ [contact](#contact) + [contact](#contact)
+ [etymology.wikipedia-etymology](#etymologywikipedia-etymology)
+ [denominations-notes](#denominations-notes) + [denominations-notes](#denominations-notes)
+ [single_level](#single_level)
+ [survey_date](#survey_date) + [survey_date](#survey_date)
+ [id_presets.shop_types](#id_presetsshop_types) + [id_presets.shop_types](#id_presetsshop_types)
+ [school.capacity](#schoolcapacity) + [school.capacity](#schoolcapacity)
@ -442,9 +444,9 @@
- fitness_centre - fitness_centre
- food - food
- hackerspace - hackerspace
- indoors
- parking - parking
- picnic_table - picnic_table
- questions
- railway_platforms - railway_platforms
- reception_desk - reception_desk
- shops - shops
@ -858,6 +860,17 @@
### etymology.wikipedia-etymology
- indoors
### denominations-notes ### denominations-notes
@ -871,6 +884,17 @@
### single_level
- questions
### survey_date ### survey_date

View file

@ -61,9 +61,6 @@
+ [just_created](#just_created) + [just_created](#just_created)
+ [leftover-questions](#leftover-questions) + [leftover-questions](#leftover-questions)
+ [all-tags](#all-tags) + [all-tags](#all-tags)
1. [matchpoint](#matchpoint)
- [Basic tags for this layer](#basic-tags-for-this-layer)
- [Supported attributes](#supported-attributes)
1. [import_candidate](#import_candidate) 1. [import_candidate](#import_candidate)
- [Basic tags for this layer](#basic-tags-for-this-layer) - [Basic tags for this layer](#basic-tags-for-this-layer)
- [Supported attributes](#supported-attributes) - [Supported attributes](#supported-attributes)
@ -79,9 +76,13 @@
+ [inbox](#inbox) + [inbox](#inbox)
+ [settings-link](#settings-link) + [settings-link](#settings-link)
+ [logout](#logout) + [logout](#logout)
+ [background-layer-readonly](#background-layer-readonly)
+ [background-layer](#background-layer)
+ [picture-license](#picture-license) + [picture-license](#picture-license)
+ [show_tags](#show_tags) + [show_tags](#show_tags)
+ [all-questions-at-once](#all-questions-at-once) + [all-questions-at-once](#all-questions-at-once)
+ [fixate-north](#fixate-north)
+ [mangrove-keys](#mangrove-keys)
+ [translations-title](#translations-title) + [translations-title](#translations-title)
+ [translation-mode](#translation-mode) + [translation-mode](#translation-mode)
+ [translation-help](#translation-help) + [translation-help](#translation-help)
@ -120,7 +121,6 @@ MapComplete has a few data layers available in the theme which have special prop
- [split_point](#split_point) - [split_point](#split_point)
- [split_road](#split_road) - [split_road](#split_road)
- [current_view](#current_view) - [current_view](#current_view)
- [matchpoint](#matchpoint)
- [import_candidate](#import_candidate) - [import_candidate](#import_candidate)
- [usersettings](#usersettings) - [usersettings](#usersettings)
@ -476,7 +476,9 @@ Meta-layer, simply showing a bbox in red
- This layer is shown at zoomlevel **0** and higher - This layer is shown at zoomlevel **0** and higher
- **This layer is included automatically in every theme. This layer might contain no points** - **This layer is included automatically in every theme. This layer might contain no points**
- This layer is not visible by default and must be enabled in the filter by the user.
- Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable.
- This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true
- Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - Not visible in the layer selection by default. If you want to make this layer toggable, override `name`
- Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`
@ -863,47 +865,6 @@ This tagrendering has no question and is thus read-only
matchpoint
============
<img src='https://mapcomplete.org/./assets/svg/crosshair-empty.svg' height="100px">
The default rendering for a locationInput which snaps onto another object
- This layer is shown at zoomlevel **0** and higher
- This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.
Basic tags for this layer
---------------------------
Elements must have the all of following tags to be shown on this layer:
Supported attributes
----------------------
import_candidate import_candidate
================== ==================
@ -1043,9 +1004,12 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/__url_parameter_initialized:language#values) [__url_parameter_initialized:language](https://wiki.openstreetmap.org/wiki/Key:__url_parameter_initialized:language) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:__url_parameter_initialized:language%3Dyes)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-preferred-background-layer#values) [mapcomplete-preferred-background-layer](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-preferred-background-layer) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3D) [osm](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dosm) [photo](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dphoto) [map](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3Dmap) [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-preferred-background-layer%3D)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-pictures-license#values) [mapcomplete-pictures-license](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-pictures-license) | Multiple choice | [CC0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC0) [CC-BY 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY 4.0) [CC-BY-SA 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY-SA 4.0) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-pictures-license#values) [mapcomplete-pictures-license](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-pictures-license) | Multiple choice | [CC0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC0) [CC-BY 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY 4.0) [CC-BY-SA 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY-SA 4.0)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-show_tags#values) [mapcomplete-show_tags](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show_tags) | Multiple choice | [no](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dno) [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dyes) [full](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dfull) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-show_tags#values) [mapcomplete-show_tags](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show_tags) | Multiple choice | [no](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dno) [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dyes) [full](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_tags%3Dfull)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-show-all-questions#values) [mapcomplete-show-all-questions](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show-all-questions) | Multiple choice | [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dtrue) [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dfalse) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-show-all-questions#values) [mapcomplete-show-all-questions](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show-all-questions) | Multiple choice | [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dtrue) [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dfalse)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-fixate-north#values) [mapcomplete-fixate-north](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-fixate-north) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-fixate-north%3D) [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-fixate-north%3Dyes)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dfalse) [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dtrue) [mobile](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dmobile) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dfalse) [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dtrue) [mobile](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dmobile)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dyes) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dyes)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/_translation_percentage#values) [_translation_percentage](https://wiki.openstreetmap.org/wiki/Key:_translation_percentage) | Multiple choice | [100](https://wiki.openstreetmap.org/wiki/Tag:_translation_percentage%3D100) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/_translation_percentage#values) [_translation_percentage](https://wiki.openstreetmap.org/wiki/Key:_translation_percentage) | Multiple choice | [100](https://wiki.openstreetmap.org/wiki/Tag:_translation_percentage%3D100)
@ -1093,6 +1057,11 @@ This tagrendering has no question and is thus read-only
- *The language was set via an URL-parameter and cannot be set by the user.²* corresponds with `__url_parameter_initialized:language=yes`
### inbox ### inbox
@ -1103,8 +1072,8 @@ This tagrendering has no question and is thus read-only
- *{link(Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages=0` - *{link(Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,,)}* corresponds with `_unreadMessages=0`
- *{link(<b class='alert'>You have &LBRACE_unreadMessages&RBRACE</b><br/>Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages>0` - *{link(<b class='alert'>You have &LBRACE_unreadMessages&RBRACE</b><br/>Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,,)}* corresponds with `_unreadMessages>0`
@ -1129,6 +1098,39 @@ This tagrendering has no question and is thus read-only
### background-layer-readonly
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `_theme:backgroundLayer~.+&mapcomplete-preferred-background-layer~.+&_theme:backgroundLayer!=`
### background-layer
The question is *What background layer should be shown by default?*
- *Use the default background layer* corresponds with ``
- *Use OpenStreetMap-carto as default layer* corresponds with `mapcomplete-preferred-background-layer=osm`
- *Use aerial imagery as default background* corresponds with `mapcomplete-preferred-background-layer=photo`
- *Use a non-openstreetmap based map as default background* corresponds with `mapcomplete-preferred-background-layer=map`
- *Use the current background layer (<span class='code'>{__current_background}</span>) as default background* corresponds with `mapcomplete-preferred-background-layer=`
- *Use background layer <span class='code'>{mapcomplete-preferred-background-layer}</span> as default background* corresponds with `mapcomplete-preferred-background-layer~.+`
- This option cannot be chosen as answer
### picture-license ### picture-license
@ -1184,6 +1186,32 @@ The question is *Should questions for unknown data fields appear one-by-one or
### fixate-north
The question is *Should north always be up?*
- *Allow to rotate the map* corresponds with ``
- *Always keep north pointing up* corresponds with `mapcomplete-fixate-north=yes`
### mangrove-keys
This tagrendering has no question and is thus read-only
### translations-title ### translations-title
@ -1265,8 +1293,8 @@ This tagrendering has no question and is thus read-only
- *A link to your Mastodon-profile has been been found: <a href='{_mastodon_link}' target='_blank'>{_mastodon_link}</a>* corresponds with `_mastodon_link~.+` - *A link to your Mastodon-profile has been been found: <a href='{_mastodon_link}' target='_blank' rel='noopener'>{_mastodon_link}</a>* corresponds with `_mastodon_link~.+`
- *We found a link to what looks to be a mastodon account, but it is unverified. <a href='https://www.openstreetmap.org/profile/edit' target='_blank'>Edit your profile description</a> and place the following there: <span class='code'>&lta href="{_mastodon_candidate}" rel="me"&gtMastodon&lt/a&gt* corresponds with `_mastodon_candidate~.+` - *We found a link to what looks to be a mastodon account, but it is unverified. <a href='https://www.openstreetmap.org/profile/edit' target='_blank' rel='noopener'>Edit your profile description</a> and place the following there: <span class='code'>&lta href="{_mastodon_candidate}" rel="me"&gtMastodon&lt/a&gt* corresponds with `_mastodon_candidate~.+`
@ -1408,6 +1436,7 @@ The following layers are included in MapComplete:
- [dogpark](./Layers/dogpark.md) - [dogpark](./Layers/dogpark.md)
- [drinking_water](./Layers/drinking_water.md) - [drinking_water](./Layers/drinking_water.md)
- [elevator](./Layers/elevator.md) - [elevator](./Layers/elevator.md)
- [elongated_coin](./Layers/elongated_coin.md)
- [entrance](./Layers/entrance.md) - [entrance](./Layers/entrance.md)
- [etymology](./Layers/etymology.md) - [etymology](./Layers/etymology.md)
- [extinguisher](./Layers/extinguisher.md) - [extinguisher](./Layers/extinguisher.md)
@ -1438,8 +1467,8 @@ The following layers are included in MapComplete:
- [map](./Layers/map.md) - [map](./Layers/map.md)
- [maproulette](./Layers/maproulette.md) - [maproulette](./Layers/maproulette.md)
- [maproulette_challenge](./Layers/maproulette_challenge.md) - [maproulette_challenge](./Layers/maproulette_challenge.md)
- [matchpoint](./Layers/matchpoint.md)
- [maxspeed](./Layers/maxspeed.md) - [maxspeed](./Layers/maxspeed.md)
- [memorial](./Layers/memorial.md)
- [named_streets](./Layers/named_streets.md) - [named_streets](./Layers/named_streets.md)
- [nature_reserve](./Layers/nature_reserve.md) - [nature_reserve](./Layers/nature_reserve.md)
- [note](./Layers/note.md) - [note](./Layers/note.md)
@ -1458,6 +1487,7 @@ The following layers are included in MapComplete:
- [postboxes](./Layers/postboxes.md) - [postboxes](./Layers/postboxes.md)
- [postoffices](./Layers/postoffices.md) - [postoffices](./Layers/postoffices.md)
- [public_bookcase](./Layers/public_bookcase.md) - [public_bookcase](./Layers/public_bookcase.md)
- [questions](./Layers/questions.md)
- [railway_platforms](./Layers/railway_platforms.md) - [railway_platforms](./Layers/railway_platforms.md)
- [rainbow_crossings](./Layers/rainbow_crossings.md) - [rainbow_crossings](./Layers/rainbow_crossings.md)
- [range](./Layers/range.md) - [range](./Layers/range.md)
@ -1467,6 +1497,7 @@ The following layers are included in MapComplete:
- [selected_element](./Layers/selected_element.md) - [selected_element](./Layers/selected_element.md)
- [shelter](./Layers/shelter.md) - [shelter](./Layers/shelter.md)
- [shops](./Layers/shops.md) - [shops](./Layers/shops.md)
- [shower](./Layers/shower.md)
- [slow_roads](./Layers/slow_roads.md) - [slow_roads](./Layers/slow_roads.md)
- [speed_camera](./Layers/speed_camera.md) - [speed_camera](./Layers/speed_camera.md)
- [speed_display](./Layers/speed_display.md) - [speed_display](./Layers/speed_display.md)
@ -1487,6 +1518,7 @@ The following layers are included in MapComplete:
- [transit_stops](./Layers/transit_stops.md) - [transit_stops](./Layers/transit_stops.md)
- [tree_node](./Layers/tree_node.md) - [tree_node](./Layers/tree_node.md)
- [usersettings](./Layers/usersettings.md) - [usersettings](./Layers/usersettings.md)
- [vending_machine](./Layers/vending_machine.md)
- [veterinary](./Layers/veterinary.md) - [veterinary](./Layers/veterinary.md)
- [viewpoint](./Layers/viewpoint.md) - [viewpoint](./Layers/viewpoint.md)
- [village_green](./Layers/village_green.md) - [village_green](./Layers/village_green.md)
@ -1497,4 +1529,4 @@ The following layers are included in MapComplete:
- [windturbine](./Layers/windturbine.md) - [windturbine](./Layers/windturbine.md)
This document is autogenerated from [Customizations/AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/develop/Customizations/AllKnownLayouts.ts) This document is autogenerated from [src/Customizations/AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/develop/src/Customizations/AllKnownLayouts.ts)

View file

@ -46,7 +46,8 @@ Special library layer which does not need a '.questions'-prefix before being imp
+ [all_tags](#all_tags) + [all_tags](#all_tags)
+ [just_created](#just_created) + [just_created](#just_created)
+ [multilevels](#multilevels) + [multilevels](#multilevels)
+ [level](#level) + [repeated](#repeated)
+ [single_level](#single_level)
+ [smoking](#smoking) + [smoking](#smoking)
+ [induction-loop](#induction-loop) + [induction-loop](#induction-loop)
+ [internet](#internet) + [internet](#internet)
@ -655,7 +656,21 @@ This is rendered with `This elevator goes to floors {level}`
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -678,6 +693,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### smoking ### smoking

View file

@ -198,7 +198,7 @@ Adds the geometry type as property. This is identical to the GoeJson geometry ty
Extract the 'level'-tag into a normalized, ';'-separated value Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.
@ -260,7 +260,9 @@ To enable this feature, add a field `calculatedTags` in the layer object, e.g.:
"calculatedTags": [ "calculatedTags": [
"_someKey=javascript-expression", "_someKey=javascript-expression (lazy execution)",
"_some_other_key:=javascript expression (strict execution)
"name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",
@ -272,6 +274,12 @@ To enable this feature, add a field `calculatedTags` in the layer object, e.g.:
By using `:=` as separator, the attribute will be calculated as soone as the data is loaded (strict evaluation)
The default behaviour, using `=` as separator, is lazy loading
The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object: The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object:

View file

@ -269,7 +269,21 @@ The question is *Is this vending machine indoors?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -292,6 +306,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### phone ### phone

View file

@ -56,7 +56,6 @@ attribute | type | values which are supported by this layer
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/colour#values) [colour](https://wiki.openstreetmap.org/wiki/Key:colour) | [color](../SpecialInputElements.md#color) | [brown](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dbrown) [green](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgreen) [gray](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgray) [white](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dwhite) [red](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dred) [black](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblack) [blue](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblue) [yellow](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dyellow) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/colour#values) [colour](https://wiki.openstreetmap.org/wiki/Key:colour) | [color](../SpecialInputElements.md#color) | [brown](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dbrown) [green](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgreen) [gray](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dgray) [white](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dwhite) [red](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dred) [black](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblack) [blue](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dblue) [yellow](https://wiki.openstreetmap.org/wiki/Tag:colour%3Dyellow)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/survey:date#values) [survey:date](https://wiki.openstreetmap.org/wiki/Key:survey:date) | [date](../SpecialInputElements.md#date) | [](https://wiki.openstreetmap.org/wiki/Tag:survey:date%3D) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/survey:date#values) [survey:date](https://wiki.openstreetmap.org/wiki/Key:survey:date) | [date](../SpecialInputElements.md#date) | [](https://wiki.openstreetmap.org/wiki/Tag:survey:date%3D)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/inscription#values) [inscription](https://wiki.openstreetmap.org/wiki/Key:inscription) | [text](../SpecialInputElements.md#text) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/inscription#values) [inscription](https://wiki.openstreetmap.org/wiki/Key:inscription) | [text](../SpecialInputElements.md#text) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/tourism#values) [tourism](https://wiki.openstreetmap.org/wiki/Key:tourism) | Multiple choice | [artwork](https://wiki.openstreetmap.org/wiki/Tag:tourism%3Dartwork) [](https://wiki.openstreetmap.org/wiki/Tag:tourism%3D)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/historic#values) [historic](https://wiki.openstreetmap.org/wiki/Key:historic) | Multiple choice | [memorial](https://wiki.openstreetmap.org/wiki/Tag:historic%3Dmemorial) [](https://wiki.openstreetmap.org/wiki/Tag:historic%3D) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/historic#values) [historic](https://wiki.openstreetmap.org/wiki/Key:historic) | Multiple choice | [memorial](https://wiki.openstreetmap.org/wiki/Tag:historic%3Dmemorial) [](https://wiki.openstreetmap.org/wiki/Tag:historic%3D)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/artwork_type#values) [artwork_type](https://wiki.openstreetmap.org/wiki/Key:artwork_type) | [string](../SpecialInputElements.md#string) | [architecture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Darchitecture) [mural](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dmural) [painting](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dpainting) [sculpture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dsculpture) [statue](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstatue) [bust](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dbust) [stone](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstone) [installation](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dinstallation) [graffiti](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dgraffiti) [relief](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Drelief) [azulejo](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dazulejo) [tilework](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dtilework) [woodcarving](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dwoodcarving) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/artwork_type#values) [artwork_type](https://wiki.openstreetmap.org/wiki/Key:artwork_type) | [string](../SpecialInputElements.md#string) | [architecture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Darchitecture) [mural](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dmural) [painting](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dpainting) [sculpture](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dsculpture) [statue](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstatue) [bust](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dbust) [stone](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dstone) [installation](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dinstallation) [graffiti](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dgraffiti) [relief](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Drelief) [azulejo](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dazulejo) [tilework](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dtilework) [woodcarving](https://wiki.openstreetmap.org/wiki/Tag:artwork_type%3Dwoodcarving)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/artist:wikidata#values) [artist:wikidata](https://wiki.openstreetmap.org/wiki/Key:artist:wikidata) | [wikidata](../SpecialInputElements.md#wikidata) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/artist:wikidata#values) [artist:wikidata](https://wiki.openstreetmap.org/wiki/Key:artist:wikidata) | [wikidata](../SpecialInputElements.md#wikidata) |
@ -263,7 +262,9 @@ The question is *Does this bench have an artistic element?*
- *This bench has an integrated artwork* corresponds with `tourism=artwork` - *This bench has an integrated artwork* corresponds with `tourism=artwork`
- *This bench does not have an integrated artwork* corresponds with `` - *This bench does not have an integrated artwork* corresponds with `not:tourism:artwork=yes`
- *This bench <span class="subtle">probably</span> doesn't have an integrated artwork* corresponds with ``
- This option cannot be chosen as answer

View file

@ -254,6 +254,16 @@ This tagrendering has no question and is thus read-only
### delete-button
This tagrendering has no question and is thus read-only
### last_edit ### last_edit

View file

@ -324,7 +324,21 @@ This tagrendering is only visible in the popup if the following condition is met
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -347,6 +361,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### leftover-questions ### leftover-questions

View file

@ -97,7 +97,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -120,6 +134,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### Name ### Name

View file

@ -1933,7 +1933,21 @@ This is rendered with `More info on <a href='{website}'>{website}</a>`
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -1956,6 +1970,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### ref ### ref

View file

@ -1931,7 +1931,21 @@ This is rendered with `More info on <a href='{website}'>{website}</a>`
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -1954,6 +1968,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### ref ### ref

View file

@ -46,13 +46,13 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno)
@ -107,31 +107,6 @@ This tagrendering has no question and is thus read-only
### level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
### Name ### Name
@ -262,6 +237,47 @@ The question is *Which methods of payment are accepted here?*
### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### wheelchair-access ### wheelchair-access
@ -727,7 +743,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
food-category.0 | Has a vegetarian menu (default) | food-category.0 | Restaurants and fast food businesses (default) |
food-category.1 | Only fastfood businesses | amenity=fast_food food-category.1 | Only fastfood businesses | amenity=fast_food
food-category.2 | Only restaurants | amenity=restaurant food-category.2 | Only restaurants | amenity=restaurant
@ -736,14 +752,14 @@ food-category.2 | Only restaurants | amenity=restaurant
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only
@ -772,6 +788,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
accepts_cards.0 | Accepts payment cards | payment:cards=yes accepts_cards.0 | Accepts payment cards | payment:cards=yes
id | question | osmTags
---- | ---------- | ---------
dogs.0 | No preference towards dogs (default) |
dogs.1 | Dogs allowed | dog=unleashed\|dog=yes
dogs.2 | No dogs allowed | dog=no
This document is autogenerated from [assets/themes/pets/pets.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/pets/pets.json) This document is autogenerated from [assets/themes/pets/pets.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/pets/pets.json)

View file

@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### copyshop-print-sizes ### copyshop-print-sizes

View file

@ -297,7 +297,21 @@ The question is *Is the penny press indoors?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -320,6 +334,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### check_date ### check_date

View file

@ -93,7 +93,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -116,6 +130,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### Entrance type ### Entrance type

View file

@ -206,7 +206,21 @@ The question is *Is this place accessible with a wheelchair?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -229,6 +243,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### reviews ### reviews

View file

@ -50,13 +50,13 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno)
@ -111,31 +111,6 @@ This tagrendering has no question and is thus read-only
### level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
### Name ### Name
@ -266,6 +241,47 @@ The question is *Which methods of payment are accepted here?*
### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### wheelchair-access ### wheelchair-access
@ -731,7 +747,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
food-category.0 | Has a vegetarian menu (default) | food-category.0 | Restaurants and fast food businesses (default) |
food-category.1 | Only fastfood businesses | amenity=fast_food food-category.1 | Only fastfood businesses | amenity=fast_food
food-category.2 | Only restaurants | amenity=restaurant food-category.2 | Only restaurants | amenity=restaurant
@ -740,14 +756,14 @@ food-category.2 | Only restaurants | amenity=restaurant
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only
@ -776,6 +792,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
accepts_cards.0 | Accepts payment cards | payment:cards=yes accepts_cards.0 | Accepts payment cards | payment:cards=yes
id | question | osmTags
---- | ---------- | ---------
dogs.0 | No preference towards dogs (default) |
dogs.1 | Dogs allowed | dog=unleashed\|dog=yes
dogs.2 | No dogs allowed | dog=no
This document is autogenerated from [assets/layers/food/food.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/food/food.json) This document is autogenerated from [assets/layers/food/food.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/food/food.json)

View file

@ -46,13 +46,13 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/amenity#values) [amenity](https://wiki.openstreetmap.org/wiki/Key:amenity) | Multiple choice | [fast_food](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Dfast_food) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:amenity%3Drestaurant)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/cuisine#values) [cuisine](https://wiki.openstreetmap.org/wiki/Key:cuisine) | [string](../SpecialInputElements.md#string) | [pizza](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpizza) [friture](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfriture) [pasta](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dpasta) [kebab](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dkebab) [sandwich](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsandwich) [burger](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dburger) [sushi](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dsushi) [coffee](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dcoffee) [italian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Ditalian) [french](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dfrench) [chinese](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dchinese) [greek](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dgreek) [indian](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dindian) [turkish](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dturkish) [thai](https://wiki.openstreetmap.org/wiki/Tag:cuisine%3Dthai)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/reservation#values) [reservation](https://wiki.openstreetmap.org/wiki/Key:reservation) | Multiple choice | [required](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drequired) [recommended](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Drecommended) [yes](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:reservation%3Dno)
@ -107,31 +107,6 @@ This tagrendering has no question and is thus read-only
### level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
### Name ### Name
@ -262,6 +237,47 @@ The question is *Which methods of payment are accepted here?*
### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### wheelchair-access ### wheelchair-access
@ -727,7 +743,7 @@ reservation.0 | Reservation not required | reservation=no\|reservation=optional\
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
food-category.0 | Has a vegetarian menu (default) | food-category.0 | Restaurants and fast food businesses (default) |
food-category.1 | Only fastfood businesses | amenity=fast_food food-category.1 | Only fastfood businesses | amenity=fast_food
food-category.2 | Only restaurants | amenity=restaurant food-category.2 | Only restaurants | amenity=restaurant
@ -736,14 +752,14 @@ food-category.2 | Only restaurants | amenity=restaurant
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegetarian.0 | Has a vegan menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only vegetarian.0 | Has a vegetarian menu | diet:vegetarian=yes\|diet:vegetarian=only\|diet:vegan=yes\|diet:vegan=only
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
vegan.0 | Has a halal menu | diet:vegan=yes\|diet:vegan=only vegan.0 | Has a vegan menu | diet:vegan=yes\|diet:vegan=only
@ -772,6 +788,15 @@ accepts_cash.0 | Accepts cash | payment:cash=yes
id | question | osmTags id | question | osmTags
---- | ---------- | --------- ---- | ---------- | ---------
accepts_cards.0 | Accepts payment cards | payment:cards=yes accepts_cards.0 | Accepts payment cards | payment:cards=yes
id | question | osmTags
---- | ---------- | ---------
dogs.0 | No preference towards dogs (default) |
dogs.1 | Dogs allowed | dog=unleashed\|dog=yes
dogs.2 | No dogs allowed | dog=no
This document is autogenerated from [assets/themes/fritures/fritures.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/fritures/fritures.json) This document is autogenerated from [assets/themes/fritures/fritures.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/fritures/fritures.json)

View file

@ -126,7 +126,21 @@ This is rendered with `This hackerspace is named <b>{name}</b>`
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -149,6 +163,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### website ### website

View file

@ -49,8 +49,12 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/ref#values) [ref](https://wiki.openstreetmap.org/wiki/Key:ref) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/ref#values) [ref](https://wiki.openstreetmap.org/wiki/Key:ref) | [string](../SpecialInputElements.md#string) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/room#values) [room](https://wiki.openstreetmap.org/wiki/Key:room) | Multiple choice | [administration](https://wiki.openstreetmap.org/wiki/Tag:room%3Dadministration) [auditorium](https://wiki.openstreetmap.org/wiki/Tag:room%3Dauditorium) [bedroom](https://wiki.openstreetmap.org/wiki/Tag:room%3Dbedroom) [chapel](https://wiki.openstreetmap.org/wiki/Tag:room%3Dchapel) [class](https://wiki.openstreetmap.org/wiki/Tag:room%3Dclass) [computer](https://wiki.openstreetmap.org/wiki/Tag:room%3Dcomputer) [conference](https://wiki.openstreetmap.org/wiki/Tag:room%3Dconference) [crypt](https://wiki.openstreetmap.org/wiki/Tag:room%3Dcrypt) [kitchen](https://wiki.openstreetmap.org/wiki/Tag:room%3Dkitchen) [laboratory](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlaboratory) [library](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlibrary) [locker](https://wiki.openstreetmap.org/wiki/Tag:room%3Dlocker) [nursery](https://wiki.openstreetmap.org/wiki/Tag:room%3Dnursery) [office](https://wiki.openstreetmap.org/wiki/Tag:room%3Doffice) [prison_cell](https://wiki.openstreetmap.org/wiki/Tag:room%3Dprison_cell) [restaurant](https://wiki.openstreetmap.org/wiki/Tag:room%3Drestaurant) [security_check](https://wiki.openstreetmap.org/wiki/Tag:room%3Dsecurity_check) [sport](https://wiki.openstreetmap.org/wiki/Tag:room%3Dsport) [storage](https://wiki.openstreetmap.org/wiki/Tag:room%3Dstorage) [technical](https://wiki.openstreetmap.org/wiki/Tag:room%3Dtechnical) [toilets](https://wiki.openstreetmap.org/wiki/Tag:room%3Dtoilets) [waiting](https://wiki.openstreetmap.org/wiki/Tag:room%3Dwaiting)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/capacity#values) [capacity](https://wiki.openstreetmap.org/wiki/Key:capacity) | [pnat](../SpecialInputElements.md#pnat) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/name:etymology:wikidata#values) [name:etymology:wikidata](https://wiki.openstreetmap.org/wiki/Key:name:etymology:wikidata) | [wikidata](../SpecialInputElements.md#wikidata) |
@ -86,6 +90,47 @@ This tagrendering has no question and is thus read-only
### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
The question is *On what level is this feature located?*
This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level)
This is rendered with `Located on the {level}th floor`
- *Located underground* corresponds with `location=underground`
- This option cannot be chosen as answer
- *Located on the ground floor* corresponds with `level=0`
- *Located on the ground floor* corresponds with ``
- This option cannot be chosen as answer
- *Located on the first floor* corresponds with `level=1`
- *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### ref ### ref
@ -118,6 +163,74 @@ This tagrendering is only visible in the popup if the following condition is met
### room-type
The question is *What type of room is this?*
- *This is a administrative room* corresponds with `room=administration`
- *This is a auditorium* corresponds with `room=auditorium`
- *This is a bedroom* corresponds with `room=bedroom`
- *This is a chapel* corresponds with `room=chapel`
- *This is a classroom* corresponds with `room=class`
- *This is a classroom* corresponds with `room=classroom`
- This option cannot be chosen as answer
- *This is a computer room* corresponds with `room=computer`
- *This is a conference room* corresponds with `room=conference`
- *This is a crypt* corresponds with `room=crypt`
- *This is a kitchen* corresponds with `room=kitchen`
- *This is a laboratory* corresponds with `room=laboratory`
- *This is a library* corresponds with `room=library`
- *This is a locker room* corresponds with `room=locker`
- *This is a nursery* corresponds with `room=nursery`
- *This is an office* corresponds with `room=office`
- *This is a prison_cell* corresponds with `room=prison_cell`
- *This is a restaurant* corresponds with `room=restaurant`
- *This is a room to perform security checks* corresponds with `room=security_check`
- *This is a sport room* corresponds with `room=sport`
- *This is a storage room* corresponds with `room=storage`
- *This is a technical room* corresponds with `room=technical`
- *These are toilets* corresponds with `room=toilets`
- *This is a waiting room* corresponds with `room=waiting`
### room-capacity
The question is *How much people can at most fit in this room?*
This rendering asks information about the property [capacity](https://wiki.openstreetmap.org/wiki/Key:capacity)
This is rendered with `At most {capacity} people fit this room`
This tagrendering is only visible in the popup if the following condition is met: `room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom`
### wikipedia-etymology
The question is *What is the Wikidata-item that this object is named after?*
This rendering asks information about the property [name:etymology:wikidata](https://wiki.openstreetmap.org/wiki/Key:name:etymology:wikidata)
This is rendered with `<h3>Wikipedia article of the name giver</h3>{wikipedia(name:etymology:wikidata):max-height:20rem}`
### leftover-questions ### leftover-questions

View file

@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### copyshop-print-sizes ### copyshop-print-sizes

View file

@ -88,7 +88,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -111,6 +125,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### parking-type ### parking-type

View file

@ -86,7 +86,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -109,6 +123,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### picnic_table-material ### picnic_table-material

View file

@ -74,7 +74,21 @@ This is rendered with `Platform {ref}`
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -97,6 +111,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### leftover-questions ### leftover-questions

View file

@ -85,7 +85,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -108,6 +122,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### desk-height ### desk-height

View file

@ -15,6 +15,8 @@ Schools giving primary and secondary education and post-secondary, non-tertiary
- This layer is shown at zoomlevel **12** and higher - This layer is shown at zoomlevel **12** and higher
- This layer will automatically load [school](./school.md) into the layout as it depends on it: a calculated tag loads features from this layer (calculatedTag[0] which calculates the value for _enclosing)
- This layer is needed as dependency for layer [school](#school)

View file

@ -389,7 +389,21 @@ The question is *Which methods of payment are accepted here?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -412,6 +426,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### copyshop-print-sizes ### copyshop-print-sizes

View file

@ -401,7 +401,21 @@ The question is *Which methods of payment are accepted here?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -424,6 +438,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### copyshop-print-sizes ### copyshop-print-sizes

View file

@ -88,7 +88,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -111,6 +125,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### access ### access

View file

@ -384,7 +384,21 @@ The question is *Which methods of payment are accepted here?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -407,6 +421,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### copyshop-print-sizes ### copyshop-print-sizes

View file

@ -16,6 +16,7 @@ This layer shows surveillance cameras and allows a contributor to update informa
- This layer is shown at zoomlevel **12** and higher - This layer is shown at zoomlevel **12** and higher
- This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[1]) - This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[1])
- This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[3])
@ -48,6 +49,7 @@ this quick overview is incomplete
attribute | type | values which are supported by this layer attribute | type | values which are supported by this layer
----------- | ------ | ------------------------------------------ ----------- | ------ | ------------------------------------------
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/id#values) [id](https://wiki.openstreetmap.org/wiki/Key:id) | Multiple choice |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/surveillance:type#values) [surveillance:type](https://wiki.openstreetmap.org/wiki/Key:surveillance:type) | Multiple choice | [camera](https://wiki.openstreetmap.org/wiki/Tag:surveillance:type%3Dcamera) [ALPR](https://wiki.openstreetmap.org/wiki/Tag:surveillance:type%3DALPR)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/camera:type#values) [camera:type](https://wiki.openstreetmap.org/wiki/Key:camera:type) | Multiple choice | [fixed](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dfixed) [dome](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Ddome) [panning](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dpanning) [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/camera:type#values) [camera:type](https://wiki.openstreetmap.org/wiki/Key:camera:type) | Multiple choice | [fixed](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dfixed) [dome](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Ddome) [panning](https://wiki.openstreetmap.org/wiki/Tag:camera:type%3Dpanning)
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/camera:direction#values) [camera:direction](https://wiki.openstreetmap.org/wiki/Key:camera:direction) | [direction](../SpecialInputElements.md#direction) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/camera:direction#values) [camera:direction](https://wiki.openstreetmap.org/wiki/Key:camera:direction) | [direction](../SpecialInputElements.md#direction) |
[<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | [<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) |
@ -91,6 +93,22 @@ This tagrendering has no question and is thus read-only
### has_alpr
The question is *Can this camera automatically detect license plates?*
- *This is a camera without number plate recognition.* corresponds with `surveillance:type=camera`
- *This is an ALPR (Automatic License Plate Reader)* corresponds with `surveillance:type=ALPR`
### Camera type: fixed; panning; dome ### Camera type: fixed; panning; dome

View file

@ -86,7 +86,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -109,6 +123,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### operator ### operator

View file

@ -73,7 +73,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -96,6 +110,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### barrier ### barrier

View file

@ -98,7 +98,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -121,6 +135,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### toilet-access ### toilet-access

View file

@ -95,7 +95,21 @@ This tagrendering has no question and is thus read-only
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -118,6 +132,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### toilet-access ### toilet-access

View file

@ -269,7 +269,21 @@ The question is *Is this vending machine indoors?*
### level ### repeated
This tagrendering has no question and is thus read-only
This tagrendering is only visible in the popup if the following condition is met: `repeat_on~.+`
This tagrendering has labels `level`
### single_level
@ -292,6 +306,8 @@ This is rendered with `Located on the {level}th floor`
- *Located on the first basement level* corresponds with `level=-1` - *Located on the first basement level* corresponds with `level=-1`
This tagrendering has labels `level`
### phone ### phone

View file

@ -110,8 +110,12 @@ In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "ar
* [Example usage of image_carousel](#example-usage-of-image_carousel) * [Example usage of image_carousel](#example-usage-of-image_carousel)
+ [image_upload](#image_upload) + [image_upload](#image_upload)
* [Example usage of image_upload](#example-usage-of-image_upload) * [Example usage of image_upload](#example-usage-of-image_upload)
+ [reviews](#reviews) + [rating](#rating)
* [Example usage of reviews](#example-usage-of-reviews) * [Example usage of rating](#example-usage-of-rating)
+ [create_review](#create_review)
* [Example usage of create_review](#example-usage-of-create_review)
+ [list_reviews](#list_reviews)
* [Example usage of list_reviews](#example-usage-of-list_reviews)
+ [opening_hours_table](#opening_hours_table) + [opening_hours_table](#opening_hours_table)
* [Example usage of opening_hours_table](#example-usage-of-opening_hours_table) * [Example usage of opening_hours_table](#example-usage-of-opening_hours_table)
+ [live](#live) + [live](#live)
@ -744,17 +748,49 @@ image_key | image,mapillary,image,wikidata,wikimedia_commons,image,image | The k
name | default | description name | default | description
------ | --------- | ------------- ------ | --------- | -------------
image-key | image | Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added) image-key | _undefined_ | Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)
label | Add image | The text to show on the button label | _undefined_ | The text to show on the button
#### Example usage of image_upload #### Example usage of image_upload
`{image_upload(image,Add image)}` `{image_upload(,)}`
### reviews ### rating
Shows stars which represent the avarage rating on mangrove.reviews
name | default | description
------ | --------- | -------------
subjectKey | name | The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>
fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value
#### Example usage of rating
`{rating(name,)}`
### create_review
Invites the contributor to leave a review. Somewhat small UI-element until interacted
name | default | description
------ | --------- | -------------
subjectKey | name | The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>
fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value
#### Example usage of create_review
`{create_review(name,)}`
### list_reviews
Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten
@ -764,7 +800,7 @@ subjectKey | name | The key to use to determine the subject. If specified, the s
fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value
#### Example usage of reviews #### Example usage of list_reviews
`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used `{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used

View file

@ -247,9 +247,14 @@
"description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches')", "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches')",
"value": "artwork" "value": "artwork"
}, },
{
"key": "not:tourism:artwork",
"description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches')",
"value": "yes"
},
{ {
"key": "tourism", "key": "tourism",
"description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Benches') Picking this answer will delete the key tourism.", "description": "Layer 'Benches' shows with a fixed text, namely 'This bench <span class=\"subtle\">probably</span> doesn't have an integrated artwork' (in the mapcomplete.org theme 'Benches') Picking this answer will delete the key tourism.",
"value": "" "value": ""
}, },
{ {

View file

@ -40,35 +40,6 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Restaurants and fast food')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Restaurants and fast food') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "-1"
},
{ {
"key": "name", "key": "name",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Restaurants and fast food')" "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Restaurants and fast food')"
@ -126,6 +97,35 @@
"description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "yes" "value": "yes"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Restaurants and fast food')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Restaurants and fast food') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",
"value": "-1"
},
{ {
"key": "wheelchair", "key": "wheelchair",
"description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Restaurants and fast food')",

View file

@ -44,35 +44,6 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Fries shop allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Fries shop allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Fries shop' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Fries shops')"
},
{
"key": "location",
"description": "Layer 'Fries shop' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Fries shops')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Fries shops') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "-1"
},
{ {
"key": "name", "key": "name",
"description": "Layer 'Fries shop' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Fries shops')" "description": "Layer 'Fries shop' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Fries shops')"
@ -130,6 +101,35 @@
"description": "Layer 'Fries shop' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", "description": "Layer 'Fries shop' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "yes" "value": "yes"
}, },
{
"key": "level",
"description": "Layer 'Fries shop' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Fries shops')"
},
{
"key": "location",
"description": "Layer 'Fries shop' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Fries shops')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Fries shops') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Fries shop' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",
"value": "-1"
},
{ {
"key": "wheelchair", "key": "wheelchair",
"description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')", "description": "Layer 'Fries shop' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Fries shops')",

View file

@ -55,6 +55,35 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Indoors' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Indoors')"
},
{
"key": "location",
"description": "Layer 'Indoors' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Indoors')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Indoors' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Indoors') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "-1"
},
{ {
"key": "ref", "key": "ref",
"description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)" "description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)"
@ -63,6 +92,129 @@
"key": "name", "key": "name",
"description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)" "description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Indoors') (This is only shown if indoor=room|indoor=area|indoor=corridor)"
}, },
{
"key": "room",
"description": "Layer 'Indoors' shows room=administration with a fixed text, namely 'This is a administrative room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "administration"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=auditorium with a fixed text, namely 'This is a auditorium' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "auditorium"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=bedroom with a fixed text, namely 'This is a bedroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "bedroom"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=chapel with a fixed text, namely 'This is a chapel' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "chapel"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=class with a fixed text, namely 'This is a classroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "class"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=classroom with a fixed text, namely 'This is a classroom' (in the mapcomplete.org theme 'Indoors')",
"value": "classroom"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=computer with a fixed text, namely 'This is a computer room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "computer"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=conference with a fixed text, namely 'This is a conference room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "conference"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=crypt with a fixed text, namely 'This is a crypt' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "crypt"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=kitchen with a fixed text, namely 'This is a kitchen' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "kitchen"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=laboratory with a fixed text, namely 'This is a laboratory' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "laboratory"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=library with a fixed text, namely 'This is a library' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "library"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=locker with a fixed text, namely 'This is a locker room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "locker"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=nursery with a fixed text, namely 'This is a nursery' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "nursery"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=office with a fixed text, namely 'This is an office' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "office"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=prison_cell with a fixed text, namely 'This is a prison_cell' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "prison_cell"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=restaurant with a fixed text, namely 'This is a restaurant' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "restaurant"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=security_check with a fixed text, namely 'This is a room to perform security checks' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "security_check"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=sport with a fixed text, namely 'This is a sport room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "sport"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=storage with a fixed text, namely 'This is a storage room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "storage"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=technical with a fixed text, namely 'This is a technical room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "technical"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=toilets with a fixed text, namely 'These are toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "toilets"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=waiting with a fixed text, namely 'This is a waiting room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Indoors')",
"value": "waiting"
},
{
"key": "capacity",
"description": "Layer 'Indoors' shows and asks freeform values for key 'capacity' (in the mapcomplete.org theme 'Indoors') (This is only shown if room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom)"
},
{
"key": "name:etymology:wikidata",
"description": "Layer 'Indoors' shows and asks freeform values for key 'name:etymology:wikidata' (in the mapcomplete.org theme 'Indoors') (This is only shown if name:etymology!=unknown)"
},
{ {
"key": "highway", "key": "highway",
"description": "The MapComplete theme Indoors has a layer Pedestrian paths showing features with this tag", "description": "The MapComplete theme Indoors has a layer Pedestrian paths showing features with this tag",

View file

@ -668,9 +668,14 @@
"description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature')", "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature')",
"value": "artwork" "value": "artwork"
}, },
{
"key": "not:tourism:artwork",
"description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature')",
"value": "yes"
},
{ {
"key": "tourism", "key": "tourism",
"description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Into nature') Picking this answer will delete the key tourism.", "description": "Layer 'Benches' shows with a fixed text, namely 'This bench <span class=\"subtle\">probably</span> doesn't have an integrated artwork' (in the mapcomplete.org theme 'Into nature') Picking this answer will delete the key tourism.",
"value": "" "value": ""
}, },
{ {

View file

@ -550,35 +550,6 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'OnWheels')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'OnWheels')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'OnWheels') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "-1"
},
{ {
"key": "name", "key": "name",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'OnWheels')" "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'OnWheels')"
@ -636,6 +607,35 @@
"description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "yes" "value": "yes"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'OnWheels')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'OnWheels')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'OnWheels') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",
"value": "-1"
},
{ {
"key": "wheelchair", "key": "wheelchair",
"description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'OnWheels')",

View file

@ -979,9 +979,14 @@
"description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "artwork" "value": "artwork"
}, },
{
"key": "not:tourism:artwork",
"description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "yes"
},
{ {
"key": "tourism", "key": "tourism",
"description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key tourism.", "description": "Layer 'Benches' shows with a fixed text, namely 'This bench <span class=\"subtle\">probably</span> doesn't have an integrated artwork' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key tourism.",
"value": "" "value": ""
}, },
{ {
@ -7305,35 +7310,6 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Restaurants and fast food allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "-1"
},
{ {
"key": "name", "key": "name",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme')" "description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme')"
@ -7391,6 +7367,35 @@
"description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "description": "Layer 'Restaurants and fast food' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "yes" "value": "yes"
}, },
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')"
},
{
"key": "location",
"description": "Layer 'Restaurants and fast food' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Restaurants and fast food' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "-1"
},
{ {
"key": "wheelchair", "key": "wheelchair",
"description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "description": "Layer 'Restaurants and fast food' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
@ -8383,6 +8388,35 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Indoors allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Indoors' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Personal theme')"
},
{
"key": "location",
"description": "Layer 'Indoors' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Personal theme')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Indoors' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Personal theme') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Indoors' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "-1"
},
{ {
"key": "ref", "key": "ref",
"description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)" "description": "Layer 'Indoors' shows and asks freeform values for key 'ref' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)"
@ -8391,6 +8425,129 @@
"key": "name", "key": "name",
"description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)" "description": "Layer 'Indoors' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Personal theme') (This is only shown if indoor=room|indoor=area|indoor=corridor)"
}, },
{
"key": "room",
"description": "Layer 'Indoors' shows room=administration with a fixed text, namely 'This is a administrative room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "administration"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=auditorium with a fixed text, namely 'This is a auditorium' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "auditorium"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=bedroom with a fixed text, namely 'This is a bedroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "bedroom"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=chapel with a fixed text, namely 'This is a chapel' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "chapel"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=class with a fixed text, namely 'This is a classroom' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "class"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=classroom with a fixed text, namely 'This is a classroom' (in the mapcomplete.org theme 'Personal theme')",
"value": "classroom"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=computer with a fixed text, namely 'This is a computer room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "computer"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=conference with a fixed text, namely 'This is a conference room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "conference"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=crypt with a fixed text, namely 'This is a crypt' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "crypt"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=kitchen with a fixed text, namely 'This is a kitchen' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "kitchen"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=laboratory with a fixed text, namely 'This is a laboratory' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "laboratory"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=library with a fixed text, namely 'This is a library' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "library"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=locker with a fixed text, namely 'This is a locker room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "locker"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=nursery with a fixed text, namely 'This is a nursery' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "nursery"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=office with a fixed text, namely 'This is an office' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "office"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=prison_cell with a fixed text, namely 'This is a prison_cell' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "prison_cell"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=restaurant with a fixed text, namely 'This is a restaurant' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "restaurant"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=security_check with a fixed text, namely 'This is a room to perform security checks' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "security_check"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=sport with a fixed text, namely 'This is a sport room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "sport"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=storage with a fixed text, namely 'This is a storage room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "storage"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=technical with a fixed text, namely 'This is a technical room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "technical"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=toilets with a fixed text, namely 'These are toilets' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "toilets"
},
{
"key": "room",
"description": "Layer 'Indoors' shows room=waiting with a fixed text, namely 'This is a waiting room' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "waiting"
},
{
"key": "capacity",
"description": "Layer 'Indoors' shows and asks freeform values for key 'capacity' (in the mapcomplete.org theme 'Personal theme') (This is only shown if room=waiting|room=restaurant|room=office|room=nursery|room=conference|room=auditorium|room=chapel|room=bedroom|room=classroom)"
},
{
"key": "name:etymology:wikidata",
"description": "Layer 'Indoors' shows and asks freeform values for key 'name:etymology:wikidata' (in the mapcomplete.org theme 'Personal theme') (This is only shown if name:etymology!=unknown)"
},
{ {
"key": "information", "key": "information",
"description": "The MapComplete theme Personal theme has a layer Information boards showing features with this tag", "description": "The MapComplete theme Personal theme has a layer Information boards showing features with this tag",
@ -12686,6 +12843,16 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "surveillance:type",
"description": "Layer 'Surveillance camera's' shows surveillance:type=camera with a fixed text, namely 'This is a camera without number plate recognition.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "camera"
},
{
"key": "surveillance:type",
"description": "Layer 'Surveillance camera's' shows surveillance:type=ALPR with a fixed text, namely 'This is an ALPR (Automatic License Plate Reader)' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",
"value": "ALPR"
},
{ {
"key": "camera:type", "key": "camera:type",
"description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')", "description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Personal theme')",

View file

@ -114,35 +114,6 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Dog friendly eateries allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Dog friendly eateries allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')"
},
{
"key": "location",
"description": "Layer 'Dog friendly eateries' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "-1"
},
{ {
"key": "name", "key": "name",
"description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')" "description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'name' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')"
@ -200,6 +171,35 @@
"description": "Layer 'Dog friendly eateries' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", "description": "Layer 'Dog friendly eateries' shows payment:qr_code=yes with a fixed text, namely 'Payment by QR-code is possible here' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "yes" "value": "yes"
}, },
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows and asks freeform values for key 'level' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')"
},
{
"key": "location",
"description": "Layer 'Dog friendly eateries' shows location=underground with a fixed text, namely 'Located underground' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "underground"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "0"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows with a fixed text, namely 'Located on the ground floor' (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities') Picking this answer will delete the key level.",
"value": ""
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "1"
},
{
"key": "level",
"description": "Layer 'Dog friendly eateries' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",
"value": "-1"
},
{ {
"key": "wheelchair", "key": "wheelchair",
"description": "Layer 'Dog friendly eateries' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')", "description": "Layer 'Dog friendly eateries' shows wheelchair=designated with a fixed text, namely 'This place is specially adapted for wheelchair users' and allows to pick this as a default answer (in the mapcomplete.org theme 'Veterinarians, dog parks and other pet-amenities')",

View file

@ -340,9 +340,14 @@
"description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds')", "description": "Layer 'Benches' shows tourism=artwork with a fixed text, namely 'This bench has an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds')",
"value": "artwork" "value": "artwork"
}, },
{
"key": "not:tourism:artwork",
"description": "Layer 'Benches' shows not:tourism:artwork=yes with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds')",
"value": "yes"
},
{ {
"key": "tourism", "key": "tourism",
"description": "Layer 'Benches' shows with a fixed text, namely 'This bench does not have an integrated artwork' and allows to pick this as a default answer (in the mapcomplete.org theme 'Playgrounds') Picking this answer will delete the key tourism.", "description": "Layer 'Benches' shows with a fixed text, namely 'This bench <span class=\"subtle\">probably</span> doesn't have an integrated artwork' (in the mapcomplete.org theme 'Playgrounds') Picking this answer will delete the key tourism.",
"value": "" "value": ""
}, },
{ {

View file

@ -50,6 +50,16 @@
"key": "wikipedia", "key": "wikipedia",
"description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" "description": "The layer 'Surveillance camera's allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary"
}, },
{
"key": "surveillance:type",
"description": "Layer 'Surveillance camera's' shows surveillance:type=camera with a fixed text, namely 'This is a camera without number plate recognition.' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')",
"value": "camera"
},
{
"key": "surveillance:type",
"description": "Layer 'Surveillance camera's' shows surveillance:type=ALPR with a fixed text, namely 'This is an ALPR (Automatic License Plate Reader)' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')",
"value": "ALPR"
},
{ {
"key": "camera:type", "key": "camera:type",
"description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')", "description": "Layer 'Surveillance camera's' shows camera:type=fixed with a fixed text, namely 'A fixed (non-moving) camera' and allows to pick this as a default answer (in the mapcomplete.org theme 'Surveillance under Surveillance')",

View file

@ -346,7 +346,7 @@ This documentation is defined in the source code at [FeatureSwitchState.ts](/src
This documentation is defined in the source code at [FeatureSwitchState.ts](/src/Logic/State/FeatureSwitchState.ts#L199) This documentation is defined in the source code at [FeatureSwitchState.ts](/src/Logic/State/FeatureSwitchState.ts#L199)
The default value is _osm_ No default value set

View file

@ -5294,4 +5294,4 @@
}, },
"neededChangesets": 10 "neededChangesets": 10
} }
} }

View file

@ -1,2 +1,2 @@
SPDX-FileCopyrightText: OpenClipArt SPDX-FileCopyrightText: OpenClipArt
SPDX-License-Identifier: PD SPDX-License-Identifier: PUBLIC-DOMAIN

View file

@ -1,2 +1,2 @@
SPDX-FileCopyrightText: NPS Graphics, converted by User:ZyMOS SPDX-FileCopyrightText: NPS Graphics, converted by User:ZyMOS
SPDX-License-Identifier: PD SPDX-License-Identifier: PUBLIC-DOMAIN

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Mangrove.reviews
SPDX-License-Identifier: LicenseRef-LOGO

16
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.33.1", "version": "0.33.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.33.1", "version": "0.33.5",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@rgossiaux/svelte-headlessui": "^1.0.2", "@rgossiaux/svelte-headlessui": "^1.0.2",
@ -4970,9 +4970,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001538", "version": "1.0.30001541",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz",
"integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -17021,9 +17021,9 @@
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001538", "version": "1.0.30001541",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz",
"integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", "integrity": "sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==",
"dev": true "dev": true
}, },
"canvg": { "canvg": {

View file

@ -8,7 +8,6 @@
"main": "index.ts", "main": "index.ts",
"type": "module", "type": "module",
"config": { "config": {
"#": "Various endpoints that are instance-specific. This is the default configuration, which is re-exported in 'Constants.ts'.",
"#": "Use MAPCOMPLETE_CONFIGURATION to use an additional configuration, e.g. `MAPCOMPLETE_CONFIGURATION=config_hetzner`", "#": "Use MAPCOMPLETE_CONFIGURATION to use an additional configuration, e.g. `MAPCOMPLETE_CONFIGURATION=config_hetzner`",
"#oauth_credentials:comment": [ "#oauth_credentials:comment": [
"`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.", "`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.",

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../UIEventSource"; import { Store, UIEventSource } from "../UIEventSource"
import { RasterLayerPolygon } from "../../Models/RasterLayers"; import { RasterLayerPolygon } from "../../Models/RasterLayers"
/** /**
* Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value. * Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value.
@ -7,40 +7,47 @@ import { RasterLayerPolygon } from "../../Models/RasterLayers";
* It the requested layer is not available, a layer of the same type will be selected. * It the requested layer is not available, a layer of the same type will be selected.
*/ */
export class PreferredRasterLayerSelector { export class PreferredRasterLayerSelector {
private readonly _rasterLayerSetting: UIEventSource<RasterLayerPolygon>; private readonly _rasterLayerSetting: UIEventSource<RasterLayerPolygon>
private readonly _availableLayers: Store<RasterLayerPolygon[]>; private readonly _availableLayers: Store<RasterLayerPolygon[]>
private readonly _preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined>; private readonly _preferredBackgroundLayer: UIEventSource<
private readonly _queryParameter: UIEventSource<string>; string | "photo" | "map" | "osmbasedmap" | undefined
>
private readonly _queryParameter: UIEventSource<string>
constructor(rasterLayerSetting: UIEventSource<RasterLayerPolygon>, availableLayers: Store<RasterLayerPolygon[]>, queryParameter: UIEventSource<string>, preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined>) { constructor(
this._rasterLayerSetting = rasterLayerSetting; rasterLayerSetting: UIEventSource<RasterLayerPolygon>,
this._availableLayers = availableLayers; availableLayers: Store<RasterLayerPolygon[]>,
this._queryParameter = queryParameter; queryParameter: UIEventSource<string>,
this._preferredBackgroundLayer = preferredBackgroundLayer; preferredBackgroundLayer: UIEventSource<
const self = this; string | "photo" | "map" | "osmbasedmap" | undefined
>
) {
this._rasterLayerSetting = rasterLayerSetting
this._availableLayers = availableLayers
this._queryParameter = queryParameter
this._preferredBackgroundLayer = preferredBackgroundLayer
const self = this
this._rasterLayerSetting.addCallbackD(layer => { this._rasterLayerSetting.addCallbackD((layer) => {
if (layer.properties.id !== this._queryParameter.data) { if (layer.properties.id !== this._queryParameter.data) {
this._queryParameter.setData(undefined); this._queryParameter.setData(undefined)
return true; return true
} }
}); })
this._queryParameter.addCallbackAndRunD((_) => {
this._queryParameter.addCallbackAndRunD(_ => { const isApplied = self.updateLayer()
const isApplied = self.updateLayer();
if (!isApplied) { if (!isApplied) {
// A different layer was set as background // A different layer was set as background
// We remove this queryParameter instead // We remove this queryParameter instead
self._queryParameter.setData(undefined); self._queryParameter.setData(undefined)
return true; // Unregister return true // Unregister
} }
}); })
this._preferredBackgroundLayer.addCallbackD(_ => self.updateLayer()); this._preferredBackgroundLayer.addCallbackD((_) => self.updateLayer())
this._availableLayers.addCallbackD(_ => self.updateLayer());
this._availableLayers.addCallbackD((_) => self.updateLayer())
} }
/** /**
@ -48,20 +55,19 @@ export class PreferredRasterLayerSelector {
* @private * @private
*/ */
private updateLayer() { private updateLayer() {
// What is the ID of the layer we have to (try to) load? // What is the ID of the layer we have to (try to) load?
const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data; const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data
const available = this._availableLayers.data; const available = this._availableLayers.data
const isCategory = targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map" const isCategory =
const foundLayer = isCategory ? available.find(l => l.properties.category === targetLayerId) : available.find(l => l.properties.id === targetLayerId); targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map"
const foundLayer = isCategory
? available.find((l) => l.properties.category === targetLayerId)
: available.find((l) => l.properties.id === targetLayerId)
if (foundLayer) { if (foundLayer) {
this._rasterLayerSetting.setData(foundLayer); this._rasterLayerSetting.setData(foundLayer)
return true; return true
} }
// The current layer is not in view // The current layer is not in view
} }
} }

View file

@ -1,159 +1,159 @@
import { ImageUploader } from "./ImageUploader"; import { ImageUploader } from "./ImageUploader"
import LinkImageAction from "../Osm/Actions/LinkImageAction"; import LinkImageAction from "../Osm/Actions/LinkImageAction"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
import { OsmId, OsmTags } from "../../Models/OsmFeature"; import { OsmId, OsmTags } from "../../Models/OsmFeature"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Store, UIEventSource } from "../UIEventSource"; import { Store, UIEventSource } from "../UIEventSource"
import { OsmConnection } from "../Osm/OsmConnection"; import { OsmConnection } from "../Osm/OsmConnection"
import { Changes } from "../Osm/Changes"; import { Changes } from "../Osm/Changes"
import Translations from "../../UI/i18n/Translations"; import Translations from "../../UI/i18n/Translations"
import NoteCommentElement from "../../UI/Popup/NoteCommentElement"; import NoteCommentElement from "../../UI/Popup/NoteCommentElement"
/** /**
* The ImageUploadManager has a * The ImageUploadManager has a
*/ */
export class ImageUploadManager { export class ImageUploadManager {
private readonly _uploader: ImageUploader
private readonly _featureProperties: FeaturePropertiesStore
private readonly _layout: LayoutConfig
private readonly _uploader: ImageUploader; private readonly _uploadStarted: Map<string, UIEventSource<number>> = new Map()
private readonly _featureProperties: FeaturePropertiesStore; private readonly _uploadFinished: Map<string, UIEventSource<number>> = new Map()
private readonly _layout: LayoutConfig; private readonly _uploadFailed: Map<string, UIEventSource<number>> = new Map()
private readonly _uploadRetried: Map<string, UIEventSource<number>> = new Map()
private readonly _uploadRetriedSuccess: Map<string, UIEventSource<number>> = new Map()
private readonly _osmConnection: OsmConnection
private readonly _changes: Changes
private readonly _uploadStarted: Map<string, UIEventSource<number>> = new Map(); constructor(
private readonly _uploadFinished: Map<string, UIEventSource<number>> = new Map(); layout: LayoutConfig,
private readonly _uploadFailed: Map<string, UIEventSource<number>> = new Map(); uploader: ImageUploader,
private readonly _uploadRetried: Map<string, UIEventSource<number>> = new Map(); featureProperties: FeaturePropertiesStore,
private readonly _uploadRetriedSuccess: Map<string, UIEventSource<number>> = new Map(); osmConnection: OsmConnection,
private readonly _osmConnection: OsmConnection; changes: Changes
private readonly _changes: Changes; ) {
this._uploader = uploader
constructor(layout: LayoutConfig, uploader: ImageUploader, featureProperties: FeaturePropertiesStore, osmConnection: OsmConnection, changes: Changes) { this._featureProperties = featureProperties
this._uploader = uploader; this._layout = layout
this._featureProperties = featureProperties; this._osmConnection = osmConnection
this._layout = layout; this._changes = changes
this._osmConnection = osmConnection;
this._changes = changes;
}
/**
* Gets various counters.
* Note that counters can only increase
* If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased
* @param featureId: the id of the feature you want information for. '*' has a global counter
*/
public getCountsFor(featureId: string | "*"): {
retried: Store<number>;
uploadStarted: Store<number>;
retrySuccess: Store<number>;
failed: Store<number>;
uploadFinished: Store<number>
} {
return {
uploadStarted: this.getCounterFor(this._uploadStarted, featureId),
uploadFinished: this.getCounterFor(this._uploadFinished, featureId),
retried: this.getCounterFor(this._uploadRetried, featureId),
failed: this.getCounterFor(this._uploadFailed, featureId),
retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId)
};
}
/**
* Uploads the given image, applies the correct title and license for the known user.
* Will then add this image to the OSM-feature or the OSM-note
*/
public async uploadImageAndApply(file: File, tagsStore: UIEventSource<OsmTags>) : Promise<void>{
const sizeInBytes = file.size;
const tags= tagsStore.data
const featureId = <OsmId>tags.id;
const self = this;
if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
this.increaseCountFor(this._uploadStarted, featureId);
this.increaseCountFor(this._uploadFailed, featureId);
throw (
Translations.t.image.toBig.Subs({
actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
max_size: self._uploader.maxFileSizeInMegabytes + "MB"
}).txt
);
} }
/**
const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0"); * Gets various counters.
const license = licenseStore?.data ?? "CC0"; * Note that counters can only increase
* If a retry was a success, both 'retrySuccess' _and_ 'uploadFinished' will be increased
const matchingLayer = this._layout?.getMatchingLayer(tags); * @param featureId: the id of the feature you want information for. '*' has a global counter
*/
const title = public getCountsFor(featureId: string | "*"): {
matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ?? retried: Store<number>
tags.name ?? uploadStarted: Store<number>
"https//osm.org/" + tags.id; retrySuccess: Store<number>
const description = [ failed: Store<number>
"author:" + this._osmConnection.userDetails.data.name, uploadFinished: Store<number>
"license:" + license, } {
"osmid:" + tags.id return {
].join("\n"); uploadStarted: this.getCounterFor(this._uploadStarted, featureId),
uploadFinished: this.getCounterFor(this._uploadFinished, featureId),
console.log("Upload done, creating "); retried: this.getCounterFor(this._uploadRetried, featureId),
const action = await this.uploadImageWithLicense(featureId, title, description, file); failed: this.getCounterFor(this._uploadFailed, featureId),
if(!isNaN(Number( featureId))){ retrySuccess: this.getCounterFor(this._uploadRetriedSuccess, featureId),
// THis is a map note }
const url = action._url
await this._osmConnection.addCommentToNote(featureId, url)
NoteCommentElement.addCommentTo(url, <UIEventSource<any>> tagsStore, {osmConnection: this._osmConnection})
return
} }
await this._changes.applyAction(action);
}
private async uploadImageWithLicense( /**
featureId: OsmId, * Uploads the given image, applies the correct title and license for the known user.
title: string, description: string, blob: File * Will then add this image to the OSM-feature or the OSM-note
): Promise<LinkImageAction> { */
this.increaseCountFor(this._uploadStarted, featureId); public async uploadImageAndApply(file: File, tagsStore: UIEventSource<OsmTags>): Promise<void> {
const properties = this._featureProperties.getStore(featureId); const sizeInBytes = file.size
let key: string; const tags = tagsStore.data
let value: string; const featureId = <OsmId>tags.id
try { const self = this
({ key, value } = await this._uploader.uploadImage(title, description, blob)); if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
} catch (e) { this.increaseCountFor(this._uploadStarted, featureId)
this.increaseCountFor(this._uploadRetried, featureId); this.increaseCountFor(this._uploadFailed, featureId)
console.error("Could not upload image, trying again:", e); throw Translations.t.image.toBig.Subs({
try { actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
max_size: self._uploader.maxFileSizeInMegabytes + "MB",
}).txt
}
({ key, value } = await this._uploader.uploadImage(title, description, blob)); const licenseStore = this._osmConnection?.GetPreference("pictures-license", "CC0")
this.increaseCountFor(this._uploadRetriedSuccess, featureId); const license = licenseStore?.data ?? "CC0"
} catch (e) {
console.error("Could again not upload image due to", e);
this.increaseCountFor(this._uploadFailed, featureId);
}
const matchingLayer = this._layout?.getMatchingLayer(tags)
const title =
matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.textFor("en") ??
tags.name ??
"https//osm.org/" + tags.id
const description = [
"author:" + this._osmConnection.userDetails.data.name,
"license:" + license,
"osmid:" + tags.id,
].join("\n")
console.log("Upload done, creating ")
const action = await this.uploadImageWithLicense(featureId, title, description, file)
if (!isNaN(Number(featureId))) {
// THis is a map note
const url = action._url
await this._osmConnection.addCommentToNote(featureId, url)
NoteCommentElement.addCommentTo(url, <UIEventSource<any>>tagsStore, {
osmConnection: this._osmConnection,
})
return
}
await this._changes.applyAction(action)
} }
console.log("Uploading done, creating action for", featureId);
const action = new LinkImageAction(featureId, key, value, properties, {
theme: this._layout.id,
changeType: "add-image"
});
this.increaseCountFor(this._uploadFinished, featureId);
return action;
}
private getCounterFor(collection: Map<string, UIEventSource<number>>, key: string | "*") { private async uploadImageWithLicense(
if (this._featureProperties.aliases.has(key)) { featureId: OsmId,
key = this._featureProperties.aliases.get(key); title: string,
description: string,
blob: File
): Promise<LinkImageAction> {
this.increaseCountFor(this._uploadStarted, featureId)
const properties = this._featureProperties.getStore(featureId)
let key: string
let value: string
try {
;({ key, value } = await this._uploader.uploadImage(title, description, blob))
} catch (e) {
this.increaseCountFor(this._uploadRetried, featureId)
console.error("Could not upload image, trying again:", e)
try {
;({ key, value } = await this._uploader.uploadImage(title, description, blob))
this.increaseCountFor(this._uploadRetriedSuccess, featureId)
} catch (e) {
console.error("Could again not upload image due to", e)
this.increaseCountFor(this._uploadFailed, featureId)
}
}
console.log("Uploading done, creating action for", featureId)
const action = new LinkImageAction(featureId, key, value, properties, {
theme: this._layout.id,
changeType: "add-image",
})
this.increaseCountFor(this._uploadFinished, featureId)
return action
} }
if (!collection.has(key)) {
collection.set(key, new UIEventSource<number>(0)); private getCounterFor(collection: Map<string, UIEventSource<number>>, key: string | "*") {
if (this._featureProperties.aliases.has(key)) {
key = this._featureProperties.aliases.get(key)
}
if (!collection.has(key)) {
collection.set(key, new UIEventSource<number>(0))
}
return collection.get(key)
} }
return collection.get(key);
}
private increaseCountFor(collection: Map<string, UIEventSource<number>>, key: string | "*") {
const counter = this.getCounterFor(collection, key);
counter.setData(counter.data + 1);
const global = this.getCounterFor(collection, "*");
global.setData(counter.data + 1);
}
private increaseCountFor(collection: Map<string, UIEventSource<number>>, key: string | "*") {
const counter = this.getCounterFor(collection, key)
counter.setData(counter.data + 1)
const global = this.getCounterFor(collection, "*")
global.setData(counter.data + 1)
}
} }

View file

@ -1,5 +1,5 @@
export interface ImageUploader { export interface ImageUploader {
maxFileSizeInMegabytes?: number; maxFileSizeInMegabytes?: number
/** /**
* Uploads the 'blob' as image, with some metadata. * Uploads the 'blob' as image, with some metadata.
* Returns the URL to be linked + the appropriate key to add this to OSM * Returns the URL to be linked + the appropriate key to add this to OSM
@ -11,5 +11,5 @@ export interface ImageUploader {
title: string, title: string,
description: string, description: string,
blob: File blob: File
): Promise<{ key: string, value: string }>; ): Promise<{ key: string; value: string }>
} }

View file

@ -1,15 +1,15 @@
import ImageProvider, { ProvidedImage } from "./ImageProvider"; import ImageProvider, { ProvidedImage } from "./ImageProvider"
import BaseUIElement from "../../UI/BaseUIElement"; import BaseUIElement from "../../UI/BaseUIElement"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants"
import { LicenseInfo } from "./LicenseInfo"; import { LicenseInfo } from "./LicenseInfo"
import { ImageUploader } from "./ImageUploader"; import { ImageUploader } from "./ImageUploader"
export class Imgur extends ImageProvider implements ImageUploader{ export class Imgur extends ImageProvider implements ImageUploader {
public static readonly defaultValuePrefix = ["https://i.imgur.com"] public static readonly defaultValuePrefix = ["https://i.imgur.com"]
public static readonly singleton = new Imgur() public static readonly singleton = new Imgur()
public readonly defaultKeyPrefixes: string[] = ["image"] public readonly defaultKeyPrefixes: string[] = ["image"]
public readonly maxFileSizeInMegabytes = 10 public readonly maxFileSizeInMegabytes = 10
private constructor() { private constructor() {
super() super()
} }
@ -24,7 +24,7 @@ export class Imgur extends ImageProvider implements ImageUploader{
title: string, title: string,
description: string, description: string,
blob: File blob: File
): Promise<{ key: string, value: string }> { ): Promise<{ key: string; value: string }> {
const apiUrl = "https://api.imgur.com/3/image" const apiUrl = "https://api.imgur.com/3/image"
const apiKey = Constants.ImgurApiKey const apiKey = Constants.ImgurApiKey
@ -33,7 +33,6 @@ export class Imgur extends ImageProvider implements ImageUploader{
formData.append("title", title) formData.append("title", title)
formData.append("description", description) formData.append("description", description)
const settings: RequestInit = { const settings: RequestInit = {
method: "POST", method: "POST",
body: formData, body: formData,

View file

@ -1,15 +1,15 @@
import ChangeTagAction from "./ChangeTagAction"; import ChangeTagAction from "./ChangeTagAction"
import { Tag } from "../../Tags/Tag"; import { Tag } from "../../Tags/Tag"
import OsmChangeAction from "./OsmChangeAction"; import OsmChangeAction from "./OsmChangeAction"
import { Changes } from "../Changes"; import { Changes } from "../Changes"
import { ChangeDescription } from "./ChangeDescription"; import { ChangeDescription } from "./ChangeDescription"
import { Store } from "../../UIEventSource"; import { Store } from "../../UIEventSource"
export default class LinkImageAction extends OsmChangeAction { export default class LinkImageAction extends OsmChangeAction {
private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string; private readonly _proposedKey: "image" | "mapillary" | "wiki_commons" | string
public readonly _url: string; public readonly _url: string
private readonly _currentTags: Store<Record<string, string>>; private readonly _currentTags: Store<Record<string, string>>
private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }; private readonly _meta: { theme: string; changeType: "add-image" | "link-image" }
/** /**
* Adds an image-link to a feature * Adds an image-link to a feature
@ -31,10 +31,10 @@ export default class LinkImageAction extends OsmChangeAction {
} }
) { ) {
super(elementId, true) super(elementId, true)
this._proposedKey = proposedKey; this._proposedKey = proposedKey
this._url = url; this._url = url
this._currentTags = currentTags; this._currentTags = currentTags
this._meta = meta; this._meta = meta
} }
protected CreateChangeDescriptions(): Promise<ChangeDescription[]> { protected CreateChangeDescriptions(): Promise<ChangeDescription[]> {
@ -46,9 +46,12 @@ export default class LinkImageAction extends OsmChangeAction {
key = this._proposedKey + ":" + i key = this._proposedKey + ":" + i
i++ i++
} }
const tagChangeAction = new ChangeTagAction ( this.mainObjectId, new Tag(key, url), currentTags, this._meta) const tagChangeAction = new ChangeTagAction(
this.mainObjectId,
new Tag(key, url),
currentTags,
this._meta
)
return tagChangeAction.CreateChangeDescriptions() return tagChangeAction.CreateChangeDescriptions()
} }
} }

View file

@ -5,7 +5,7 @@ import Locale from "../../UI/i18n/Locale"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { Changes } from "./Changes" import { Changes } from "./Changes"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
export interface ChangesetTag { export interface ChangesetTag {
key: string key: string
@ -30,11 +30,14 @@ export class ChangesetHandler {
constructor( constructor(
dryRun: Store<boolean>, dryRun: Store<boolean>,
osmConnection: OsmConnection, osmConnection: OsmConnection,
allElements: FeaturePropertiesStore | { addAlias: (id0: string, id1: string) => void } | undefined, allElements:
| FeaturePropertiesStore
| { addAlias: (id0: string, id1: string) => void }
| undefined,
changes: Changes changes: Changes
) { ) {
this.osmConnection = osmConnection this.osmConnection = osmConnection
this.allElements = <FeaturePropertiesStore> allElements this.allElements = <FeaturePropertiesStore>allElements
this.changes = changes this.changes = changes
this._dryRun = dryRun this._dryRun = dryRun
this.userDetails = osmConnection.userDetails this.userDetails = osmConnection.userDetails

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,13 @@
import { UIEventSource } from "../UIEventSource"; import { UIEventSource } from "../UIEventSource"
import { LocalStorageSource } from "../Web/LocalStorageSource"; import { LocalStorageSource } from "../Web/LocalStorageSource"
import { QueryParameters } from "../Web/QueryParameters"; import { QueryParameters } from "../Web/QueryParameters"
export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied"
export interface GeoLocationPointProperties extends GeolocationCoordinates { export interface GeoLocationPointProperties extends GeolocationCoordinates {
id: "gps"; id: "gps"
"user:location": "yes"; "user:location": "yes"
date: string; date: string
} }
/** /**
@ -23,22 +23,22 @@ export class GeoLocationState {
*/ */
public readonly permission: UIEventSource<GeolocationPermissionState> = new UIEventSource( public readonly permission: UIEventSource<GeolocationPermissionState> = new UIEventSource(
"prompt" "prompt"
); )
/** /**
* Important to determine e.g. if we move automatically on fix or not * Important to determine e.g. if we move automatically on fix or not
*/ */
public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined); public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined)
/** /**
* If true: the map will center (and re-center) to this location * If true: the map will center (and re-center) to this location
*/ */
public readonly allowMoving: UIEventSource<boolean> = new UIEventSource<boolean>(true); public readonly allowMoving: UIEventSource<boolean> = new UIEventSource<boolean>(true)
/** /**
* The latest GeoLocationCoordinates, as given by the WebAPI * The latest GeoLocationCoordinates, as given by the WebAPI
*/ */
public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> = public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> =
new UIEventSource<GeolocationCoordinates | undefined>(undefined); new UIEventSource<GeolocationCoordinates | undefined>(undefined)
/** /**
* A small flag on localstorage. If the user previously granted the geolocation, it will be set. * A small flag on localstorage. If the user previously granted the geolocation, it will be set.
@ -50,46 +50,46 @@ export class GeoLocationState {
*/ */
private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>( private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>(
LocalStorageSource.Get("geolocation-permissions") LocalStorageSource.Get("geolocation-permissions")
); )
/** /**
* Used to detect a permission retraction * Used to detect a permission retraction
*/ */
private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false); private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false)
constructor() { constructor() {
const self = this; const self = this
this.permission.addCallbackAndRunD(async (state) => { this.permission.addCallbackAndRunD(async (state) => {
if (state === "granted") { if (state === "granted") {
self._previousLocationGrant.setData("true"); self._previousLocationGrant.setData("true")
self._grantedThisSession.setData(true); self._grantedThisSession.setData(true)
} }
if (state === "prompt" && self._grantedThisSession.data) { if (state === "prompt" && self._grantedThisSession.data) {
// This is _really_ weird: we had a grant earlier, but it's 'prompt' now? // This is _really_ weird: we had a grant earlier, but it's 'prompt' now?
// This means that the rights have been revoked again! // This means that the rights have been revoked again!
self._previousLocationGrant.setData("false"); self._previousLocationGrant.setData("false")
self.permission.setData("denied"); self.permission.setData("denied")
self.currentGPSLocation.setData(undefined); self.currentGPSLocation.setData(undefined)
console.warn("Detected a downgrade in permissions!"); console.warn("Detected a downgrade in permissions!")
} }
if (state === "denied") { if (state === "denied") {
self._previousLocationGrant.setData("false"); self._previousLocationGrant.setData("false")
} }
}); })
console.log("Previous location grant:", this._previousLocationGrant.data); console.log("Previous location grant:", this._previousLocationGrant.data)
if (this._previousLocationGrant.data === "true") { if (this._previousLocationGrant.data === "true") {
// A previous visit successfully granted permission. Chance is high that we are allowed to use it again! // A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
// We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
this._previousLocationGrant.setData("false"); this._previousLocationGrant.setData("false")
console.log("Requesting access to GPS as this was previously granted"); console.log("Requesting access to GPS as this was previously granted")
const latLonGivenViaUrl = const latLonGivenViaUrl =
QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon"); QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon")
if (!latLonGivenViaUrl) { if (!latLonGivenViaUrl) {
this.requestMoment.setData(new Date()); this.requestMoment.setData(new Date())
} }
this.requestPermission(); this.requestPermission()
} }
} }
@ -101,37 +101,36 @@ export class GeoLocationState {
public requestPermission() { public requestPermission() {
if (typeof navigator === "undefined") { if (typeof navigator === "undefined") {
// Not compatible with this browser // Not compatible with this browser
this.permission.setData("denied"); this.permission.setData("denied")
return; return
} }
if (this.permission.data !== "prompt" && this.permission.data !== "requested") { if (this.permission.data !== "prompt" && this.permission.data !== "requested") {
// If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well
// Hence that we continue the flow if it is "requested" // Hence that we continue the flow if it is "requested"
return; return
} }
this.permission.setData("requested"); this.permission.setData("requested")
try { try {
navigator?.permissions navigator?.permissions
?.query({ name: "geolocation" }) ?.query({ name: "geolocation" })
.then((status) => { .then((status) => {
const self = this; const self = this
if(status.state === "granted" || status.state === "denied"){ if (status.state === "granted" || status.state === "denied") {
self.permission.setData(status.state) self.permission.setData(status.state)
return return
} }
status.addEventListener("change", (e) => { status.addEventListener("change", (e) => {
self.permission.setData(status.state); self.permission.setData(status.state)
})
});
// The code above might have reset it to 'prompt', but we _did_ request permission! // The code above might have reset it to 'prompt', but we _did_ request permission!
this.permission.setData("requested") this.permission.setData("requested")
// We _must_ call 'startWatching', as that is the actual trigger for the popup... // We _must_ call 'startWatching', as that is the actual trigger for the popup...
self.startWatching(); self.startWatching()
}) })
.catch((e) => console.error("Could not get geopermission", e)); .catch((e) => console.error("Could not get geopermission", e))
} catch (e) { } catch (e) {
console.error("Could not get permission:", e); console.error("Could not get permission:", e)
} }
} }
@ -140,18 +139,18 @@ export class GeoLocationState {
* @private * @private
*/ */
private async startWatching() { private async startWatching() {
const self = this; const self = this
navigator.geolocation.watchPosition( navigator.geolocation.watchPosition(
function(position) { function (position) {
self.currentGPSLocation.setData(position.coords); self.currentGPSLocation.setData(position.coords)
self._previousLocationGrant.setData("true"); self._previousLocationGrant.setData("true")
}, },
function() { function () {
console.warn("Could not get location with navigator.geolocation"); console.warn("Could not get location with navigator.geolocation")
}, },
{ {
enableHighAccuracy: true enableHighAccuracy: true,
} }
); )
} }
} }

View file

@ -1,23 +1,23 @@
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../Osm/OsmConnection"; import { OsmConnection } from "../Osm/OsmConnection"
import { MangroveIdentity } from "../Web/MangroveReviews"; import { MangroveIdentity } from "../Web/MangroveReviews"
import { Store, Stores, UIEventSource } from "../UIEventSource"; import { Store, Stores, UIEventSource } from "../UIEventSource"
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
import { FeatureSource } from "../FeatureSource/FeatureSource"; import { FeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"; import { Feature } from "geojson"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
import translators from "../../assets/translators.json"; import translators from "../../assets/translators.json"
import codeContributors from "../../assets/contributors.json"; import codeContributors from "../../assets/contributors.json"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import usersettings from "../../../src/assets/generated/layers/usersettings.json"; import usersettings from "../../../src/assets/generated/layers/usersettings.json"
import Locale from "../../UI/i18n/Locale"; import Locale from "../../UI/i18n/Locale"
import LinkToWeblate from "../../UI/Base/LinkToWeblate"; import LinkToWeblate from "../../UI/Base/LinkToWeblate"
import FeatureSwitchState from "./FeatureSwitchState"; import FeatureSwitchState from "./FeatureSwitchState"
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants"
import { QueryParameters } from "../Web/QueryParameters"; import { QueryParameters } from "../Web/QueryParameters"
import { ThemeMetaTagging } from "./UserSettingsMetaTagging"; import { ThemeMetaTagging } from "./UserSettingsMetaTagging"
import { MapProperties } from "../../Models/MapProperties"; import { MapProperties } from "../../Models/MapProperties"
/** /**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
@ -42,8 +42,10 @@ export default class UserRelatedState {
public readonly fixateNorth: UIEventSource<undefined | "yes"> public readonly fixateNorth: UIEventSource<undefined | "yes">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string> public readonly language: UIEventSource<string>
public readonly preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined> public readonly preferredBackgroundLayer: UIEventSource<
public readonly imageLicense : UIEventSource<string> string | "photo" | "map" | "osmbasedmap" | undefined
>
public readonly imageLicense: UIEventSource<string>
/** /**
* The number of seconds that the GPS-locations are stored in memory. * The number of seconds that the GPS-locations are stored in memory.
* Time in seconds * Time in seconds
@ -61,7 +63,7 @@ export default class UserRelatedState {
* Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource * Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource
*/ */
public readonly preferencesAsTags: UIEventSource<Record<string, string>> public readonly preferencesAsTags: UIEventSource<Record<string, string>>
private readonly _mapProperties: MapProperties; private readonly _mapProperties: MapProperties
constructor( constructor(
osmConnection: OsmConnection, osmConnection: OsmConnection,
@ -71,7 +73,7 @@ export default class UserRelatedState {
mapProperties?: MapProperties mapProperties?: MapProperties
) { ) {
this.osmConnection = osmConnection this.osmConnection = osmConnection
this._mapProperties = mapProperties; this._mapProperties = mapProperties
{ {
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> = const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
this.osmConnection.GetPreference("translation-mode", "false") this.osmConnection.GetPreference("translation-mode", "false")
@ -104,12 +106,17 @@ export default class UserRelatedState {
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")
) )
this.preferredBackgroundLayer= this.osmConnection.GetPreference("preferred-background-layer", undefined, { this.preferredBackgroundLayer = this.osmConnection.GetPreference(
documentation: "The ID of a layer or layer category that MapComplete uses by default" "preferred-background-layer",
}) undefined,
{
documentation:
"The ID of a layer or layer category that MapComplete uses by default",
}
)
this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", {
documentation: "The license under which new images are uploaded" documentation: "The license under which new images are uploaded",
}) })
this.installedUserThemes = this.InitInstalledUserThemes() this.installedUserThemes = this.InitInstalledUserThemes()
@ -277,7 +284,6 @@ export default class UserRelatedState {
amendedPrefs.data["__url_parameter_initialized:" + key] = "yes" amendedPrefs.data["__url_parameter_initialized:" + key] = "yes"
} }
const osmConnection = this.osmConnection const osmConnection = this.osmConnection
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) { for (const k in newPrefs) {
@ -405,13 +411,11 @@ export default class UserRelatedState {
} }
} }
this._mapProperties?.rasterLayer?.addCallbackAndRun((l) => {
this._mapProperties?.rasterLayer?.addCallbackAndRun(l => {
amendedPrefs.data["__current_background"] = l?.properties?.id amendedPrefs.data["__current_background"] = l?.properties?.id
amendedPrefs.ping() amendedPrefs.ping()
}) })
return amendedPrefs return amendedPrefs
} }
} }

View file

@ -1,14 +1,42 @@
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', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' ) feat.properties._description
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) ) .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
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) ) ?.at(1)
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) )
feat.properties['__current_backgroun'] = 'initial_value' Utils.AddLazyProperty(
} feat.properties,
} "_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/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"
}
}

View file

@ -1,35 +1,34 @@
import { ImmutableStore, Store, UIEventSource } from "../UIEventSource"; import { ImmutableStore, Store, UIEventSource } from "../UIEventSource"
import { MangroveReviews, Review } from "mangrove-reviews-typescript"; import { MangroveReviews, Review } from "mangrove-reviews-typescript"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
import { Feature, Position } from "geojson"; import { Feature, Position } from "geojson"
import { GeoOperations } from "../GeoOperations"; import { GeoOperations } from "../GeoOperations"
export class MangroveIdentity { export class MangroveIdentity {
public readonly keypair: Store<CryptoKeyPair>; public readonly keypair: Store<CryptoKeyPair>
public readonly key_id: Store<string>; public readonly key_id: Store<string>
constructor(mangroveIdentity: UIEventSource<string>) { constructor(mangroveIdentity: UIEventSource<string>) {
const key_id = new UIEventSource<string>(undefined); const key_id = new UIEventSource<string>(undefined)
this.key_id = key_id; this.key_id = key_id
const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined); const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined)
this.keypair = keypairEventSource; this.keypair = keypairEventSource
mangroveIdentity.addCallbackAndRunD(async (data) => { mangroveIdentity.addCallbackAndRunD(async (data) => {
if (data === "") { if (data === "") {
return; return
} }
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)); const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data))
keypairEventSource.setData(keypair); keypairEventSource.setData(keypair)
const pem = await MangroveReviews.publicToPem(keypair.publicKey); const pem = await MangroveReviews.publicToPem(keypair.publicKey)
key_id.setData(pem); key_id.setData(pem)
}); })
try { try {
if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") { if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") {
MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => { MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {})
});
} }
} catch (e) { } catch (e) {
console.error("Could not create identity: ", e); console.error("Could not create identity: ", e)
} }
} }
@ -39,13 +38,13 @@ export class MangroveIdentity {
* @constructor * @constructor
*/ */
private static async CreateIdentity(identity: UIEventSource<string>): Promise<void> { private static async CreateIdentity(identity: UIEventSource<string>): Promise<void> {
const keypair = await MangroveReviews.generateKeypair(); const keypair = await MangroveReviews.generateKeypair()
const jwk = await MangroveReviews.keypairToJwk(keypair); const jwk = await MangroveReviews.keypairToJwk(keypair)
if ((identity.data ?? "") !== "") { if ((identity.data ?? "") !== "") {
// Identity has been loaded via osmPreferences by now - we don't overwrite // Identity has been loaded via osmPreferences by now - we don't overwrite
return; return
} }
identity.setData(JSON.stringify(jwk)); identity.setData(JSON.stringify(jwk))
} }
} }
@ -53,18 +52,18 @@ export class MangroveIdentity {
* Tracks all reviews of a given feature, allows to create a new review * Tracks all reviews of a given feature, allows to create a new review
*/ */
export default class FeatureReviews { export default class FeatureReviews {
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}; private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}
public readonly subjectUri: Store<string>; public readonly subjectUri: Store<string>
public readonly average: Store<number | null>; public readonly average: Store<number | null>
private readonly _reviews: UIEventSource<(Review & { madeByLoggedInUser: Store<boolean> })[]> = private readonly _reviews: UIEventSource<(Review & { madeByLoggedInUser: Store<boolean> })[]> =
new UIEventSource([]); new UIEventSource([])
public readonly reviews: Store<(Review & { madeByLoggedInUser: Store<boolean> })[]> = public readonly reviews: Store<(Review & { madeByLoggedInUser: Store<boolean> })[]> =
this._reviews; this._reviews
private readonly _lat: number; private readonly _lat: number
private readonly _lon: number; private readonly _lon: number
private readonly _uncertainty: number; private readonly _uncertainty: number
private readonly _name: Store<string>; private readonly _name: Store<string>
private readonly _identity: MangroveIdentity; private readonly _identity: MangroveIdentity
private constructor( private constructor(
feature: Feature, feature: Feature,
@ -77,72 +76,72 @@ export default class FeatureReviews {
} }
) { ) {
const centerLonLat = GeoOperations.centerpointCoordinates(feature) const centerLonLat = GeoOperations.centerpointCoordinates(feature)
;[this._lon, this._lat] = centerLonLat; ;[this._lon, this._lat] = centerLonLat
this._identity = this._identity =
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined)); mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
const nameKey = options?.nameKey ?? "name"; const nameKey = options?.nameKey ?? "name"
if (feature.geometry.type === "Point") { if (feature.geometry.type === "Point") {
this._uncertainty = options?.uncertaintyRadius ?? 10; this._uncertainty = options?.uncertaintyRadius ?? 10
} else { } else {
let coordss: Position[][]; let coordss: Position[][]
if (feature.geometry.type === "LineString") { if (feature.geometry.type === "LineString") {
coordss = [feature.geometry.coordinates]; coordss = [feature.geometry.coordinates]
} else if ( } else if (
feature.geometry.type === "MultiLineString" || feature.geometry.type === "MultiLineString" ||
feature.geometry.type === "Polygon" feature.geometry.type === "Polygon"
) { ) {
coordss = feature.geometry.coordinates; coordss = feature.geometry.coordinates
} }
let maxDistance = 0; let maxDistance = 0
for (const coords of coordss) { for (const coords of coordss) {
for (const coord of coords) { for (const coord of coords) {
maxDistance = Math.max( maxDistance = Math.max(
maxDistance, maxDistance,
GeoOperations.distanceBetween(centerLonLat, coord) GeoOperations.distanceBetween(centerLonLat, coord)
); )
} }
} }
this._uncertainty = options?.uncertaintyRadius ?? maxDistance; this._uncertainty = options?.uncertaintyRadius ?? maxDistance
} }
this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName); this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName)
this.subjectUri = this.ConstructSubjectUri(); this.subjectUri = this.ConstructSubjectUri()
const self = this; const self = this
this.subjectUri.addCallbackAndRunD(async (sub) => { this.subjectUri.addCallbackAndRunD(async (sub) => {
const reviews = await MangroveReviews.getReviews({ sub }); const reviews = await MangroveReviews.getReviews({ sub })
self.addReviews(reviews.reviews); self.addReviews(reviews.reviews)
}); })
/* We also construct all subject queries _without_ encoding the name to work around a previous bug /* We also construct all subject queries _without_ encoding the name to work around a previous bug
* See https://github.com/giggls/opencampsitemap/issues/30 * See https://github.com/giggls/opencampsitemap/issues/30
*/ */
this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => { this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => {
try { try {
const reviews = await MangroveReviews.getReviews({ sub }); const reviews = await MangroveReviews.getReviews({ sub })
self.addReviews(reviews.reviews); self.addReviews(reviews.reviews)
} catch (e) { } catch (e) {
console.log("Could not fetch reviews for partially incorrect query ", sub); console.log("Could not fetch reviews for partially incorrect query ", sub)
} }
}); })
this.average = this._reviews.map(reviews => { this.average = this._reviews.map((reviews) => {
if (!reviews) { if (!reviews) {
return null;
}
if(reviews.length === 0){
return null return null
} }
let sum = 0; if (reviews.length === 0) {
let count = 0; return null
}
let sum = 0
let count = 0
for (const review of reviews) { for (const review of reviews) {
if (review.rating !== undefined) { if (review.rating !== undefined) {
count++; count++
sum += review.rating; sum += review.rating
} }
} }
return Math.round(sum / count) return Math.round(sum / count)
}); })
} }
/** /**
@ -158,14 +157,14 @@ export default class FeatureReviews {
uncertaintyRadius?: number uncertaintyRadius?: number
} }
) { ) {
const key = feature.properties.id; const key = feature.properties.id
const cached = FeatureReviews._featureReviewsCache[key]; const cached = FeatureReviews._featureReviewsCache[key]
if (cached !== undefined) { if (cached !== undefined) {
return cached; return cached
} }
const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options); const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options)
FeatureReviews._featureReviewsCache[key] = featureReviews; FeatureReviews._featureReviewsCache[key] = featureReviews
return featureReviews; return featureReviews
} }
/** /**
@ -174,15 +173,15 @@ export default class FeatureReviews {
public async createReview(review: Omit<Review, "sub">): Promise<void> { public async createReview(review: Omit<Review, "sub">): Promise<void> {
const r: Review = { const r: Review = {
sub: this.subjectUri.data, sub: this.subjectUri.data,
...review ...review,
}; }
const keypair: CryptoKeyPair = this._identity.keypair.data; const keypair: CryptoKeyPair = this._identity.keypair.data
console.log(r); console.log(r)
const jwt = await MangroveReviews.signReview(keypair, r); const jwt = await MangroveReviews.signReview(keypair, r)
console.log("Signed:", jwt); console.log("Signed:", jwt)
await MangroveReviews.submitReview(jwt); await MangroveReviews.submitReview(jwt)
this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) }); this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) })
this._reviews.ping(); this._reviews.ping()
} }
/** /**
@ -191,48 +190,48 @@ export default class FeatureReviews {
* @private * @private
*/ */
private addReviews(reviews: { payload: Review; kid: string }[]) { private addReviews(reviews: { payload: Review; kid: string }[]) {
const self = this; const self = this
const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion)); const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion))
let hasNew = false; let hasNew = false
for (const reviewData of reviews) { for (const reviewData of reviews) {
const review = reviewData.payload; const review = reviewData.payload
try { try {
const url = new URL(review.sub); const url = new URL(review.sub)
console.log("URL is", url); console.log("URL is", url)
if (url.protocol === "geo:") { if (url.protocol === "geo:") {
const coordinate = <[number, number]>( const coordinate = <[number, number]>(
url.pathname.split(",").map((n) => Number(n)) url.pathname.split(",").map((n) => Number(n))
); )
const distance = GeoOperations.distanceBetween( const distance = GeoOperations.distanceBetween(
[this._lat, this._lon], [this._lat, this._lon],
coordinate coordinate
); )
if (distance > this._uncertainty) { if (distance > this._uncertainty) {
continue; continue
} }
} }
} catch (e) { } catch (e) {
console.warn(e); console.warn(e)
} }
const key = review.rating + " " + review.opinion; const key = review.rating + " " + review.opinion
if (alreadyKnown.has(key)) { if (alreadyKnown.has(key)) {
continue; continue
} }
self._reviews.data.push({ self._reviews.data.push({
...review, ...review,
madeByLoggedInUser: this._identity.key_id.map((user_key_id) => { madeByLoggedInUser: this._identity.key_id.map((user_key_id) => {
return reviewData.kid === user_key_id; return reviewData.kid === user_key_id
}) }),
}); })
hasNew = true; hasNew = true
} }
if (hasNew) { if (hasNew) {
self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first
self._reviews.ping(); self._reviews.ping()
} }
} }
@ -245,13 +244,13 @@ export default class FeatureReviews {
private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> { private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> {
// https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2 // https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2
// `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3 // `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3
const self = this; const self = this
return this._name.map(function(name) { return this._name.map(function (name) {
let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`; let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`
if (name) { if (name) {
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name)); uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))
} }
return uri; return uri
}); })
} }
} }

View file

@ -1,41 +1,41 @@
import { Feature, Polygon } from "geojson"; import { Feature, Polygon } from "geojson"
import * as editorlayerindex from "../assets/editor-layer-index.json"; import * as editorlayerindex from "../assets/editor-layer-index.json"
import * as globallayers from "../assets/global-raster-layers.json"; import * as globallayers from "../assets/global-raster-layers.json"
import { BBox } from "../Logic/BBox"; import { BBox } from "../Logic/BBox"
import { Store, Stores } from "../Logic/UIEventSource"; import { Store, Stores } from "../Logic/UIEventSource"
import { GeoOperations } from "../Logic/GeoOperations"; import { GeoOperations } from "../Logic/GeoOperations"
import { RasterLayerProperties } from "./RasterLayerProperties"; import { RasterLayerProperties } from "./RasterLayerProperties"
export class AvailableRasterLayers { export class AvailableRasterLayers {
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> & public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
RasterLayerPolygon)[] = <any>editorlayerindex.features; RasterLayerPolygon)[] = <any>editorlayerindex.features
public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map(
(properties) => (properties) =>
<RasterLayerPolygon>{ <RasterLayerPolygon>{
type: "Feature", type: "Feature",
properties, properties,
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
} }
); )
public static readonly osmCartoProperties: RasterLayerProperties = { public static readonly osmCartoProperties: RasterLayerProperties = {
id: "osm", id: "osm",
name: "OpenStreetMap", name: "OpenStreetMap",
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: { attribution: {
text: "OpenStreetMap", text: "OpenStreetMap",
url: "https://openStreetMap.org/copyright" url: "https://openStreetMap.org/copyright",
}, },
best: true, best: true,
max_zoom: 19, max_zoom: 19,
min_zoom: 0, min_zoom: 0,
category: "osmbasedmap" category: "osmbasedmap",
}; }
public static readonly osmCarto: RasterLayerPolygon = { public static readonly osmCarto: RasterLayerPolygon = {
type: "Feature", type: "Feature",
properties: AvailableRasterLayers.osmCartoProperties, properties: AvailableRasterLayers.osmCartoProperties,
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
}; }
public static readonly maptilerDefaultLayer: RasterLayerPolygon = { public static readonly maptilerDefaultLayer: RasterLayerPolygon = {
type: "Feature", type: "Feature",
@ -47,11 +47,11 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Maptiler", text: "Maptiler",
url: "https://www.maptiler.com/copyright/" url: "https://www.maptiler.com/copyright/",
} },
}, },
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
}; }
public static readonly maptilerCarto: RasterLayerPolygon = { public static readonly maptilerCarto: RasterLayerPolygon = {
type: "Feature", type: "Feature",
@ -63,11 +63,11 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Maptiler", text: "Maptiler",
url: "https://www.maptiler.com/copyright/" url: "https://www.maptiler.com/copyright/",
} },
}, },
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
}; }
public static readonly maptilerBackdrop: RasterLayerPolygon = { public static readonly maptilerBackdrop: RasterLayerPolygon = {
type: "Feature", type: "Feature",
@ -79,11 +79,11 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Maptiler", text: "Maptiler",
url: "https://www.maptiler.com/copyright/" url: "https://www.maptiler.com/copyright/",
} },
}, },
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
}; }
public static readonly americana: RasterLayerPolygon = { public static readonly americana: RasterLayerPolygon = {
type: "Feature", type: "Feature",
properties: { properties: {
@ -94,43 +94,45 @@ export class AvailableRasterLayers {
type: "vector", type: "vector",
attribution: { attribution: {
text: "Americana", text: "Americana",
url: "https://github.com/ZeLonewolf/openstreetmap-americana/" url: "https://github.com/ZeLonewolf/openstreetmap-americana/",
} },
}, },
geometry: BBox.global.asGeometry() geometry: BBox.global.asGeometry(),
}; }
public static layersAvailableAt( public static layersAvailableAt(
location: Store<{ lon: number; lat: number }> location: Store<{ lon: number; lat: number }>
): Store<RasterLayerPolygon[]> { ): Store<RasterLayerPolygon[]> {
const availableLayersBboxes = Stores.ListStabilized( const availableLayersBboxes = Stores.ListStabilized(
location.mapD((loc) => { location.mapD((loc) => {
const lonlat: [number, number] = [loc.lon, loc.lat]; const lonlat: [number, number] = [loc.lon, loc.lat]
return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) =>
BBox.get(eliPolygon).contains(lonlat) BBox.get(eliPolygon).contains(lonlat)
); )
}) })
); )
const available = Stores.ListStabilized( const available = 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]
const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => {
if (eliPolygon.geometry === null) { if (eliPolygon.geometry === null) {
return true; // global ELI-layer return true // global ELI-layer
} }
return GeoOperations.inside(lonlat, eliPolygon); return GeoOperations.inside(lonlat, eliPolygon)
}); })
matching.push(...AvailableRasterLayers.globalLayers); matching.push(...AvailableRasterLayers.globalLayers)
matching.unshift(AvailableRasterLayers.maptilerDefaultLayer, matching.unshift(
AvailableRasterLayers.maptilerDefaultLayer,
AvailableRasterLayers.osmCarto, AvailableRasterLayers.osmCarto,
AvailableRasterLayers.maptilerCarto, AvailableRasterLayers.maptilerCarto,
AvailableRasterLayers.maptilerBackdrop, AvailableRasterLayers.maptilerBackdrop,
AvailableRasterLayers.americana); AvailableRasterLayers.americana
return matching; )
return matching
}) })
); )
return available; return available
} }
} }
@ -148,22 +150,22 @@ export class RasterLayerUtils {
preferredCategory: string, preferredCategory: string,
ignoreLayer?: RasterLayerPolygon ignoreLayer?: RasterLayerPolygon
): RasterLayerPolygon { ): RasterLayerPolygon {
let secondBest: RasterLayerPolygon = undefined; let secondBest: RasterLayerPolygon = undefined
for (const rasterLayer of available) { for (const rasterLayer of available) {
if (rasterLayer === ignoreLayer) { if (rasterLayer === ignoreLayer) {
continue; continue
} }
const p = rasterLayer.properties; const p = rasterLayer.properties
if (p.category === preferredCategory) { if (p.category === preferredCategory) {
if (p.best) { if (p.best) {
return rasterLayer; return rasterLayer
} }
if (!secondBest) { if (!secondBest) {
secondBest = rasterLayer; secondBest = rasterLayer
} }
} }
} }
return secondBest; return secondBest
} }
} }
@ -179,11 +181,11 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
/** /**
* The name of the imagery source * The name of the imagery source
*/ */
readonly name: string; readonly name: string
/** /**
* Whether the imagery name should be translated * Whether the imagery name should be translated
*/ */
readonly i18n?: boolean; readonly i18n?: boolean
readonly type: readonly type:
| "tms" | "tms"
| "wms" | "wms"
@ -191,7 +193,7 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
| "scanex" | "scanex"
| "wms_endpoint" | "wms_endpoint"
| "wmts" | "wmts"
| "vector"; /* Vector is not actually part of the ELI-spec, we add it for vector layers */ | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */
/** /**
* A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories.
*/ */
@ -203,53 +205,53 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
| "historicphoto" | "historicphoto"
| "qa" | "qa"
| "elevation" | "elevation"
| "other"; | "other"
/** /**
* A URL template for imagery tiles * A URL template for imagery tiles
*/ */
readonly url: string; readonly url: string
readonly min_zoom?: number; readonly min_zoom?: number
readonly max_zoom?: number; readonly max_zoom?: number
/** /**
* explicit/implicit permission by the owner for use in OSM * explicit/implicit permission by the owner for use in OSM
*/ */
readonly permission_osm?: "explicit" | "implicit" | "no"; readonly permission_osm?: "explicit" | "implicit" | "no"
/** /**
* A URL for the license or permissions for the imagery * A URL for the license or permissions for the imagery
*/ */
readonly license_url?: string; readonly license_url?: string
/** /**
* A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery.
*/ */
readonly privacy_policy_url?: string | boolean; readonly privacy_policy_url?: string | boolean
/** /**
* A unique identifier for the source; used in imagery_used changeset tag * A unique identifier for the source; used in imagery_used changeset tag
*/ */
readonly id: string; readonly id: string
/** /**
* A short English-language description of the source * A short English-language description of the source
*/ */
readonly description?: string; readonly description?: string
/** /**
* The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple.
*/ */
readonly country_code?: string; readonly country_code?: string
/** /**
* Whether this imagery should be shown in the default world-wide menu * Whether this imagery should be shown in the default world-wide menu
*/ */
readonly default?: boolean; readonly default?: boolean
/** /**
* Whether this imagery is the best source for the region * Whether this imagery is the best source for the region
*/ */
readonly best?: boolean; readonly best?: boolean
/** /**
* The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one
*/ */
readonly start_date?: string; readonly start_date?: string
/** /**
* The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one
*/ */
readonly end_date?: string; readonly end_date?: string
/** /**
* HTTP header to check for information if the tile is invalid * HTTP header to check for information if the tile is invalid
*/ */
@ -259,61 +261,61 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
* via the `patternProperty` "^.*$". * via the `patternProperty` "^.*$".
*/ */
[k: string]: string[] | null [k: string]: string[] | null
}; }
/** /**
* 'true' if tiles are transparent and can be overlaid on another source * 'true' if tiles are transparent and can be overlaid on another source
*/ */
readonly overlay?: boolean & string; readonly overlay?: boolean & string
readonly available_projections?: string[]; readonly available_projections?: string[]
readonly attribution?: { readonly attribution?: {
readonly url?: string readonly url?: string
readonly text?: string readonly text?: string
readonly html?: string readonly html?: string
readonly required?: boolean readonly required?: boolean
}; }
/** /**
* A URL for an image, that can be displayed in the list of imagery layers next to the name * A URL for an image, that can be displayed in the list of imagery layers next to the name
*/ */
readonly icon?: string; readonly icon?: string
/** /**
* A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text.
*/ */
readonly eula?: string; readonly eula?: string
/** /**
* A URL for an image, that is displayed in the mapview for attribution * A URL for an image, that is displayed in the mapview for attribution
*/ */
readonly "logo-image"?: string; readonly "logo-image"?: string
/** /**
* Customized text for the terms of use link (default is "Background Terms of Use") * Customized text for the terms of use link (default is "Background Terms of Use")
*/ */
readonly "terms-of-use-text"?: string; readonly "terms-of-use-text"?: string
/** /**
* Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm.
*/ */
readonly "no-tile-checksum"?: string; readonly "no-tile-checksum"?: string
/** /**
* header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog
*/ */
readonly "metadata-header"?: string; readonly "metadata-header"?: string
/** /**
* Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too.
*/ */
readonly "valid-georeference"?: boolean; readonly "valid-georeference"?: boolean
/** /**
* Size of individual tiles delivered by a TMS service * Size of individual tiles delivered by a TMS service
*/ */
readonly "tile-size"?: number; readonly "tile-size"?: number
/** /**
* Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty.
*/ */
readonly "mod-tile-features"?: string; readonly "mod-tile-features"?: string
/** /**
* HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times.
*/ */
readonly "custom-http-headers"?: { readonly "custom-http-headers"?: {
readonly "header-name"?: string readonly "header-name"?: string
readonly "header-value"?: string readonly "header-value"?: string
}; }
/** /**
* Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute)
*/ */
@ -324,17 +326,17 @@ export interface EditorLayerIndexProperties extends RasterLayerProperties {
[k: string]: unknown [k: string]: unknown
} }
[k: string]: unknown [k: string]: unknown
}[]; }[]
/** /**
* format to use when connecting tile server (when using WMS_ENDPOINT type) * format to use when connecting tile server (when using WMS_ENDPOINT type)
*/ */
readonly format?: string; readonly format?: string
/** /**
* If `true` transparent tiles will be requested from WMS server * If `true` transparent tiles will be requested from WMS server
*/ */
readonly transparent?: boolean & string; readonly transparent?: boolean & string
/** /**
* minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid
*/ */
readonly "minimum-tile-expire"?: number; readonly "minimum-tile-expire"?: number
} }

View file

@ -4,7 +4,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import { LayerConfigJson } from "../Json/LayerConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson"
import { DesugaringStep, Each, Fuse, On } from "./Conversion" import { DesugaringStep, Each, Fuse, On } from "./Conversion"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
import { del } from "idb-keyval"; import { del } from "idb-keyval"
export class UpdateLegacyLayer extends DesugaringStep< export class UpdateLegacyLayer extends DesugaringStep<
LayerConfigJson | string | { builtin; override } LayerConfigJson | string | { builtin; override }
@ -197,7 +197,7 @@ class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> {
delete oldThemeConfig.socialImage delete oldThemeConfig.socialImage
} }
if(oldThemeConfig.defaultBackgroundId === "osm"){ if (oldThemeConfig.defaultBackgroundId === "osm") {
console.log("Removing old background in", json.id) console.log("Removing old background in", json.id)
} }

View file

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store"
/** /**
* For some stupid reason, it is very hard to bind inputs * For some stupid reason, it is very hard to bind inputs
*/ */
export let selected: Writable<boolean>; export let selected: Writable<boolean>
let _c: boolean = selected.data ?? true; let _c: boolean = selected.data ?? true
$: selected.set(_c); $: selected.set(_c)
</script> </script>
<label class="no-image-background flex gap-1"> <label class="no-image-background flex gap-1">
<input bind:checked={_c} type="checkbox" /> <input bind:checked={_c} type="checkbox" />
<slot /> <slot />

View file

@ -1,40 +1,48 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"
import { twMerge } from "tailwind-merge"
import { createEventDispatcher } from "svelte"; export let accept: string
import { twMerge } from "tailwind-merge"; export let multiple: boolean = true
export let accept: string; const dispatcher = createEventDispatcher<{ submit: FileList }>()
export let multiple: boolean = true; export let cls: string = ""
let drawAttention = false
const dispatcher = createEventDispatcher<{ submit: FileList }>(); let inputElement: HTMLInputElement
export let cls: string = ""; let id = Math.random() * 1000000000 + ""
let drawAttention = false;
let inputElement: HTMLInputElement;
let id = Math.random() * 1000000000 + "";
</script> </script>
<form> <form>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput"+id}> <label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
<slot /> <slot />
</label> </label>
<input {accept} bind:this={inputElement} class="hidden" id={"fileinput" + id} {multiple} name="file-input" <input
on:change|preventDefault={() => { {accept}
drawAttention = false; bind:this={inputElement}
dispatcher("submit", inputElement.files)}} class="hidden"
id={"fileinput" + id}
on:dragend={ () => {drawAttention = false}} {multiple}
on:dragover|preventDefault|stopPropagation={(e) => { name="file-input"
console.log("Dragging over!") on:change|preventDefault={() => {
drawAttention = true drawAttention = false
e.dataTransfer.drop = "copy" dispatcher("submit", inputElement.files)
}} }}
on:dragstart={ () => {drawAttention = false}} on:dragend={() => {
on:drop|preventDefault|stopPropagation={(e) => { drawAttention = false
console.log("Got a 'drop'") }}
drawAttention = false on:dragover|preventDefault|stopPropagation={(e) => {
dispatcher("submit", e.dataTransfer.files) console.log("Dragging over!")
}} drawAttention = true
type="file" 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"
/>
</form> </form>

View file

@ -2,7 +2,7 @@
/** /**
* Given an HTML string, properly shows this * Given an HTML string, properly shows this
*/ */
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
export let src: string export let src: string

View file

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import ToSvelte from "./ToSvelte.svelte"; import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge"
export let cls : string = undefined export let cls: string = undefined
</script> </script>
<div class={twMerge( "flex p-1 pl-2", cls)}> <div class={twMerge("flex p-1 pl-2", cls)}>
<div class="min-w-6 h-6 w-6 animate-spin self-center"> <div class="min-w-6 h-6 w-6 animate-spin self-center">
<ToSvelte construct={Svg.loading_svg()} /> <ToSvelte construct={Svg.loading_svg()} />
</div> </div>

View file

@ -55,26 +55,26 @@
{#if filteredLayer.layerDef.name} {#if filteredLayer.layerDef.name}
<div bind:this={mainElem} class="mb-1.5"> <div bind:this={mainElem} class="mb-1.5">
<Checkbox selected={isDisplayed} > <Checkbox selected={isDisplayed}>
<If condition={filteredLayer.isDisplayed}> <If condition={filteredLayer.isDisplayed}>
<ToSvelte <ToSvelte
construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")} construct={() => layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background")}
/> />
<ToSvelte <ToSvelte
slot="else" slot="else"
construct={() => construct={() =>
layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")} layer.defaultIcon()?.SetClass("block h-6 w-6 no-image-background opacity-50")}
/> />
</If> </If>
{filteredLayer.layerDef.name} {filteredLayer.layerDef.name}
{#if $zoomlevel < layer.minzoom} {#if $zoomlevel < layer.minzoom}
<span class="alert"> <span class="alert">
<Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} /> <Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} />
</span> </span>
{/if} {/if}
</Checkbox> </Checkbox>
{#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0} {#if $isDisplayed && filteredLayer.layerDef.filters?.length > 0}
<div id="subfilters" class="ml-4 flex flex-col gap-y-1"> <div id="subfilters" class="ml-4 flex flex-col gap-y-1">
@ -82,9 +82,9 @@
<div> <div>
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields --> <!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
{#if filter.options.length === 1 && filter.options[0].fields.length === 0} {#if filter.options.length === 1 && filter.options[0].fields.length === 0}
<Checkbox selected={getBooleanStateFor(filter)} > <Checkbox selected={getBooleanStateFor(filter)}>
{filter.options[0].question} {filter.options[0].question}
</Checkbox> </Checkbox>
{/if} {/if}
{#if filter.options.length === 1 && filter.options[0].fields.length > 0} {#if filter.options.length === 1 && filter.options[0].fields.length > 0}

View file

@ -1,44 +1,45 @@
<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"
/** /**
* 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> = state.geolocation.geolocationState.permission; let geopermission: Store<GeolocationPermissionState> =
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation; state.geolocation.geolocationState.permission
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") { if (glstate.permission.data !== "granted") {
glstate.requestPermission(); glstate.requestPermission()
return; return
} }
} }
</script> </script>
@ -69,22 +70,29 @@
</button> </button>
<!-- No geolocation granted - we don't show the button --> <!-- No geolocation granted - we don't show the button -->
{:else if $geopermission === "requested"} {:else if $geopermission === "requested"}
<button class="flex w-full items-center gap-x-2 disabled" on:click={jumpToCurrentLocation}> <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 --> <!-- 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;")} /> <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} /> <Tr t={Translations.t.general.waitingForGeopermission} />
</button> </button>
{:else if $geopermission === "denied"} {:else if $geopermission === "denied"}
<button class="flex w-full items-center gap-x-2 disabled"> <button class="disabled flex w-full items-center gap-x-2">
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} /> <ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
<Tr t={Translations.t.general.geopermissionDenied} /> <Tr t={Translations.t.general.geopermissionDenied} />
</button> </button>
{:else } {:else}
<button class="flex w-full items-center gap-x-2 disabled"> <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;")} /> <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} /> <Tr t={Translations.t.general.waitingForLocation} />
</button> </button>
{/if} {/if}
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2"> <div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">

View file

@ -1,63 +1,63 @@
<script lang="ts">/** <script lang="ts">
* Shows an 'upload'-button which will start the upload for this feature /**
*/ * Shows an 'upload'-button which will start the upload for this feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"; import type { OsmTags } from "../../Models/OsmFeature"
import LoginToggle from "../Base/LoginToggle.svelte"; import LoginToggle from "../Base/LoginToggle.svelte"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import UploadingImageCounter from "./UploadingImageCounter.svelte"; import UploadingImageCounter from "./UploadingImageCounter.svelte"
import FileSelector from "../Base/FileSelector.svelte"; import FileSelector from "../Base/FileSelector.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"; import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
export let tags: Store<OsmTags>; export let tags: Store<OsmTags>
/** /**
* Image to show in the button * Image to show in the button
* NOT the image to upload! * NOT the image to upload!
*/ */
export let image: string = undefined; export let image: string = undefined
if (image === "") { if (image === "") {
image = undefined; image = undefined
} }
export let labelText: string = undefined; export let labelText: string = undefined
const t = Translations.t.image; const t = Translations.t.image
let licenseStore = state.userRelatedState.imageLicense; let licenseStore = state.userRelatedState.imageLicense
function handleFiles(files: FileList) { function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files.item(i); const file = files.item(i)
console.log("Got file", file.name) console.log("Got file", file.name)
try { try {
state.imageUploadManager.uploadImageAndApply(file, tags); state.imageUploadManager.uploadImageAndApply(file, tags)
} catch (e) { } catch (e) {
alert(e); alert(e)
}
} }
} }
}
</script> </script>
<LoginToggle {state}> <LoginToggle {state}>
<Tr slot="not-logged-in" t={t.pleaseLogin} /> <Tr slot="not-logged-in" t={t.pleaseLogin} />
<div class="flex flex-col"> <div class="flex flex-col">
<UploadingImageCounter {state} {tags} /> <UploadingImageCounter {state} {tags} />
<FileSelector accept="image/*" cls="button border-2 text-2xl" multiple={true} <FileSelector
on:submit={e => handleFiles(e.detail)}> accept="image/*"
cls="button border-2 text-2xl"
multiple={true}
on:submit={(e) => handleFiles(e.detail)}
>
<div class="flex items-center"> <div class="flex items-center">
{#if image !== undefined} {#if image !== undefined}
<img src={image} /> <img src={image} />
{:else} {:else}
<ToSvelte construct={ Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} /> <ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} />
{/if} {/if}
{#if labelText} {#if labelText}
{labelText} {labelText}
@ -68,10 +68,14 @@ function handleFiles(files: FileList) {
</FileSelector> </FileSelector>
<div class="text-sm"> <div class="text-sm">
<Tr t={t.respectPrivacy} /> <Tr t={t.respectPrivacy} />
<a class="cursor-pointer" on:click={() => {state.guistate.openUsersettings("picture-license")}}> <a
<Tr t={t.currentLicense.Subs({license: $licenseStore})} /> class="cursor-pointer"
on:click={() => {
state.guistate.openUsersettings("picture-license")
}}
>
<Tr t={t.currentLicense.Subs({ license: $licenseStore })} />
</a> </a>
</div> </div>
</div> </div>
</LoginToggle> </LoginToggle>

View file

@ -1,32 +1,28 @@
<script lang="ts">/** <script lang="ts">
* Shows information about how much images are uploaded for the given feature /**
*/ * Shows information about how much images are uploaded for the given feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"; import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"; import Loading from "../Base/Loading.svelte"
export let state: SpecialVisualizationState;
export let tags: Store<OsmTags>;
const featureId = tags.data.id;
const {
uploadStarted,
uploadFinished,
retried,
failed
} = state.imageUploadManager.getCountsFor(featureId);
const t = Translations.t.image;
export let state: SpecialVisualizationState
export let tags: Store<OsmTags>
const featureId = tags.data.id
const { uploadStarted, uploadFinished, retried, failed } =
state.imageUploadManager.getCountsFor(featureId)
const t = Translations.t.image
</script> </script>
{#if $uploadStarted == 1} {#if $uploadStarted == 1}
{#if $uploadFinished == 1 } {#if $uploadFinished == 1}
<Tr cls="thanks" t={t.upload.one.done} /> <Tr cls="thanks" t={t.upload.one.done} />
{:else if $failed == 1} {:else if $failed == 1}
<div class="flex flex-col alert"> <div class="alert flex flex-col">
<Tr cls="self-center" t={t.upload.one.failed} /> <Tr cls="self-center" t={t.upload.one.failed} />
<Tr t={t.upload.failReasons} /> <Tr t={t.upload.failReasons} />
<Tr t={t.upload.failReasonsAdvanced} /> <Tr t={t.upload.failReasonsAdvanced} />
@ -35,30 +31,34 @@ const t = Translations.t.image;
<Loading cls="alert"> <Loading cls="alert">
<Tr t={t.upload.one.retrying} /> <Tr t={t.upload.one.retrying} />
</Loading> </Loading>
{:else } {:else}
<Loading cls="alert"> <Loading cls="alert">
<Tr t={t.upload.one.uploading} /> <Tr t={t.upload.one.uploading} />
</Loading> </Loading>
{/if} {/if}
{:else if $uploadStarted > 1} {:else if $uploadStarted > 1}
{#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0} {#if $uploadFinished + $failed == $uploadStarted && $uploadFinished > 0}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({count: $uploadFinished})} /> <Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $uploadFinished })} />
{:else if $uploadFinished == 0} {:else if $uploadFinished == 0}
<Loading cls="alert"> <Loading cls="alert">
<Tr t={t.upload.multiple.uploading.Subs({count: $uploadStarted})} /> <Tr t={t.upload.multiple.uploading.Subs({ count: $uploadStarted })} />
</Loading> </Loading>
{:else if $uploadFinished > 0} {:else if $uploadFinished > 0}
<Loading cls="alert"> <Loading cls="alert">
<Tr t={t.upload.multiple.partiallyDone.Subs({count: $uploadStarted - $uploadFinished, done: $uploadFinished})} /> <Tr
t={t.upload.multiple.partiallyDone.Subs({
count: $uploadStarted - $uploadFinished,
done: $uploadFinished,
})}
/>
</Loading> </Loading>
{/if} {/if}
{#if $failed > 0} {#if $failed > 0}
<div class="flex flex-col alert"> <div class="alert flex flex-col">
{#if failed === 1} {#if failed === 1}
<Tr cls="self-center" t={t.upload.one.failed} /> <Tr cls="self-center" t={t.upload.one.failed} />
{:else} {:else}
<Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({count: $failed})} /> <Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({ count: $failed })} />
{/if} {/if}
<Tr t={t.upload.failReasons} /> <Tr t={t.upload.failReasons} />
<Tr t={t.upload.failReasonsAdvanced} /> <Tr t={t.upload.failReasonsAdvanced} />

View file

@ -432,7 +432,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
) )
} }
await this.awaitStyleIsLoaded() await this.awaitStyleIsLoaded()
if(this._currentRasterLayer !== background?.id){ if (this._currentRasterLayer !== background?.id) {
this.removeCurrentLayer(map) this.removeCurrentLayer(map)
} }
this._currentRasterLayer = background?.id this._currentRasterLayer = background?.id
@ -459,10 +459,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.rotateTo(0, { duration: 0 }) map.rotateTo(0, { duration: 0 })
map.setPitch(0) map.setPitch(0)
map.dragRotate.disable() map.dragRotate.disable()
map.touchZoomRotate.disableRotation(); map.touchZoomRotate.disableRotation()
} else { } else {
map.dragRotate.enable() map.dragRotate.enable()
map.touchZoomRotate.enableRotation(); map.touchZoomRotate.enableRotation()
} }
} }

View file

@ -1,76 +1,79 @@
<script lang="ts"> <script lang="ts">
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import PlantNetSpeciesList from "./PlantNetSpeciesList.svelte"; import PlantNetSpeciesList from "./PlantNetSpeciesList.svelte"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"; import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"; import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import PlantNet from "../../Logic/Web/PlantNet"; import PlantNet from "../../Logic/Web/PlantNet"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"; import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import BackButton from "../Base/BackButton.svelte"; import BackButton from "../Base/BackButton.svelte"
import NextButton from "../Base/NextButton.svelte"; import NextButton from "../Base/NextButton.svelte"
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"; import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte"; import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
/** /**
* The main entry point for the plantnet wizard * The main entry point for the plantnet wizard
*/ */
const t = Translations.t.plantDetection; const t = Translations.t.plantDetection
/** /**
* All the URLs pointing to images of the selected feature. * All the URLs pointing to images of the selected feature.
* We need to feed them into Plantnet when applicable * We need to feed them into Plantnet when applicable
*/ */
export let imageUrls: Store<string[]>; export let imageUrls: Store<string[]>
export let onConfirm: (wikidataId: string) => void; export let onConfirm: (wikidataId: string) => void
const dispatch = createEventDispatcher<{ selected: string }>(); const dispatch = createEventDispatcher<{ selected: string }>()
let collapsedMode = true; let collapsedMode = true
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(undefined); let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(
undefined
)
let error: string = undefined; let error: string = undefined
/** /**
* The Wikidata-id of the species to apply * The Wikidata-id of the species to apply
*/ */
let selectedOption: string; let selectedOption: string
let done = false; let done = false
function speciesSelected(species: PlantNetSpeciesMatch) { function speciesSelected(species: PlantNetSpeciesMatch) {
console.log("Selected:", species); console.log("Selected:", species)
selectedOption = species; selectedOption = species
} }
async function detectSpecies() { async function detectSpecies() {
collapsedMode = false; collapsedMode = false
try { try {
const result = await PlantNet.query(imageUrls.data.slice(0, 5))
const result = await PlantNet.query(imageUrls.data.slice(0, 5)); options.set(result.results.filter((r) => r.score > 0.005).slice(0, 8))
options.set(result.results.filter(r => r.score > 0.005).slice(0, 8));
} catch (e) { } catch (e) {
error = e; error = e
} }
} }
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col">
{#if collapsedMode} {#if collapsedMode}
<button class="w-full" on:click={detectSpecies}> <button class="w-full" on:click={detectSpecies}>
<Tr t={t.button} /> <Tr t={t.button} />
</button> </button>
{:else if $error !== undefined} {:else if $error !== undefined}
<Tr cls="alert" t={t.error.Subs({error})} /> <Tr cls="alert" t={t.error.Subs({ error })} />
{:else if $imageUrls.length === 0} {:else if $imageUrls.length === 0}
<!-- No urls are available, show the explanation instead--> <!-- No urls are available, show the explanation instead-->
<div class=" border-region p-2 mb-1 relative"> <div class=" border-region relative mb-1 p-2">
<XCircleIcon class="absolute top-0 right-0 w-8 h-8 m-4 cursor-pointer" <XCircleIcon
on:click={() => {collapsedMode = true}}></XCircleIcon> class="absolute top-0 right-0 m-4 h-8 w-8 cursor-pointer"
on:click={() => {
collapsedMode = true
}}
/>
<Tr t={t.takeImages} /> <Tr t={t.takeImages} />
<Tr t={ t.howTo.intro} /> <Tr t={t.howTo.intro} />
<ul> <ul>
<li> <li>
<Tr t={t.howTo.li0} /> <Tr t={t.howTo.li0} />
@ -87,23 +90,39 @@
</ul> </ul>
</div> </div>
{:else if selectedOption === undefined} {:else if selectedOption === undefined}
<PlantNetSpeciesList {options} numberOfImages={$imageUrls.length} <PlantNetSpeciesList
on:selected={(species) => speciesSelected(species.detail)}> {options}
<XCircleIcon slot="upper-right" class="w-8 h-8 m-4 cursor-pointer" numberOfImages={$imageUrls.length}
on:click={() => {collapsedMode = true}}></XCircleIcon> on:selected={(species) => speciesSelected(species.detail)}
>
<XCircleIcon
slot="upper-right"
class="m-4 h-8 w-8 cursor-pointer"
on:click={() => {
collapsedMode = true
}}
/>
</PlantNetSpeciesList> </PlantNetSpeciesList>
{:else if !done} {:else if !done}
<div class="flex flex-col border-interactive"> <div class="border-interactive flex flex-col">
<div class="m-2"> <div class="m-2">
<WikipediaPanel wikiIds={new ImmutableStore([selectedOption])} /> <WikipediaPanel wikiIds={new ImmutableStore([selectedOption])} />
</div> </div>
<div class="flex flex-col items-stretch"> <div class="flex flex-col items-stretch">
<BackButton on:click={() => {selectedOption = undefined}}> <BackButton
on:click={() => {
selectedOption = undefined
}}
>
<Tr t={t.back} /> <Tr t={t.back} />
</BackButton> </BackButton>
<NextButton clss="primary" on:click={() => { done = true; onConfirm(selectedOption); }} > <NextButton
clss="primary"
on:click={() => {
done = true
onConfirm(selectedOption)
}}
>
<Tr t={t.confirm} /> <Tr t={t.confirm} />
</NextButton> </NextButton>
</div> </div>
@ -111,13 +130,21 @@
{:else} {:else}
<!-- done ! --> <!-- done ! -->
<Tr t={t.done} cls="thanks w-full" /> <Tr t={t.done} cls="thanks w-full" />
<BackButton imageClass="w-6 h-6 shrink-0" clss="p-1 m-0" on:click={() => {done = false; selectedOption = undefined}}> <BackButton
imageClass="w-6 h-6 shrink-0"
clss="p-1 m-0"
on:click={() => {
done = false
selectedOption = undefined
}}
>
<Tr t={t.tryAgain} /> <Tr t={t.tryAgain} />
</BackButton> </BackButton>
{/if} {/if}
<div class="flex p-2 low-interaction rounded-xl self-end"> <div class="low-interaction flex self-end rounded-xl p-2">
<ToSvelte construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")} /> <ToSvelte
construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")}
/>
<Tr t={t.poweredByPlantnet} /> <Tr t={t.poweredByPlantnet} />
</div> </div>
</div> </div>

View file

@ -1,29 +1,28 @@
<script lang="ts">/** <script lang="ts">
* Show the list of options to choose from /**
*/ * Show the list of options to choose from
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"; */
import { Store } from "../../Logic/UIEventSource"; import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import Translations from "../i18n/Translations"; import { Store } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte"; import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte"; import Tr from "../Base/Tr.svelte"
import SpeciesButton from "./SpeciesButton.svelte"; import Loading from "../Base/Loading.svelte"
import SpeciesButton from "./SpeciesButton.svelte"
const t = Translations.t.plantDetection; const t = Translations.t.plantDetection
export let options: Store<PlantNetSpeciesMatch[]>;
export let numberOfImages: number;
export let options: Store<PlantNetSpeciesMatch[]>
export let numberOfImages: number
</script> </script>
{#if $options === undefined} {#if $options === undefined}
<Loading> <Loading>
<Tr t={t.querying.Subs({length: numberOfImages})} /> <Tr t={t.querying.Subs({ length: numberOfImages })} />
</Loading> </Loading>
{:else} {:else}
<div class="low-interaction border-interactive flex p-2 flex-col relative"> <div class="low-interaction border-interactive relative flex flex-col p-2">
<div class="absolute top-0 right-0" > <div class="absolute top-0 right-0">
<slot name="upper-right" />
<slot name="upper-right"/>
</div> </div>
<h3> <h3>
<Tr t={t.overviewTitle} /> <Tr t={t.overviewTitle} />
@ -31,7 +30,7 @@ export let numberOfImages: number;
<Tr t={t.overviewIntro} /> <Tr t={t.overviewIntro} />
<Tr cls="font-bold" t={t.overviewVerify} /> <Tr cls="font-bold" t={t.overviewVerify} />
{#each $options as species} {#each $options as species}
<SpeciesButton {species} on:selected/> <SpeciesButton {species} on:selected />
{/each} {/each}
</div> </div>
{/if} {/if}

View file

@ -1,54 +1,61 @@
<script lang="ts">/** <script lang="ts">
* A button to select a single species /**
*/ * A button to select a single species
import { createEventDispatcher } from "svelte"; */
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"; import { createEventDispatcher } from "svelte"
import { UIEventSource } from "../../Logic/UIEventSource"; import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
import Wikidata from "../../Logic/Web/Wikidata"; import { UIEventSource } from "../../Logic/UIEventSource"
import NextButton from "../Base/NextButton.svelte"; import Wikidata from "../../Logic/Web/Wikidata"
import Loading from "../Base/Loading.svelte"; import NextButton from "../Base/NextButton.svelte"
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox"; import Loading from "../Base/Loading.svelte"
import Tr from "../Base/Tr.svelte"; import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox"
import Translations from "../i18n/Translations"; import Tr from "../Base/Tr.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"; import Translations from "../i18n/Translations"
import ToSvelte from "../Base/ToSvelte.svelte"
export let species: PlantNetSpeciesMatch; export let species: PlantNetSpeciesMatch
let wikidata = UIEventSource.FromPromise( let wikidata = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>( Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"], ["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""] ['?species wdt:P846 "' + species.gbif.id + '"']
)
) )
);
const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>(); const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>()
const t = Translations.t.plantDetection; const t = Translations.t.plantDetection
/**
/** * PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead. * We look this up in wikidata
* We look this up in wikidata */
*/ const wikidataId: Store<string> = UIEventSource.FromPromise(
const wikidataId: Store<string> = UIEventSource.FromPromise( Wikidata.Sparql<{ species }>(
Wikidata.Sparql<{ species }>( ["?species", "?speciesLabel"],
["?species", "?speciesLabel"], ['?species wdt:P846 "' + species.gbif.id + '"']
["?species wdt:P846 \"" + species.gbif.id + "\""] )
) ).mapD((wd) => wd[0]?.species?.value)
).mapD(wd => wd[0]?.species?.value);
</script> </script>
<NextButton on:click={() => dispatch("selected", $wikidataId)}> <NextButton on:click={() => dispatch("selected", $wikidataId)}>
{#if $wikidata === undefined} {#if $wikidata === undefined}
<Loading> <Loading>
<Tr t={ t.loadingWikidata.Subs({ <Tr
species: species.species.scientificNameWithoutAuthor, t={t.loadingWikidata.Subs({
})} /> species: species.species.scientificNameWithoutAuthor,
})}
/>
</Loading> </Loading>
{:else} {:else}
<ToSvelte construct={() => new WikidataPreviewBox(wikidataId, <ToSvelte
{ imageStyle: "max-width: 8rem; width: unset; height: 8rem", construct={() =>
extraItems: [t.matchPercentage new WikidataPreviewBox(wikidataId, {
.Subs({ match: Math.round(species.score * 100) }) imageStyle: "max-width: 8rem; width: unset; height: 8rem",
.SetClass("thanks w-fit self-center")] extraItems: [
}).SetClass("w-full")}></ToSvelte> t.matchPercentage
.Subs({ match: Math.round(species.score * 100) })
.SetClass("thanks w-fit self-center"),
],
}).SetClass("w-full")}
/>
{/if} {/if}
</NextButton> </NextButton>

View file

@ -3,109 +3,109 @@
* This component ties together all the steps that are needed to create a new point. * This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that * There are many subcomponents which help with that
*/ */
import type { SpecialVisualizationState } from "../../SpecialVisualization"; import type { SpecialVisualizationState } from "../../SpecialVisualization"
import PresetList from "./PresetList.svelte"; import PresetList from "./PresetList.svelte"
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"; import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import Tr from "../../Base/Tr.svelte"; import Tr from "../../Base/Tr.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"; import SubtleButton from "../../Base/SubtleButton.svelte"
import FromHtml from "../../Base/FromHtml.svelte"; import FromHtml from "../../Base/FromHtml.svelte"
import Translations from "../../i18n/Translations.js"; import Translations from "../../i18n/Translations.js"
import TagHint from "../TagHint.svelte"; import TagHint from "../TagHint.svelte"
import { And } from "../../../Logic/Tags/And.js"; import { And } from "../../../Logic/Tags/And.js"
import LoginToggle from "../../Base/LoginToggle.svelte"; import LoginToggle from "../../Base/LoginToggle.svelte"
import Constants from "../../../Models/Constants.js"; import Constants from "../../../Models/Constants.js"
import FilteredLayer from "../../../Models/FilteredLayer"; import FilteredLayer from "../../../Models/FilteredLayer"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"; import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"; import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
import LoginButton from "../../Base/LoginButton.svelte"; import LoginButton from "../../Base/LoginButton.svelte"
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
import { OsmWay } from "../../../Logic/Osm/OsmObject"; import { OsmWay } from "../../../Logic/Osm/OsmObject"
import { Tag } from "../../../Logic/Tags/Tag"; import { Tag } from "../../../Logic/Tags/Tag"
import type { WayId } from "../../../Models/OsmFeature"; import type { WayId } from "../../../Models/OsmFeature"
import Loading from "../../Base/Loading.svelte"; import Loading from "../../Base/Loading.svelte"
import type { GlobalFilter } from "../../../Models/GlobalFilter"; import type { GlobalFilter } from "../../../Models/GlobalFilter"
import { onDestroy } from "svelte"; import { onDestroy } from "svelte"
import NextButton from "../../Base/NextButton.svelte"; import NextButton from "../../Base/NextButton.svelte"
import BackButton from "../../Base/BackButton.svelte"; import BackButton from "../../Base/BackButton.svelte"
import ToSvelte from "../../Base/ToSvelte.svelte"; import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg"; import Svg from "../../../Svg"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"; import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
import { twJoin } from "tailwind-merge"; import { twJoin } from "tailwind-merge"
export let coordinate: { lon: number; lat: number }; export let coordinate: { lon: number; lat: number }
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
let selectedPreset: { let selectedPreset: {
preset: PresetConfig preset: PresetConfig
layer: LayerConfig layer: LayerConfig
icon: string icon: string
tags: Record<string, string> tags: Record<string, string>
} = undefined; } = undefined
let checkedOfGlobalFilters: number = 0; let checkedOfGlobalFilters: number = 0
let confirmedCategory = false; let confirmedCategory = false
$: if (selectedPreset === undefined) { $: if (selectedPreset === undefined) {
confirmedCategory = false; confirmedCategory = false
creating = false; creating = false
checkedOfGlobalFilters = 0; checkedOfGlobalFilters = 0
} }
let flayer: FilteredLayer = undefined; let flayer: FilteredLayer = undefined
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined; let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined
let layerHasFilters: Store<boolean> | undefined = undefined; let layerHasFilters: Store<boolean> | undefined = undefined
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters; let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters
let _globalFilter: GlobalFilter[] = []; let _globalFilter: GlobalFilter[] = []
onDestroy( onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => { globalFilter.addCallbackAndRun((globalFilter) => {
console.log("Global filters are", globalFilter); console.log("Global filters are", globalFilter)
_globalFilter = globalFilter ?? []; _globalFilter = globalFilter ?? []
}) })
); )
$: { $: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
layerIsDisplayed = flayer?.isDisplayed; layerIsDisplayed = flayer?.isDisplayed
layerHasFilters = flayer?.hasFilter; layerHasFilters = flayer?.hasFilter
} }
const t = Translations.t.general.add; const t = Translations.t.general.add
const zoom = state.mapProperties.zoom; const zoom = state.mapProperties.zoom
const isLoading = state.dataIsLoading; const isLoading = state.dataIsLoading
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined); let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined); let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map // Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
let preciseInputIsTapped = false; let preciseInputIsTapped = false
let creating = false; let creating = false
/** /**
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters. * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location * Will delete the lastclick-location
*/ */
function abort() { function abort() {
state.selectedElement.setData(undefined); state.selectedElement.setData(undefined)
// When aborted, we force the contributors to place the pin _again_ // When aborted, we force the contributors to place the pin _again_
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
state.lastClickObject.features.setData([]); state.lastClickObject.features.setData([])
preciseInputIsTapped = false; preciseInputIsTapped = false
} }
async function confirm() { async function confirm() {
creating = true; creating = true
const location: { lon: number; lat: number } = preciseCoordinate.data; const location: { lon: number; lat: number } = preciseCoordinate.data
const snapTo: WayId | undefined = <WayId>snappedToObject.data; const snapTo: WayId | undefined = <WayId>snappedToObject.data
const tags: Tag[] = selectedPreset.preset.tags.concat( const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) ..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
); )
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
let snapToWay: undefined | OsmWay = undefined; let snapToWay: undefined | OsmWay = undefined
if (snapTo !== undefined && snapTo !== null) { if (snapTo !== undefined && snapTo !== null) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
if (downloaded !== "deleted") { if (downloaded !== "deleted") {
snapToWay = downloaded; snapToWay = downloaded
} }
} }
@ -113,42 +113,44 @@
theme: state.layout?.id ?? "unkown", theme: state.layout?.id ?? "unkown",
changeType: "create", changeType: "create",
snapOnto: snapToWay, snapOnto: snapToWay,
reusePointWithinMeters: 1 reusePointWithinMeters: 1,
}); })
await state.changes.applyAction(newElementAction); await state.changes.applyAction(newElementAction)
state.newFeatures.features.ping(); state.newFeatures.features.ping()
// The 'changes' should have created a new point, which added this into the 'featureProperties' // The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId; const newId = newElementAction.newElementId
console.log("Applied pending changes, fetching store for", newId); console.log("Applied pending changes, fetching store for", newId)
const tagsStore = state.featureProperties.getStore(newId); const tagsStore = state.featureProperties.getStore(newId)
if (!tagsStore) { if (!tagsStore) {
console.error("Bug: no tagsStore found for", newId); console.error("Bug: no tagsStore found for", newId)
} }
{ {
// Set some metainfo // Set some metainfo
const properties = tagsStore.data; const properties = tagsStore.data
if (snapTo) { if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this // metatags (starting with underscore) are not uploaded, so we can safely mark this
delete properties["_referencing_ways"]; delete properties["_referencing_ways"]
properties["_referencing_ways"] = `["${snapTo}"]`; properties["_referencing_ways"] = `["${snapTo}"]`
} }
properties["_backend"] = state.osmConnection.Backend(); properties["_backend"] = state.osmConnection.Backend()
properties["_last_edit:timestamp"] = new Date().toISOString(); properties["_last_edit:timestamp"] = new Date().toISOString()
const userdetails = state.osmConnection.userDetails.data; const userdetails = state.osmConnection.userDetails.data
properties["_last_edit:contributor"] = userdetails.name; properties["_last_edit:contributor"] = userdetails.name
properties["_last_edit:uid"] = "" + userdetails.uid; properties["_last_edit:uid"] = "" + userdetails.uid
tagsStore.ping(); tagsStore.ping()
} }
const feature = state.indexedFeatures.featuresById.data.get(newId); const feature = state.indexedFeatures.featuresById.data.get(newId)
console.log("Selecting feature", feature, "and opening their popup"); console.log("Selecting feature", feature, "and opening their popup")
abort(); abort()
state.selectedLayer.setData(selectedPreset.layer); state.selectedLayer.setData(selectedPreset.layer)
state.selectedElement.setData(feature); state.selectedElement.setData(feature)
tagsStore.ping(); tagsStore.ping()
} }
function confirmSync() { function confirmSync() {
confirm().then(_ => console.debug("New point successfully handled")).catch(e => console.error("Handling the new point went wrong due to", e)); confirm()
.then((_) => console.debug("New point successfully handled"))
.catch((e) => console.error("Handling the new point went wrong due to", e))
} }
</script> </script>

View file

@ -62,7 +62,7 @@
state.newFeatures.features.data.push(feature) state.newFeatures.features.data.push(feature)
state.newFeatures.features.ping() state.newFeatures.features.ping()
state.selectedElement?.setData(feature) state.selectedElement?.setData(feature)
if(state.featureProperties.trackFeature){ if (state.featureProperties.trackFeature) {
state.featureProperties.trackFeature(feature) state.featureProperties.trackFeature(feature)
} }
comment.setData("") comment.setData("")

View file

@ -23,18 +23,20 @@
export let feature: Feature export let feature: Feature
export let layer: LayerConfig export let layer: LayerConfig
export let linkable = true; export let linkable = true
let isLinked = Object.values(tags.data).some(v => image.pictureUrl === v); let isLinked = Object.values(tags.data).some((v) => image.pictureUrl === v)
const t = Translations.t.image.nearby; const t = Translations.t.image.nearby
const c = [lon, lat]; const c = [lon, lat]
let attributedImage = new AttributedImage({ let attributedImage = new AttributedImage({
url: image.thumbUrl ?? image.pictureUrl, url: image.thumbUrl ?? image.pictureUrl,
provider: AllImageProviders.byName(image.provider), provider: AllImageProviders.byName(image.provider),
date: new Date(image.date) date: new Date(image.date),
}); })
let distance = Math.round(GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)); let distance = Math.round(
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
)
$: { $: {
const currentTags = tags.data const currentTags = tags.data
const key = Object.keys(image.osmTags)[0] const key = Object.keys(image.osmTags)[0]

View file

@ -1,41 +1,40 @@
<script lang="ts"> <script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews"; import FeatureReviews from "../../Logic/Web/MangroveReviews"
import SingleReview from "./SingleReview.svelte"; import SingleReview from "./SingleReview.svelte"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
import StarsBar from "./StarsBar.svelte"; import StarsBar from "./StarsBar.svelte"
import ReviewForm from "./ReviewForm.svelte"; import ReviewForm from "./ReviewForm.svelte"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"; import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"; import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"; import Svg from "../../Svg"
/** /**
* An element showing all reviews * An element showing all reviews
*/ */
export let reviews: FeatureReviews; export let reviews: FeatureReviews
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>; export let tags: UIEventSource<Record<string, string>>
export let feature: Feature; export let feature: Feature
export let layer: LayerConfig; export let layer: LayerConfig
let average = reviews.average; let average = reviews.average
let _reviews = []; let _reviews = []
reviews.reviews.addCallbackAndRunD(r => { reviews.reviews.addCallbackAndRunD((r) => {
_reviews = Utils.NoNull(r); _reviews = Utils.NoNull(r)
}); })
</script> </script>
<div class="border-gray-300 border-dashed border-2"> <div class="border-2 border-dashed border-gray-300">
{#if _reviews.length > 1} {#if _reviews.length > 1}
<StarsBar score={$average}></StarsBar> <StarsBar score={$average} />
{/if} {/if}
{#if _reviews.length > 0} {#if _reviews.length > 0}
{#each _reviews as review} {#each _reviews as review}
<SingleReview {review}></SingleReview> <SingleReview {review} />
{/each} {/each}
{:else} {:else}
<Tr t={Translations.t.reviews.no_reviews_yet} /> <Tr t={Translations.t.reviews.no_reviews_yet} />

View file

@ -1,60 +1,61 @@
<script lang="ts"> <script lang="ts">
import FeatureReviews from "../../Logic/Web/MangroveReviews"; import FeatureReviews from "../../Logic/Web/MangroveReviews"
import StarsBar from "./StarsBar.svelte"; import StarsBar from "./StarsBar.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"; import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"; import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"; import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Checkbox from "../Base/Checkbox.svelte"; import Checkbox from "../Base/Checkbox.svelte"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
import If from "../Base/If.svelte"; import If from "../Base/If.svelte"
import Loading from "../Base/Loading.svelte"; import Loading from "../Base/Loading.svelte"
import { Review } from "mangrove-reviews-typescript"; import { Review } from "mangrove-reviews-typescript"
import { Utils } from "../../Utils"; import { Utils } from "../../Utils"
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>; export let tags: UIEventSource<Record<string, string>>
export let feature: Feature; export let feature: Feature
export let layer: LayerConfig; export let layer: LayerConfig
/** /**
* The form to create a new review. * The form to create a new review.
* This is multi-stepped. * This is multi-stepped.
*/ */
export let reviews: FeatureReviews; export let reviews: FeatureReviews
let score = 0; let score = 0
let confirmedScore = undefined; let confirmedScore = undefined
let isAffiliated = new UIEventSource(false); let isAffiliated = new UIEventSource(false)
let opinion = new UIEventSource<string>(undefined); let opinion = new UIEventSource<string>(undefined)
const t = Translations.t.reviews; const t = Translations.t.reviews
let _state: "ask" | "saving" | "done" = "ask"; let _state: "ask" | "saving" | "done" = "ask"
const connection = state.osmConnection; const connection = state.osmConnection
async function save() { async function save() {
_state = "saving"; _state = "saving"
let nickname = undefined; let nickname = undefined
if (connection.isLoggedIn.data) { if (connection.isLoggedIn.data) {
nickname = connection.userDetails.data.name; nickname = connection.userDetails.data.name
} }
const review: Omit<Review, "sub"> = { const review: Omit<Review, "sub"> = {
rating: confirmedScore, rating: confirmedScore,
opinion: opinion.data, opinion: opinion.data,
metadata: { nickname, is_affiliated: isAffiliated.data } metadata: { nickname, is_affiliated: isAffiliated.data },
};
if (state.featureSwitchIsTesting.data) {
console.log("Testing - not actually saving review", review);
await Utils.waitFor(1000);
} else {
await reviews.createReview(review);
} }
_state = "done"; if (state.featureSwitchIsTesting.data) {
console.log("Testing - not actually saving review", review)
await Utils.waitFor(1000)
} else {
await reviews.createReview(review)
}
_state = "done"
} }
</script> </script>
{#if _state === "done"} {#if _state === "done"}
<Tr cls="thanks w-full" t={t.saved} /> <Tr cls="thanks w-full" t={t.saved} />
{:else if _state === "saving"} {:else if _state === "saving"}
@ -64,24 +65,34 @@
{:else} {:else}
<div class="interactive border-interactive p-1"> <div class="interactive border-interactive p-1">
<div class="font-bold"> <div class="font-bold">
<SpecialTranslation {feature} {layer} {state} t={Translations.t.reviews.question} {tags}></SpecialTranslation> <SpecialTranslation {feature} {layer} {state} t={Translations.t.reviews.question} {tags} />
</div> </div>
<StarsBar on:click={e => {confirmedScore = e.detail.score}} on:hover={e => {score = e.detail.score}} <StarsBar
on:mouseout={e => {score = null}} score={score ?? confirmedScore ?? 0} on:click={(e) => {
starSize="w-8 h-8"></StarsBar> confirmedScore = e.detail.score
}}
on:hover={(e) => {
score = e.detail.score
}}
on:mouseout={(e) => {
score = null
}}
score={score ?? confirmedScore ?? 0}
starSize="w-8 h-8"
/>
{#if confirmedScore !== undefined} {#if confirmedScore !== undefined}
<Tr cls="font-bold mt-2" t={t.question_opinion} /> <Tr cls="font-bold mt-2" t={t.question_opinion} />
<textarea bind:value={$opinion} inputmode="text" rows="3" class="w-full mb-1" /> <textarea bind:value={$opinion} inputmode="text" rows="3" class="mb-1 w-full" />
<Checkbox selected={isAffiliated}> <Checkbox selected={isAffiliated}>
<div class="flex flex-col"> <div class="flex flex-col">
<Tr t={t.i_am_affiliated} /> <Tr t={t.i_am_affiliated} />
<Tr cls="subtle" t={t.i_am_affiliated_explanation} /> <Tr cls="subtle" t={t.i_am_affiliated_explanation} />
</div> </div>
</Checkbox> </Checkbox>
<div class="flex w-full justify-between flex-wrap items-center"> <div class="flex w-full flex-wrap items-center justify-between">
<If condition={state.osmConnection.isLoggedIn}> <If condition={state.osmConnection.isLoggedIn}>
<Tr t={t.reviewing_as.Subs({nickname: state.osmConnection.userDetails.data.name})} /> <Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
<Tr slot="else" t={t.reviewing_as_anonymous} /> <Tr slot="else" t={t.reviewing_as_anonymous} />
</If> </If>
<button class="primary" on:click={save}> <button class="primary" on:click={save}>
@ -90,8 +101,6 @@
</div> </div>
<Tr cls="subtle mt-4" t={t.tos} /> <Tr cls="subtle mt-4" t={t.tos} />
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -1,32 +1,32 @@
<script lang="ts"> <script lang="ts">
import { Review } from "mangrove-reviews-typescript"; import { Review } from "mangrove-reviews-typescript"
import { Store } from "../../Logic/UIEventSource"; import { Store } from "../../Logic/UIEventSource"
import StarsBar from "./StarsBar.svelte"; import StarsBar from "./StarsBar.svelte"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte"
export let review: Review & { madeByLoggedInUser: Store<boolean> }; export let review: Review & { madeByLoggedInUser: Store<boolean> }
let name = review.metadata.nickname; let name = review.metadata.nickname
name ??= (review.metadata.given_name ?? "") + " " + (review.metadata.family_name ?? "").trim(); name ??= (review.metadata.given_name ?? "") + " " + (review.metadata.family_name ?? "").trim()
if (name.length === 0) { if (name.length === 0) {
name = "Anonymous"; name = "Anonymous"
} }
let d = new Date(); let d = new Date()
d.setTime(review.iat * 1000); d.setTime(review.iat * 1000)
let date = d.toDateString(); let date = d.toDateString()
let byLoggedInUser = review.madeByLoggedInUser; let byLoggedInUser = review.madeByLoggedInUser
</script> </script>
<div class={"low-interaction p-1 px-2 rounded-lg "+ ($byLoggedInUser ? "border-interactive" : "")}> <div class={"low-interaction rounded-lg p-1 px-2 " + ($byLoggedInUser ? "border-interactive" : "")}>
<div class="flex justify-between items-center"> <div class="flex items-center justify-between">
<StarsBar score={review.rating}></StarsBar> <StarsBar score={review.rating} />
<div class="flex flex-wrap space-x-2"> <div class="flex flex-wrap space-x-2">
<div class="font-bold"> <div class="font-bold">
{name} {name}
</div> </div>
<span class="subtle"> <span class="subtle">
{date} {date}
</span> </span>
</div> </div>
</div> </div>
{#if review.opinion} {#if review.opinion}

View file

@ -1,27 +1,27 @@
<script lang="ts"> <script lang="ts">
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte"; export let score: number
import Svg from "../../Svg"; export let cutoff: number
import { createEventDispatcher } from "svelte"; export let starSize = "w-h h-4"
export let score: number; let dispatch = createEventDispatcher<{ hover: { score: number } }>()
export let cutoff: number; let container: HTMLElement
export let starSize = "w-h h-4";
let dispatch = createEventDispatcher<{ hover: { score: number } }>();
let container: HTMLElement;
function getScore(e: MouseEvent): number { function getScore(e: MouseEvent): number {
const x = e.clientX - e.target.getBoundingClientRect().x; const x = e.clientX - e.target.getBoundingClientRect().x
const w = container.getClientRects()[0]?.width; const w = container.getClientRects()[0]?.width
return (x / w) < 0.5 ? cutoff - 10 : cutoff; return x / w < 0.5 ? cutoff - 10 : cutoff
} }
</script> </script>
<div bind:this={container} on:click={(e) => dispatch("click", {score: getScore(e)})} <div
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}> bind:this={container}
on:click={(e) => dispatch("click", { score: getScore(e) })}
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}
>
{#if score >= cutoff} {#if score >= cutoff}
<ToSvelte construct={Svg.star_svg().SetClass(starSize)} /> <ToSvelte construct={Svg.star_svg().SetClass(starSize)} />
{:else if score + 10 >= cutoff} {:else if score + 10 >= cutoff}

View file

@ -1,21 +1,21 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte"
import StarElement from "./StarElement.svelte"; import StarElement from "./StarElement.svelte"
/** /**
* Number between 0 and 100. Every 10 points, another half star is added * Number between 0 and 100. Every 10 points, another half star is added
*/ */
export let score: number; export let score: number
let dispatch = createEventDispatcher<{ hover: number, click: number }>(); let dispatch = createEventDispatcher<{ hover: number; click: number }>()
let cutoffs = [20,40,60,80,100] let cutoffs = [20, 40, 60, 80, 100]
export let starSize = "w-h h-4" export let starSize = "w-h h-4"
</script> </script>
{#if score !== undefined} {#if score !== undefined}
<div class="flex" on:mouseout> <div class="flex" on:mouseout>
{#each cutoffs as cutoff} {#each cutoffs as cutoff}
<StarElement {score} {cutoff} {starSize} on:hover on:click/> <StarElement {score} {cutoff} {starSize} on:hover on:click />
{/each} {/each}
</div> </div>
{/if} {/if}

View file

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Store } from "../../Logic/UIEventSource"
import StarsBar from "./StarsBar.svelte"
import { Store } from "../../Logic/UIEventSource"; export let score: Store<number>
import StarsBar from "./StarsBar.svelte";
export let score: Store<number>;
</script> </script>
{#if $score !== undefined && $score !== null} {#if $score !== undefined && $score !== null}

View file

@ -1,118 +1,121 @@
import { Store, UIEventSource } from "../Logic/UIEventSource"; import { Store, UIEventSource } from "../Logic/UIEventSource"
import BaseUIElement from "./BaseUIElement"; import BaseUIElement from "./BaseUIElement"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"; import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { Changes } from "../Logic/Osm/Changes"; import { Changes } from "../Logic/Osm/Changes"
import { ExportableMap, MapProperties } from "../Models/MapProperties"; import { ExportableMap, MapProperties } from "../Models/MapProperties"
import LayerState from "../Logic/State/LayerState"; import LayerState from "../Logic/State/LayerState"
import { Feature, Geometry, Point } from "geojson"; import { Feature, Geometry, Point } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import { MenuState } from "../Models/MenuState"; import { MenuState } from "../Models/MenuState"
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
import { RasterLayerPolygon } from "../Models/RasterLayers"; import { RasterLayerPolygon } from "../Models/RasterLayers"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
import { OsmTags } from "../Models/OsmFeature"; import { OsmTags } from "../Models/OsmFeature"
/** /**
* The state needed to render a special Visualisation. * The state needed to render a special Visualisation.
*/ */
export interface SpecialVisualizationState { export interface SpecialVisualizationState {
readonly guistate: MenuState; readonly guistate: MenuState
readonly layout: LayoutConfig; readonly layout: LayoutConfig
readonly featureSwitches: FeatureSwitchState; readonly featureSwitches: FeatureSwitchState
readonly layerState: LayerState; readonly layerState: LayerState
readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>>, trackFeature?(feature: { properties: OsmTags }) }; readonly featureProperties: {
getStore(id: string): UIEventSource<Record<string, string>>
trackFeature?(feature: { properties: OsmTags })
}
readonly indexedFeatures: IndexedFeatureSource; readonly indexedFeatures: IndexedFeatureSource
/** /**
* Some features will create a new element that should be displayed. * Some features will create a new element that should be displayed.
* These can be injected by appending them to this featuresource (and pinging it) * These can be injected by appending them to this featuresource (and pinging it)
*/ */
readonly newFeatures: WritableFeatureSource; readonly newFeatures: WritableFeatureSource
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>; readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
readonly osmConnection: OsmConnection; readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>; readonly featureSwitchUserbadge: Store<boolean>
readonly featureSwitchIsTesting: Store<boolean>; readonly featureSwitchIsTesting: Store<boolean>
readonly changes: Changes; readonly changes: Changes
readonly osmObjectDownloader: OsmObjectDownloader; readonly osmObjectDownloader: OsmObjectDownloader
/** /**
* State of the main map * State of the main map
*/ */
readonly mapProperties: MapProperties & ExportableMap; readonly mapProperties: MapProperties & ExportableMap
readonly selectedElement: UIEventSource<Feature>; readonly selectedElement: UIEventSource<Feature>
/** /**
* Works together with 'selectedElement' to indicate what properties should be displayed * Works together with 'selectedElement' to indicate what properties should be displayed
*/ */
readonly selectedLayer: UIEventSource<LayerConfig>; readonly selectedLayer: UIEventSource<LayerConfig>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
/** /**
* If data is currently being fetched from external sources * If data is currently being fetched from external sources
*/ */
readonly dataIsLoading: Store<boolean>; readonly dataIsLoading: Store<boolean>
/** /**
* Only needed for 'ReplaceGeometryAction' * Only needed for 'ReplaceGeometryAction'
*/ */
readonly fullNodeDatabase?: FullNodeDatabaseSource; readonly fullNodeDatabase?: FullNodeDatabaseSource
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>; readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly userRelatedState: { readonly userRelatedState: {
readonly imageLicense: UIEventSource<string>; readonly imageLicense: UIEventSource<string>
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
readonly mangroveIdentity: MangroveIdentity readonly mangroveIdentity: MangroveIdentity
readonly showAllQuestionsAtOnce: UIEventSource<boolean> readonly showAllQuestionsAtOnce: UIEventSource<boolean>
readonly preferencesAsTags: Store<Record<string, string>> readonly preferencesAsTags: Store<Record<string, string>>
readonly language: UIEventSource<string> readonly language: UIEventSource<string>
}; }
readonly lastClickObject: WritableFeatureSource; readonly lastClickObject: WritableFeatureSource
readonly availableLayers: Store<RasterLayerPolygon[]>; readonly availableLayers: Store<RasterLayerPolygon[]>
readonly imageUploadManager: ImageUploadManager; readonly imageUploadManager: ImageUploadManager
} }
export interface SpecialVisualization { export interface SpecialVisualization {
readonly funcName: string; readonly funcName: string
readonly docs: string | BaseUIElement; readonly docs: string | BaseUIElement
readonly example?: string; readonly example?: string
/** /**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
*/ */
readonly needsNodeDatabase?: boolean; readonly needsNodeDatabase?: boolean
readonly args: { readonly args: {
name: string name: string
defaultValue?: string defaultValue?: string
doc: string doc: string
required?: false | boolean required?: false | boolean
}[]; }[]
readonly getLayerDependencies?: (argument: string[]) => string[]; readonly getLayerDependencies?: (argument: string[]) => string[]
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]; structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig
): BaseUIElement; ): BaseUIElement
} }
export type RenderingSpecification = export type RenderingSpecification =
| string | string
| { | {
func: SpecialVisualization func: SpecialVisualization
args: string[] args: string[]
style: string style: string
} }

View file

@ -1,72 +1,76 @@
import Combine from "./Base/Combine"; import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement"; import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"; import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title"; import Title from "./Base/Title"
import Table from "./Base/Table"; import Table from "./Base/Table"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; import {
import { HistogramViz } from "./Popup/HistogramViz"; RenderingSpecification,
import { MinimapViz } from "./Popup/MinimapViz"; SpecialVisualization,
import { ShareLinkViz } from "./Popup/ShareLinkViz"; SpecialVisualizationState,
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; } from "./SpecialVisualization"
import { MultiApplyViz } from "./Popup/MultiApplyViz"; import { HistogramViz } from "./Popup/HistogramViz"
import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; import { MinimapViz } from "./Popup/MinimapViz"
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; import { ShareLinkViz } from "./Popup/ShareLinkViz"
import TagApplyButton from "./Popup/TagApplyButton"; import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { CloseNoteButton } from "./Popup/CloseNoteButton"; import { MultiApplyViz } from "./Popup/MultiApplyViz"
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"
import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; import TagApplyButton from "./Popup/TagApplyButton"
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; import { CloseNoteButton } from "./Popup/CloseNoteButton"
import { ImageCarousel } from "./Image/ImageCarousel"; import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
import { VariableUiElement } from "./Base/VariableUIElement"; import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
import { Utils } from "../Utils"; import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
import { Translation } from "./i18n/Translation"; import { ImageCarousel } from "./Image/ImageCarousel"
import Translations from "./i18n/Translations"; import { VariableUiElement } from "./Base/VariableUIElement"
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; import { Utils } from "../Utils"
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
import { SubtleButton } from "./Base/SubtleButton"; import { Translation } from "./i18n/Translation"
import Svg from "../Svg"; import Translations from "./i18n/Translations"
import NoteCommentElement from "./Popup/NoteCommentElement"; import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
import { SubstitutedTranslation } from "./SubstitutedTranslation"; import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
import List from "./Base/List"; import { SubtleButton } from "./Base/SubtleButton"
import StatisticsPanel from "./BigComponents/StatisticsPanel"; import Svg from "../Svg"
import AutoApplyButton from "./Popup/AutoApplyButton"; import NoteCommentElement from "./Popup/NoteCommentElement"
import { LanguageElement } from "./Popup/LanguageElement"; import { SubstitutedTranslation } from "./SubstitutedTranslation"
import FeatureReviews from "../Logic/Web/MangroveReviews"; import List from "./Base/List"
import Maproulette from "../Logic/Maproulette"; import StatisticsPanel from "./BigComponents/StatisticsPanel"
import SvelteUIElement from "./Base/SvelteUIElement"; import AutoApplyButton from "./Popup/AutoApplyButton"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; import { LanguageElement } from "./Popup/LanguageElement"
import QuestionViz from "./Popup/QuestionViz"; import FeatureReviews from "../Logic/Web/MangroveReviews"
import { Feature, Point } from "geojson"; import Maproulette from "../Logic/Maproulette"
import { GeoOperations } from "../Logic/GeoOperations"; import SvelteUIElement from "./Base/SvelteUIElement"
import CreateNewNote from "./Popup/CreateNewNote.svelte"; import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; import QuestionViz from "./Popup/QuestionViz"
import UserProfile from "./BigComponents/UserProfile.svelte"; import { Feature, Point } from "geojson"
import LanguagePicker from "./LanguagePicker"; import { GeoOperations } from "../Logic/GeoOperations"
import Link from "./Base/Link"; import CreateNewNote from "./Popup/CreateNewNote.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; import UserProfile from "./BigComponents/UserProfile.svelte"
import { OsmTags, WayId } from "../Models/OsmFeature"; import LanguagePicker from "./LanguagePicker"
import MoveWizard from "./Popup/MoveWizard"; import Link from "./Base/Link"
import SplitRoadWizard from "./Popup/SplitRoadWizard"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; import { OsmTags, WayId } from "../Models/OsmFeature"
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; import MoveWizard from "./Popup/MoveWizard"
import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; import SplitRoadWizard from "./Popup/SplitRoadWizard"
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"; import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import FediverseValidator from "./InputElement/Validators/FediverseValidator"; import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import SendEmail from "./Popup/SendEmail.svelte"; import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import NearbyImages from "./Popup/NearbyImages.svelte"; import { OpenJosm } from "./BigComponents/OpenJosm"
import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import UploadImage from "./Image/UploadImage.svelte"; import FediverseValidator from "./InputElement/Validators/FediverseValidator"
import AllReviews from "./Reviews/AllReviews.svelte"; import SendEmail from "./Popup/SendEmail.svelte"
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"; import NearbyImages from "./Popup/NearbyImages.svelte"
import ReviewForm from "./Reviews/ReviewForm.svelte"; import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"
import UploadImage from "./Image/UploadImage.svelte"
import AllReviews from "./Reviews/AllReviews.svelte"
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"
import ReviewForm from "./Reviews/ReviewForm.svelte"
class NearbyImageVis implements SpecialVisualization { class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -265,7 +269,6 @@ export default class SpecialVisualizations {
SpecialVisualizations.specialVisualizations SpecialVisualizations.specialVisualizations
.map((sp) => sp.funcName + "()") .map((sp) => sp.funcName + "()")
.join(", ") .join(", ")
} }
} }
@ -610,17 +613,20 @@ export default class SpecialVisualizations {
{ {
name: "image-key", name: "image-key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
required: false required: false,
}, },
{ {
name: "label", name: "label",
doc: "The text to show on the button", doc: "The text to show on the button",
required: false required: false,
}, },
], ],
constr: (state, tags, args) => { constr: (state, tags, args) => {
return new SvelteUIElement(UploadImage, { return new SvelteUIElement(UploadImage, {
state,tags, labelText: args[1], image: args[0] state,
tags,
labelText: args[1],
image: args[0],
}) })
}, },
}, },
@ -642,15 +648,22 @@ export default class SpecialVisualizations {
const nameKey = args[0] ?? "name" const nameKey = args[0] ?? "name"
let fallbackName = args[1] let fallbackName = args[1]
const reviews = FeatureReviews.construct( const reviews = FeatureReviews.construct(
feature, feature,
tags, tags,
state.userRelatedState.mangroveIdentity, state.userRelatedState.mangroveIdentity,
{ {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
} }
) )
return new SvelteUIElement(StarsBarIcon, {score:reviews.average, reviews, state, tags, feature, layer}) return new SvelteUIElement(StarsBarIcon, {
score: reviews.average,
reviews,
state,
tags,
feature,
layer,
})
}, },
}, },
@ -672,15 +685,15 @@ export default class SpecialVisualizations {
const nameKey = args[0] ?? "name" const nameKey = args[0] ?? "name"
let fallbackName = args[1] let fallbackName = args[1]
const reviews = FeatureReviews.construct( const reviews = FeatureReviews.construct(
feature, feature,
tags, tags,
state.userRelatedState.mangroveIdentity, state.userRelatedState.mangroveIdentity,
{ {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
} }
) )
return new SvelteUIElement(ReviewForm, {reviews, state, tags, feature, layer}) return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
}, },
}, },
{ {
@ -711,7 +724,7 @@ export default class SpecialVisualizations {
fallbackName, fallbackName,
} }
) )
return new SvelteUIElement(AllReviews, {reviews, state, tags, feature, layer}) return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
}, },
}, },
{ {
@ -920,8 +933,8 @@ export default class SpecialVisualizations {
const id = tags.data[args[0] ?? "id"] const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id) tags = state.featureProperties.getStore(id)
console.log("Id is", id) console.log("Id is", id)
return new SvelteUIElement(UploadImage, {state, tags}) return new SvelteUIElement(UploadImage, { state, tags })
} },
}, },
{ {
funcName: "title", funcName: "title",

View file

@ -32,7 +32,7 @@
<div class="border-interactive interactive"> <div class="border-interactive interactive">
Highly interactive area (mostly: active question) Highly interactive area (mostly: active question)
</div> </div>
<div class="flex"> <div class="flex">
<button class="primary"> <button class="primary">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} /> <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />

View file

@ -33,22 +33,22 @@
<Tr t={Translations.t.general.wikipedia.loading} /> <Tr t={Translations.t.general.wikipedia.loading} />
</Loading> </Loading>
{:else} {:else}
<span class="wikipedia-article"> <span class="wikipedia-article">
<FromHtml src={$wikipediaDetails.firstParagraph} /> <FromHtml src={$wikipediaDetails.firstParagraph} />
<Disclosure let:open> <Disclosure let:open>
<DisclosureButton> <DisclosureButton>
<span class="flex"> <span class="flex">
<ChevronRightIcon <ChevronRightIcon
style={(open ? "transform: rotate(90deg); " : "") + style={(open ? "transform: rotate(90deg); " : "") +
" transition: all .25s linear; width: 1.5rem; height: 1.5rem"} " transition: all .25s linear; width: 1.5rem; height: 1.5rem"}
/> />
<Tr t={Translations.t.general.wikipedia.readMore}/> <Tr t={Translations.t.general.wikipedia.readMore} />
</span> </span>
</DisclosureButton> </DisclosureButton>
<DisclosurePanel> <DisclosurePanel>
<FromHtml src={$wikipediaDetails.restOfArticle} /> <FromHtml src={$wikipediaDetails.restOfArticle} />
</DisclosurePanel> </DisclosurePanel>
</Disclosure> </Disclosure>
</span> </span>
{/if} {/if}
{/if} {/if}

View file

@ -17,7 +17,7 @@
export let wikiIds: Store<string[]> export let wikiIds: Store<string[]>
let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind((language) => let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind((language) =>
wikiIds?.map((wikiIds) => wikiIds?.map((id) => Wikipedia.fetchArticleAndWikidata(id, language))) wikiIds?.map((wikiIds) => wikiIds?.map((id) => Wikipedia.fetchArticleAndWikidata(id, language)))
); )
let _wikipediaStores let _wikipediaStores
onDestroy( onDestroy(
wikipediaStores.addCallbackAndRunD((wikipediaStores) => { wikipediaStores.addCallbackAndRunD((wikipediaStores) => {

View file

@ -1,7 +1,7 @@
{ {
"contributors": [ "contributors": [
{ {
"commits": 5965, "commits": 6039,
"contributor": "Pieter Vander Vennet" "contributor": "Pieter Vander Vennet"
}, },
{ {
@ -33,7 +33,7 @@
"contributor": "paunofu" "contributor": "paunofu"
}, },
{ {
"commits": 28, "commits": 29,
"contributor": "Hosted Weblate" "contributor": "Hosted Weblate"
}, },
{ {

View file

@ -12,7 +12,7 @@
"gl": "lingua galega", "gl": "lingua galega",
"he": "עברית", "he": "עברית",
"hu": "magyar", "hu": "magyar",
"id": "Indonesia", "id": "bahasa Indonesia",
"it": "italiano", "it": "italiano",
"ja": "日本語", "ja": "日本語",
"nb_NO": "bokmål", "nb_NO": "bokmål",
@ -23,5 +23,6 @@
"ru": "русский язык", "ru": "русский язык",
"sl": "slovenščina", "sl": "slovenščina",
"sv": "svenska", "sv": "svenska",
"zh_Hant": "簡體中文" "zh_Hans": "简体中文",
"zh_Hant": "繁體中文"
} }

View file

@ -146,7 +146,7 @@
"gl": "Lingua adigue", "gl": "Lingua adigue",
"he": "אדיגית", "he": "אדיגית",
"hu": "adigei", "hu": "adigei",
"id": "Adyghe", "id": "bahasa Adyghe",
"it": "adighè", "it": "adighè",
"ja": "アディゲ語", "ja": "アディゲ語",
"nb_NO": "adygeisk", "nb_NO": "adygeisk",
@ -603,7 +603,7 @@
"gl": "árabe", "gl": "árabe",
"he": "ערבית", "he": "ערבית",
"hu": "arab", "hu": "arab",
"id": "Arab", "id": "bahasa Arab",
"it": "arabo", "it": "arabo",
"ja": "アラビア語", "ja": "アラビア語",
"nb_NO": "arabisk", "nb_NO": "arabisk",
@ -929,7 +929,7 @@
"fi": "Awadhin kieli", "fi": "Awadhin kieli",
"fr": "awadhi", "fr": "awadhi",
"gl": "Lingua awadhi", "gl": "Lingua awadhi",
"he": "אוודית", "he": "אוודהית",
"id": "Bahasa Awadhi", "id": "Bahasa Awadhi",
"it": "awadhi", "it": "awadhi",
"ja": "アワディー語", "ja": "アワディー語",
@ -1603,7 +1603,7 @@
"gl": "lingua bretoa", "gl": "lingua bretoa",
"he": "ברטונית", "he": "ברטונית",
"hu": "breton", "hu": "breton",
"id": "Breton", "id": "Bahasa Breton",
"it": "bretone", "it": "bretone",
"ja": "ブルトン語", "ja": "ブルトン語",
"nb_NO": "bretonsk", "nb_NO": "bretonsk",
@ -1772,7 +1772,7 @@
"gl": "Lingua buriata", "gl": "Lingua buriata",
"he": "בוריאטית", "he": "בוריאטית",
"hu": "burját", "hu": "burját",
"id": "Buryat", "id": "bahasa Buryat",
"it": "buriato", "it": "buriato",
"ja": "ブリヤート語", "ja": "ブリヤート語",
"nb_NO": "burjatisk", "nb_NO": "burjatisk",
@ -2316,7 +2316,7 @@
"gl": "Lingua tártara de Crimea", "gl": "Lingua tártara de Crimea",
"he": "טטרית של קרים", "he": "טטרית של קרים",
"hu": "krími tatár", "hu": "krími tatár",
"id": "Tatar Krimea", "id": "Bahasa Tatar Krimea",
"it": "tataro di Crimea", "it": "tataro di Crimea",
"ja": "クリミア・タタール語", "ja": "クリミア・タタール語",
"nb_NO": "krimtatarisk", "nb_NO": "krimtatarisk",
@ -2442,7 +2442,6 @@
"id": "Bahasa Chittagonia", "id": "Bahasa Chittagonia",
"it": "lingua chittagonian", "it": "lingua chittagonian",
"ja": "チッタゴン語", "ja": "チッタゴン語",
"nb_NO": "Chittagong",
"pl": "Język chatgaya", "pl": "Język chatgaya",
"pt": "Língua chittagong", "pt": "Língua chittagong",
"pt_BR": "Língua chittagong", "pt_BR": "Língua chittagong",
@ -2533,7 +2532,7 @@
"gl": "lingua dinamarquesa", "gl": "lingua dinamarquesa",
"he": "דנית", "he": "דנית",
"hu": "dán", "hu": "dán",
"id": "Denmark", "id": "bahasa Denmark",
"it": "danese", "it": "danese",
"ja": "デンマーク語", "ja": "デンマーク語",
"nb_NO": "dansk", "nb_NO": "dansk",
@ -2596,7 +2595,7 @@
"gl": "lingua alemá", "gl": "lingua alemá",
"he": "גרמנית", "he": "גרמנית",
"hu": "német", "hu": "német",
"id": "Jerman", "id": "bahasa Jerman",
"it": "tedesco", "it": "tedesco",
"ja": "ドイツ語", "ja": "ドイツ語",
"nb_NO": "tysk", "nb_NO": "tysk",
@ -2967,8 +2966,8 @@
"ru": "новогреческий язык", "ru": "новогреческий язык",
"sl": "novogrščina", "sl": "novogrščina",
"sv": "nygrekiska", "sv": "nygrekiska",
"zh_Hans": "现代希腊语", "zh_Hans": "希腊语",
"zh_Hant": "現代希臘語", "zh_Hant": "希臘語",
"_meta": { "_meta": {
"countries": [ "countries": [
"CY", "CY",
@ -3585,7 +3584,7 @@
"gl": "lingua feroesa", "gl": "lingua feroesa",
"he": "פארואזית", "he": "פארואזית",
"hu": "feröeri", "hu": "feröeri",
"id": "Faroe", "id": "bahasa Faroe",
"it": "faroese", "it": "faroese",
"ja": "フェロー語", "ja": "フェロー語",
"nb_NO": "færøysk", "nb_NO": "færøysk",
@ -4873,7 +4872,7 @@
"gl": "lingua indonesia", "gl": "lingua indonesia",
"he": "אינדונזית", "he": "אינדונזית",
"hu": "indonéz", "hu": "indonéz",
"id": "Indonesia", "id": "bahasa Indonesia",
"it": "indonesiano", "it": "indonesiano",
"ja": "インドネシア語", "ja": "インドネシア語",
"nb_NO": "indonesisk", "nb_NO": "indonesisk",
@ -5020,7 +5019,7 @@
"gl": "lingua islandesa", "gl": "lingua islandesa",
"he": "איסלנדית", "he": "איסלנדית",
"hu": "izlandi", "hu": "izlandi",
"id": "Islandia", "id": "bahasa Islandia",
"it": "islandese", "it": "islandese",
"ja": "アイスランド語", "ja": "アイスランド語",
"nb_NO": "islandsk", "nb_NO": "islandsk",
@ -5056,7 +5055,7 @@
"gl": "lingua italiana", "gl": "lingua italiana",
"he": "איטלקית", "he": "איטלקית",
"hu": "olasz", "hu": "olasz",
"id": "Italia", "id": "bahasa Italia",
"it": "italiano", "it": "italiano",
"ja": "イタリア語", "ja": "イタリア語",
"nb_NO": "italiensk", "nb_NO": "italiensk",
@ -5128,7 +5127,7 @@
"gl": "lingua xaponesa", "gl": "lingua xaponesa",
"he": "יפנית", "he": "יפנית",
"hu": "japán", "hu": "japán",
"id": "Jepang", "id": "bahasa Jepang",
"it": "giapponese", "it": "giapponese",
"ja": "日本語", "ja": "日本語",
"nb_NO": "japansk", "nb_NO": "japansk",
@ -5211,7 +5210,7 @@
"gl": "Lingua xavanesa", "gl": "Lingua xavanesa",
"he": "ג'אווה", "he": "ג'אווה",
"hu": "jávai", "hu": "jávai",
"id": "Jawa", "id": "bahasa Jawa",
"it": "giavanese", "it": "giavanese",
"ja": "ジャワ語", "ja": "ジャワ語",
"nb_NO": "javanesisk", "nb_NO": "javanesisk",
@ -5248,7 +5247,7 @@
"gl": "lingua xeorxiana", "gl": "lingua xeorxiana",
"he": "גאורגית", "he": "גאורגית",
"hu": "grúz", "hu": "grúz",
"id": "Georgia", "id": "Bahasa Georgia",
"it": "georgiano", "it": "georgiano",
"ja": "ジョージア語", "ja": "ジョージア語",
"nb_NO": "georgisk", "nb_NO": "georgisk",
@ -5283,7 +5282,7 @@
"gl": "Lingua karakalpak", "gl": "Lingua karakalpak",
"he": "קראקלפקית", "he": "קראקלפקית",
"hu": "karakalpak", "hu": "karakalpak",
"id": "Karakalpak", "id": "Bahasa Karakalpak",
"it": "karakalpako", "it": "karakalpako",
"ja": "カラカルパク語", "ja": "カラカルパク語",
"nl": "Karakalpaks", "nl": "Karakalpaks",
@ -5467,7 +5466,6 @@
"ja": "カインガング語", "ja": "カインガング語",
"nb_NO": "Kaingang", "nb_NO": "Kaingang",
"nl": "Kaingang", "nl": "Kaingang",
"pl": "Języki caingang",
"pt": "Língua caingangue", "pt": "Língua caingangue",
"pt_BR": "Língua kaingáng", "pt_BR": "Língua kaingáng",
"ru": "Каинганг", "ru": "Каинганг",
@ -5638,7 +5636,7 @@
"gl": "Lingua casaca", "gl": "Lingua casaca",
"he": "קזחית", "he": "קזחית",
"hu": "kazak", "hu": "kazak",
"id": "Kazakh", "id": "bahasa Kazakh",
"it": "kazako", "it": "kazako",
"ja": "カザフ語", "ja": "カザフ語",
"nb_NO": "kasakhisk", "nb_NO": "kasakhisk",
@ -5675,7 +5673,7 @@
"gl": "Lingua grenlandesa", "gl": "Lingua grenlandesa",
"he": "גרינלנדית", "he": "גרינלנדית",
"hu": "grönlandi", "hu": "grönlandi",
"id": "Greenland", "id": "bahasa Greenland",
"it": "groenlandese", "it": "groenlandese",
"ja": "グリーンランド語", "ja": "グリーンランド語",
"nb_NO": "grønlandsk", "nb_NO": "grønlandsk",
@ -5707,7 +5705,7 @@
"gl": "Lingua khmer", "gl": "Lingua khmer",
"he": "קמרית", "he": "קמרית",
"hu": "khmer", "hu": "khmer",
"id": "Khmer", "id": "bahasa Khmer",
"it": "khmer", "it": "khmer",
"ja": "クメール語", "ja": "クメール語",
"nb_NO": "khmer", "nb_NO": "khmer",
@ -5818,7 +5816,6 @@
"pl": "język komi-permiacki", "pl": "język komi-permiacki",
"pt": "Língua komi-permyak", "pt": "Língua komi-permyak",
"ru": "коми-пермяцкий язык", "ru": "коми-пермяцкий язык",
"sl": "permjaščina",
"sv": "komi-permjakiska", "sv": "komi-permjakiska",
"zh_Hans": "彼尔姆科米语", "zh_Hans": "彼尔姆科米语",
"zh_Hant": "彼爾姆科米語", "zh_Hant": "彼爾姆科米語",
@ -6028,32 +6025,32 @@
} }
}, },
"ku": { "ku": {
"ca": "kurd", "ca": "kurd del nord",
"cs": "kurdština", "cs": "kurmándží",
"da": "kurdisk", "da": "Kurmanji",
"de": "Kurdisch", "de": "Kurmandschi",
"en": "Kurdish", "en": "Kurmanji",
"eo": "kurda lingvo", "eo": "kurmanĝa lingvo",
"es": "kurdo", "es": "kurmanji",
"eu": "kurduera", "eu": "Kurmanji",
"fi": "kurdi", "fi": "Kurmandži",
"fr": "kurde", "fr": "kurmandji",
"gl": "lingua kurda", "gl": "lingua kurda",
"he": "כורדית", "he": "כורמנג'ית",
"hu": "kurd", "hu": "kurmandzsi",
"id": "Bahasa Kurdi", "id": "Kurmanji",
"it": "curdo", "it": "kurmanji",
"ja": "クルド語", "ja": "クルマンジー",
"nb_NO": "kurdisk", "nb_NO": "kurdisk",
"nl": "Koerdisch", "nl": "Kurmançi",
"pl": "język kurdyjski", "pl": "język kurmandżi",
"pt": "língua curda", "pt": "curmânji",
"pt_BR": "língua curda", "pt_BR": "Curmânji",
"ru": "курдские языки", "ru": "курманджи",
"sl": "kurdščina", "sl": "kurmandži",
"sv": "kurdiska", "sv": "nordkurdiska",
"zh_Hans": "库尔德语", "zh_Hans": "库尔德语",
"zh_Hant": "庫德語", "zh_Hant": "庫德語",
"_meta": { "_meta": {
"countries": [ "countries": [
"IQ" "IQ"
@ -6130,7 +6127,7 @@
"gl": "lingua komi", "gl": "lingua komi",
"he": "קומי", "he": "קומי",
"hu": "komi", "hu": "komi",
"id": "Komi", "id": "Bahasa Komi",
"it": "comi", "it": "comi",
"ja": "コミ語", "ja": "コミ語",
"nb_NO": "syrjensk", "nb_NO": "syrjensk",
@ -6138,7 +6135,6 @@
"pl": "język komi", "pl": "język komi",
"pt": "língua komi", "pt": "língua komi",
"ru": "коми язык", "ru": "коми язык",
"sl": "komijščina",
"sv": "komi", "sv": "komi",
"_meta": { "_meta": {
"dir": [ "dir": [
@ -6221,7 +6217,7 @@
"gl": "kirguiz", "gl": "kirguiz",
"he": "קירגיזית", "he": "קירגיזית",
"hu": "kirgiz", "hu": "kirgiz",
"id": "Kirgiz", "id": "bahasa Kirgiz",
"it": "kirghiso", "it": "kirghiso",
"ja": "キルギス語", "ja": "キルギス語",
"nb_NO": "kirgisisk", "nb_NO": "kirgisisk",
@ -6307,7 +6303,7 @@
"gl": "Lingua luxemburguesa", "gl": "Lingua luxemburguesa",
"he": "לוקסמבורגית", "he": "לוקסמבורגית",
"hu": "luxemburgi", "hu": "luxemburgi",
"id": "Luksemburg", "id": "bahasa Luksemburg",
"it": "lussemburghese", "it": "lussemburghese",
"ja": "ルクセンブルク語", "ja": "ルクセンブルク語",
"nb_NO": "luxembourgsk", "nb_NO": "luxembourgsk",
@ -6547,7 +6543,7 @@
"gl": "Lingua lombarda", "gl": "Lingua lombarda",
"he": "לומברד (שפה)", "he": "לומברד (שפה)",
"hu": "lombard", "hu": "lombard",
"id": "Lombard", "id": "bahasa Lombard",
"it": "lingua lombarda", "it": "lingua lombarda",
"ja": "ロンバルド語", "ja": "ロンバルド語",
"nb_NO": "lombardisk", "nb_NO": "lombardisk",
@ -6607,7 +6603,7 @@
"gl": "Lingua laosiana", "gl": "Lingua laosiana",
"he": "לאית", "he": "לאית",
"hu": "lao", "hu": "lao",
"id": "Lao", "id": "bahasa Lao",
"it": "lao", "it": "lao",
"ja": "ラーオ語", "ja": "ラーオ語",
"nb_NO": "laotisk", "nb_NO": "laotisk",
@ -6977,7 +6973,7 @@
"gl": "Lingua malgaxe", "gl": "Lingua malgaxe",
"he": "מלגשית", "he": "מלגשית",
"hu": "malgas", "hu": "malgas",
"id": "Malagasi", "id": "Bahasa Malagasi",
"it": "malgascio", "it": "malgascio",
"ja": "マダガスカル語", "ja": "マダガスカル語",
"nb_NO": "gassisk", "nb_NO": "gassisk",
@ -7163,7 +7159,7 @@
"gl": "Lingua macedonia", "gl": "Lingua macedonia",
"he": "מקדונית", "he": "מקדונית",
"hu": "macedón", "hu": "macedón",
"id": "Makedonia", "id": "bahasa Makedonia",
"it": "macedone", "it": "macedone",
"ja": "マケドニア語", "ja": "マケドニア語",
"nb_NO": "makedonsk", "nb_NO": "makedonsk",
@ -7231,7 +7227,7 @@
"gl": "Lingua mongol", "gl": "Lingua mongol",
"he": "מונגולית", "he": "מונגולית",
"hu": "mongol", "hu": "mongol",
"id": "Mongol", "id": "bahasa Mongol",
"it": "mongolo", "it": "mongolo",
"ja": "モンゴル語", "ja": "モンゴル語",
"nb_NO": "mongolsk", "nb_NO": "mongolsk",
@ -7472,7 +7468,7 @@
"gl": "lingua malaia", "gl": "lingua malaia",
"he": "מלאית", "he": "מלאית",
"hu": "maláj", "hu": "maláj",
"id": "Melayu", "id": "bahasa Melayu",
"it": "malese", "it": "malese",
"ja": "マレー語", "ja": "マレー語",
"nb_NO": "malayisk", "nb_NO": "malayisk",
@ -7650,7 +7646,7 @@
"gl": "birmano", "gl": "birmano",
"he": "בורמזית", "he": "בורמזית",
"hu": "burmai", "hu": "burmai",
"id": "Burma", "id": "bahasa Burma",
"it": "birmano", "it": "birmano",
"ja": "ビルマ語", "ja": "ビルマ語",
"nb_NO": "burmesisk", "nb_NO": "burmesisk",
@ -8112,7 +8108,7 @@
"gl": "lingua norueguesa", "gl": "lingua norueguesa",
"he": "נורווגית", "he": "נורווגית",
"hu": "norvég", "hu": "norvég",
"id": "Norwegia", "id": "bahasa Norwegia",
"it": "norvegese", "it": "norvegese",
"ja": "ノルウェー語", "ja": "ノルウェー語",
"nb_NO": "norsk", "nb_NO": "norsk",
@ -8439,12 +8435,12 @@
"eo": "olonec-karela lingvo", "eo": "olonec-karela lingvo",
"fi": "livvinkarjala", "fi": "livvinkarjala",
"fr": "olonetsien", "fr": "olonetsien",
"gl": "lingua livvi", "gl": "Lingua livvi",
"it": "lingua livvi", "it": "lingua livvi",
"ja": "リッヴィ語", "ja": "リッヴィ語",
"nb_NO": "livvisk", "nb_NO": "livvisk",
"nl": "Olonetsisch", "nl": "Olonetsisch",
"pl": "dialekt ołoniecki", "pl": "Dialekt ołoniecki",
"ru": "ливвиковское наречие", "ru": "ливвиковское наречие",
"sv": "livvi", "sv": "livvi",
"zh_Hant": "利維卡累利阿語", "zh_Hant": "利維卡累利阿語",
@ -8549,7 +8545,7 @@
"gl": "Lingua oseta", "gl": "Lingua oseta",
"he": "אוסטית", "he": "אוסטית",
"hu": "oszét", "hu": "oszét",
"id": "Ossetia", "id": "bahasa Ossetia",
"it": "osseto", "it": "osseto",
"ja": "オセット語", "ja": "オセット語",
"nb_NO": "ossetisk", "nb_NO": "ossetisk",
@ -8625,7 +8621,7 @@
"gl": "lingua punjabi (Shahmukhi)", "gl": "lingua punjabi (Shahmukhi)",
"he": "פנג'אבי (אלפבית שאהמוקי)", "he": "פנג'אבי (אלפבית שאהמוקי)",
"hu": "pandzsábi (Shahmukhi)", "hu": "pandzsábi (Shahmukhi)",
"id": "Punjab (Abjad Shahmukhi)", "id": "Bahasa Punjab (Abjad Shahmukhi)",
"it": "punjabi (Shahmukhī)", "it": "punjabi (Shahmukhī)",
"ja": "パンジャーブ語 (シャームキー文字)", "ja": "パンジャーブ語 (シャームキー文字)",
"nb_NO": "panjabi (Shahmukhi)", "nb_NO": "panjabi (Shahmukhi)",
@ -8850,7 +8846,6 @@
"pl": "Język neosalomoński", "pl": "Język neosalomoński",
"pt": "Língua pijin", "pt": "Língua pijin",
"ru": "Пиджин Соломоновых Островов", "ru": "Пиджин Соломоновых Островов",
"sl": "salomonski pidžin",
"sv": "pijin", "sv": "pijin",
"_meta": { "_meta": {
"dir": [ "dir": [
@ -9048,7 +9043,7 @@
"gl": "lingua portuguesa", "gl": "lingua portuguesa",
"he": "פורטוגזית", "he": "פורטוגזית",
"hu": "portugál", "hu": "portugál",
"id": "Portugis", "id": "bahasa Portugis",
"it": "portoghese", "it": "portoghese",
"ja": "ポルトガル語", "ja": "ポルトガル語",
"nb_NO": "portugisisk", "nb_NO": "portugisisk",
@ -9258,7 +9253,7 @@
"en": "Rakhine", "en": "Rakhine",
"fr": "arakanais", "fr": "arakanais",
"gl": "Lingua arakanesa", "gl": "Lingua arakanesa",
"id": "Rakhine", "id": "bahasa Rakhine",
"ja": "ラカイン語", "ja": "ラカイン語",
"nl": "Arakanees", "nl": "Arakanees",
"pl": "Język arakański", "pl": "Język arakański",
@ -9506,7 +9501,7 @@
"gl": "Lingua arromanesa", "gl": "Lingua arromanesa",
"he": "ארומנית", "he": "ארומנית",
"hu": "aromán", "hu": "aromán",
"id": "Arumania", "id": "Bahasa Arumania",
"it": "arumeno", "it": "arumeno",
"ja": "アルーマニア語", "ja": "アルーマニア語",
"nb_NO": "arumensk", "nb_NO": "arumensk",
@ -9901,7 +9896,7 @@
"ca": "taixelhit", "ca": "taixelhit",
"cs": "tašelhit", "cs": "tašelhit",
"de": "Taschelhit", "de": "Taschelhit",
"en": "Tachelhit", "en": "Shilha",
"eo": "ŝelha lingvo", "eo": "ŝelha lingvo",
"es": "chilha", "es": "chilha",
"fi": "Tašelhit", "fi": "Tašelhit",
@ -10001,7 +9996,7 @@
"pt": "Língua cingalesa", "pt": "Língua cingalesa",
"pt_BR": "Língua cingalesa", "pt_BR": "Língua cingalesa",
"ru": "сингальский язык", "ru": "сингальский язык",
"sl": "singalščina", "sl": "sinhalščina",
"sv": "singalesiska", "sv": "singalesiska",
"zh_Hant": "僧伽羅語", "zh_Hant": "僧伽羅語",
"_meta": { "_meta": {
@ -10461,7 +10456,7 @@
"gl": "Lingua albanesa", "gl": "Lingua albanesa",
"he": "אלבנית", "he": "אלבנית",
"hu": "albán", "hu": "albán",
"id": "Albania", "id": "Bahasa Albania",
"it": "albanese", "it": "albanese",
"ja": "アルバニア語", "ja": "アルバニア語",
"nb_NO": "albansk", "nb_NO": "albansk",
@ -10704,7 +10699,7 @@
"gl": "lingua sueca", "gl": "lingua sueca",
"he": "שוודית", "he": "שוודית",
"hu": "svéd", "hu": "svéd",
"id": "Swedia", "id": "bahasa Swedia",
"it": "svedese", "it": "svedese",
"ja": "スウェーデン語", "ja": "スウェーデン語",
"nb_NO": "svensk", "nb_NO": "svensk",
@ -10803,7 +10798,7 @@
"gl": "Lingua silesiana", "gl": "Lingua silesiana",
"he": "שלזית", "he": "שלזית",
"hu": "sziléziai", "hu": "sziléziai",
"id": "Silesia", "id": "bahasa Silesia",
"it": "slesiano", "it": "slesiano",
"ja": "シレジア語", "ja": "シレジア語",
"nb_NO": "schlesisk", "nb_NO": "schlesisk",
@ -10852,7 +10847,7 @@
"gl": "Lingua támil", "gl": "Lingua támil",
"he": "טמילית", "he": "טמילית",
"hu": "tamil", "hu": "tamil",
"id": "Tamil", "id": "Bahasa Tamil",
"it": "tamil", "it": "tamil",
"ja": "タミル語", "ja": "タミル語",
"nb_NO": "tamilsk", "nb_NO": "tamilsk",
@ -11039,7 +11034,7 @@
"gl": "lingua tailandesa", "gl": "lingua tailandesa",
"he": "תאית", "he": "תאית",
"hu": "thai", "hu": "thai",
"id": "Thai", "id": "bahasa Thai",
"it": "thailandese", "it": "thailandese",
"ja": "タイ語", "ja": "タイ語",
"nb_NO": "thai", "nb_NO": "thai",
@ -11109,7 +11104,7 @@
"gl": "Lingua turcomá", "gl": "Lingua turcomá",
"he": "טורקמנית", "he": "טורקמנית",
"hu": "türkmén", "hu": "türkmén",
"id": "Turkmen", "id": "bahasa Turkmen",
"it": "Turkmeno", "it": "Turkmeno",
"ja": "トルクメン語", "ja": "トルクメン語",
"nb_NO": "turkmensk", "nb_NO": "turkmensk",
@ -11637,7 +11632,7 @@
"gl": "Lingua uigur", "gl": "Lingua uigur",
"he": "אויגורית", "he": "אויגורית",
"hu": "ujgur", "hu": "ujgur",
"id": "Uighur", "id": "bahasa Uyghur",
"it": "uiguro", "it": "uiguro",
"ja": "ウイグル語", "ja": "ウイグル語",
"nb_NO": "uigurisk", "nb_NO": "uigurisk",
@ -11707,7 +11702,7 @@
"gl": "Lingua usbeka", "gl": "Lingua usbeka",
"he": "אוזבקית", "he": "אוזבקית",
"hu": "üzbég", "hu": "üzbég",
"id": "Uzbek", "id": "bahasa Uzbek",
"it": "uzbeco", "it": "uzbeco",
"ja": "ウズベク語", "ja": "ウズベク語",
"nb_NO": "usbekisk", "nb_NO": "usbekisk",
@ -12596,7 +12591,7 @@
"gl": "lingua chinesa", "gl": "lingua chinesa",
"he": "סינית", "he": "סינית",
"hu": "kínai", "hu": "kínai",
"id": "Tionghoa", "id": "bahasa Tionghoa",
"it": "cinese", "it": "cinese",
"ja": "中国語", "ja": "中国語",
"nb_NO": "kinesisk", "nb_NO": "kinesisk",
@ -12652,7 +12647,7 @@
] ]
} }
}, },
"zh_Hant": { "zh_Hans": {
"ca": "xinès simplificat", "ca": "xinès simplificat",
"cs": "zjednodušená čínština", "cs": "zjednodušená čínština",
"da": "forenklet kinesisk", "da": "forenklet kinesisk",
@ -12661,7 +12656,6 @@
"eo": "simpligita ĉina skribsistemo", "eo": "simpligita ĉina skribsistemo",
"es": "chino simplificado", "es": "chino simplificado",
"eu": "Txinera sinplifikatua", "eu": "Txinera sinplifikatua",
"fi": "perinteinen kiina",
"fr": "chinois simplifié", "fr": "chinois simplifié",
"gl": "chinés simplificado", "gl": "chinés simplificado",
"he": "סינית מפושטת", "he": "סינית מפושטת",
@ -12684,6 +12678,36 @@
] ]
} }
}, },
"zh_Hant": {
"ca": "xinès tradicional",
"cs": "čínština (tradiční)",
"da": "traditionel kinesisk",
"de": "traditionelles Chinesisch",
"en": "Traditional Chinese",
"eo": "ĉina lingvo de tradicia ortografio",
"es": "chino tradicional",
"eu": "Txinera tradizional",
"fi": "perinteinen kiina",
"fr": "chinois traditionnel",
"gl": "chinés tradicional",
"he": "סינית מסורתית",
"it": "cinese tradizionale",
"ja": "繁体字中国語",
"nb_NO": "tradisjonell kinesisk",
"nl": "traditioneel Chinees",
"pl": "język chiński tradycyjny",
"pt": "chinês tradicional",
"ru": "традиционный китайский",
"sl": "tradicionalna kitajščina",
"sv": "traditionell kinesiska",
"zh_Hans": "繁体中文",
"zh_Hant": "繁體中文",
"_meta": {
"dir": [
"left-to-right"
]
}
},
"zu": { "zu": {
"ca": "zulu", "ca": "zulu",
"cs": "zuluština", "cs": "zuluština",

View file

@ -1,15 +1,15 @@
{ {
"contributors": [ "contributors": [
{ {
"commits": 303, "commits": 306,
"contributor": "kjon" "contributor": "kjon"
}, },
{ {
"commits": 285, "commits": 287,
"contributor": "Pieter Vander Vennet" "contributor": "Pieter Vander Vennet"
}, },
{ {
"commits": 163, "commits": 171,
"contributor": "paunofu" "contributor": "paunofu"
}, },
{ {