forked from MapComplete/MapComplete
		
	Chore: formatting
This commit is contained in:
		
							parent
							
								
									8ef9b48e2b
								
							
						
					
					
						commit
						8a3f7a012d
					
				
					 97 changed files with 3350 additions and 2136 deletions
				
			
		|  | @ -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  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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'><a href="{_mastodon_candidate}" rel="me">Mastodon</a>*  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'><a href="{_mastodon_candidate}" rel="me">Mastodon</a>*  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) | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -774,4 +790,13 @@ 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) | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -778,4 +794,13 @@ 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) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -774,4 +790,13 @@ 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) | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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": "" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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", | ||||||
|  |  | ||||||
|  | @ -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": "" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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": "" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -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')", | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,2 +1,2 @@ | ||||||
| SPDX-FileCopyrightText: OpenClipArt | SPDX-FileCopyrightText: OpenClipArt | ||||||
| SPDX-License-Identifier: PD | SPDX-License-Identifier: PUBLIC-DOMAIN | ||||||
|  | @ -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 | ||||||
							
								
								
									
										2
									
								
								assets/svg/mangrove_logo.svg.license
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								assets/svg/mangrove_logo.svg.license
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | SPDX-FileCopyrightText: Mangrove.reviews | ||||||
|  | SPDX-License-Identifier: LicenseRef-LOGO | ||||||
							
								
								
									
										16
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -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": { | ||||||
|  |  | ||||||
|  | @ -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.", | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 }> | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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
											
										
									
								
							|  | @ -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, | ||||||
|             } |             } | ||||||
|         ); |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(/</g,'<')?.replace(/>/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(/</g, "<")?.replace(/>/g, ">") ?? "" | ||||||
|  |         ) | ||||||
|  |         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => | ||||||
|  |             ((feat) => { | ||||||
|  |                 const e = document.createElement("div") | ||||||
|  |                 e.innerHTML = feat.properties._d | ||||||
|  |                 return Array.from(e.getElementsByTagName("a")).filter( | ||||||
|  |                     (a) => a.href.match(/mastodon|en.osm.town/) !== null | ||||||
|  |                 )[0]?.href | ||||||
|  |             })(feat) | ||||||
|  |         ) | ||||||
|  |         Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => | ||||||
|  |             ((feat) => { | ||||||
|  |                 const e = document.createElement("div") | ||||||
|  |                 e.innerHTML = feat.properties._d | ||||||
|  |                 return Array.from(e.getElementsByTagName("a")).filter( | ||||||
|  |                     (a) => a.getAttribute("rel")?.indexOf("me") >= 0 | ||||||
|  |                 )[0]?.href | ||||||
|  |             })(feat) | ||||||
|  |         ) | ||||||
|  |         Utils.AddLazyProperty( | ||||||
|  |             feat.properties, | ||||||
|  |             "_mastodon_candidate", | ||||||
|  |             () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a | ||||||
|  |         ) | ||||||
|  |         feat.properties["__current_backgroun"] = "initial_value" | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -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 | ||||||
|         }); |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 /> | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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"> | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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} /> | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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("") | ||||||
|  |  | ||||||
|  | @ -23,17 +23,19 @@ | ||||||
|   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 | ||||||
|  |  | ||||||
|  | @ -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} /> | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| } |       } | ||||||
|  |  | ||||||
|  | @ -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", | ||||||
|  |  | ||||||
|  | @ -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} | ||||||
|  |  | ||||||
|  | @ -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) => { | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -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": "繁體中文" | ||||||
| } | } | ||||||
|  | @ -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", | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue