forked from MapComplete/MapComplete
Refactoring: put all special visualisations into their own class, add their location into the documentation
This commit is contained in:
parent
c0e7c9e8fa
commit
ae5205f92d
29 changed files with 2270 additions and 2075 deletions
|
@ -139,7 +139,7 @@ Show a literal text within braces
|
|||
-----|-----|----- |
|
||||
| text | _undefined_ | The value to show |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L313](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L313)
|
||||
|
||||
#### Example usage of braced
|
||||
|
||||
|
@ -149,7 +149,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L293](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L293)
|
||||
|
||||
#### Example usage of title
|
||||
|
||||
|
@ -163,7 +163,7 @@ If the given key can be interpreted as a JSON, only show the key containing the
|
|||
-----|-----|----- |
|
||||
| key | value | The attribute to interpret as json |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L260](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L260)
|
||||
|
||||
#### Example usage of translated
|
||||
|
||||
|
@ -177,7 +177,7 @@ Visualises data of a POI, sometimes with data updating capabilities
|
|||
|
||||
Prints all key-value pairs of the object - used for debugging
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L274](/src/UI/Popup/DataVisualisations.ts#L274)
|
||||
|
||||
#### Example usage of all_tags
|
||||
|
||||
|
@ -191,7 +191,7 @@ Converts a short, canonical value into the long, translated text including the u
|
|||
-----|-----|----- |
|
||||
| key | _undefined_ | The key of the tag to give the canonical text for |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L168](/src/UI/Popup/DataVisualisations.ts#L168)
|
||||
|
||||
#### Example usage of canonical
|
||||
|
||||
|
@ -206,7 +206,7 @@ Converts compass degrees (with 0° being north, 90° being east, ...) into a hum
|
|||
| key | _direction:centerpoint | The attribute containing the degrees |
|
||||
| offset | 0 | Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L45](/src/UI/Popup/DataVisualisations.ts#L45)
|
||||
|
||||
#### Example usage of direction_absolute
|
||||
|
||||
|
@ -216,7 +216,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L28](/src/UI/Popup/DataVisualisations.ts#L28)
|
||||
|
||||
#### Example usage of direction_indicator
|
||||
|
||||
|
@ -232,7 +232,7 @@ A small element, showing if the POI is currently open and when the next change i
|
|||
| prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__ |
|
||||
| postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__ |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L127](/src/UI/Popup/DataVisualisations.ts#L127)
|
||||
|
||||
#### Example usage of opening_hours_state
|
||||
|
||||
|
@ -248,7 +248,7 @@ Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to c
|
|||
| prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__ |
|
||||
| postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__ |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L89](/src/UI/Popup/DataVisualisations.ts#L89)
|
||||
|
||||
#### Example usage of opening_hours_table
|
||||
|
||||
|
@ -258,7 +258,7 @@ A normal opening hours table can be invoked with `{opening_hours_table()}`. A ta
|
|||
|
||||
Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L209](/src/UI/Popup/DataVisualisations.ts#L209)
|
||||
|
||||
#### Example usage of statistics
|
||||
|
||||
|
@ -286,7 +286,7 @@ Elements to help with importing data to OSM. For example: buttons to import a fe
|
|||
| text | _undefined_ | The text to show on the button |
|
||||
| icon | ./assets/svg/robot.svg | The icon to show on the button |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/AutoApplyButtonVis.ts#L64](/src/UI/Popup/AutoApplyButtonVis.ts#L64)
|
||||
|
||||
#### Example usage of auto_apply
|
||||
|
||||
|
@ -302,7 +302,7 @@ Gives an interactive element which shows a tag comparison between the OSM-object
|
|||
| host | _undefined_ | The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. |
|
||||
| readonly | _undefined_ | If 'yes', will not show 'apply'-buttons |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L239](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L239)
|
||||
|
||||
#### Example usage of compare_data
|
||||
|
||||
|
@ -362,7 +362,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
| icon | ./assets/svg/addSmall.svg | A nice icon to show in the button |
|
||||
| way_to_conflate | _undefined_ | The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L25](/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L25)
|
||||
|
||||
#### Example usage of conflate_button
|
||||
|
||||
|
@ -426,7 +426,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
| maproulette_id | _undefined_ | The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer. |
|
||||
| to_point | _undefined_ | If set, a feature will be converted to a centerpoint |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/ImportButtons/PointImportButtonViz.ts#L17](/src/UI/Popup/ImportButtons/PointImportButtonViz.ts#L17)
|
||||
|
||||
#### Example usage of import_button
|
||||
|
||||
|
@ -491,7 +491,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
| snap_onto_layers | _undefined_ | If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead |
|
||||
| snap_to_layer_max_distance | 0.1 | Distance to distort the geometry to snap to this layer |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22](/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22)
|
||||
|
||||
#### Example usage of import_way_button
|
||||
|
||||
|
@ -509,7 +509,7 @@ Attempts to load (via a proxy) the specified website and parsed ld+json from the
|
|||
| mode | _undefined_ | If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM |
|
||||
| collapsed | yes | If the containing accordion should be closed |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L102](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L102)
|
||||
|
||||
#### Example usage of linked_data_from_website
|
||||
|
||||
|
@ -528,7 +528,7 @@ Change the status of the given MapRoulette task
|
|||
| maproulette_id | mr_taskId | The property name containing the maproulette id |
|
||||
| ask_feedback | _empty string_ | If not an empty string, this will be used as question to ask some additional feedback. A text field will be added |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L19](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L19)
|
||||
|
||||
#### Example usage of maproulette_set_status
|
||||
|
||||
|
@ -558,7 +558,7 @@ Sends the images linked to the current object to plantnet.org and asks it what p
|
|||
-----|-----|----- |
|
||||
| image_key | image,mapillary,image,wikidata,wikimedia_commons,image,panoramax,image,image | The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/PlantNetDetectionViz.ts#L13](/src/UI/Popup/PlantNetDetectionViz.ts#L13)
|
||||
|
||||
#### Example usage of plantnet_detection
|
||||
|
||||
|
@ -589,7 +589,7 @@ Note that these values can be prepare with javascript in the theme by using a [c
|
|||
| id_of_object_to_apply_this_one | _undefined_ | If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element |
|
||||
| maproulette_id | _undefined_ | If specified, this maproulette-challenge will be closed when the tags are applied. This should be the `id` of the individual task, _not_ the task_id (which corresponds with the challenge). |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagApplyViz.ts#L13](/src/UI/SpecialVisualisations/TagApplyViz.ts#L13)
|
||||
|
||||
#### Example usage of tag_apply
|
||||
|
||||
|
@ -603,7 +603,7 @@ These special visualisations are (mostly) interactive components that most eleme
|
|||
|
||||
An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L245](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L245)
|
||||
|
||||
#### Example usage of add_new_point
|
||||
|
||||
|
@ -613,7 +613,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Adds a button which allows to delete the object at this location. The config will be read from the layer config
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L154](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L154)
|
||||
|
||||
#### Example usage of delete_button
|
||||
|
||||
|
@ -628,7 +628,7 @@ Shows a 'nothing is currently known-message if there is at least one unanswered
|
|||
| text | _undefined_ | Text to show |
|
||||
| cssClasses | _undefined_ | Classes to apply onto the text |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L213](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L213)
|
||||
|
||||
#### Example usage of if_nothing_known
|
||||
|
||||
|
@ -643,7 +643,7 @@ A small map showing the selected feature.
|
|||
| zoomlevel | 18 | The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close |
|
||||
| idKey | id | (Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled) |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81)
|
||||
|
||||
#### Example usage of minimap
|
||||
|
||||
|
@ -653,7 +653,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Adds a button which allows to move the object to another location. The config will be read from the layer config
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L129](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L129)
|
||||
|
||||
#### Example usage of move_button
|
||||
|
||||
|
@ -668,7 +668,7 @@ Generates a QR-code to share the selected object
|
|||
| text | _undefined_ | Extra text on the side of the QR-code |
|
||||
| textClass | _undefined_ | CSS class of the the side text |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L180](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L180)
|
||||
|
||||
#### Example usage of qr_code
|
||||
|
||||
|
@ -684,7 +684,7 @@ The special element which shows the questions which are unknown. Added by defaul
|
|||
| blacklisted-labels | _undefined_ | One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'. If both a whitelist and a blacklist are given, will show questions having at least one label from the whitelist but none of the blacklist. |
|
||||
| show_all | _undefined_ | Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L25](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L25)
|
||||
|
||||
#### Example usage of questions
|
||||
|
||||
|
@ -699,7 +699,7 @@ Creates a link that (attempts to) open the native 'share'-screen
|
|||
| url | _undefined_ | The url to share (default: current URL) |
|
||||
| text | _undefined_ | The text to show on the button. If none is given, will act as a titleIcon |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/ShareLinkViz.ts#L7](/src/UI/Popup/ShareLinkViz.ts#L7)
|
||||
|
||||
#### Example usage of share_link
|
||||
|
||||
|
@ -709,7 +709,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Adds a button which allows to split a way
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L111](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L111)
|
||||
|
||||
#### Example usage of split_button
|
||||
|
||||
|
@ -723,7 +723,7 @@ Elements relating to marking an object as favourite (giving it a heart). Default
|
|||
|
||||
A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L32](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L32)
|
||||
|
||||
#### Example usage of favourite_icon
|
||||
|
||||
|
@ -733,7 +733,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
A button that allows a (logged in) contributor to mark a location as a favourite location
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L9](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L9)
|
||||
|
||||
#### Example usage of favourite_status
|
||||
|
||||
|
@ -751,7 +751,7 @@ Creates an image carousel for the given sources. An attempt will be made to gues
|
|||
-----|-----|----- |
|
||||
| image_key | image;mapillary;image;wikidata;wikimedia_commons;image;panoramax;image;image | The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L57](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L57)
|
||||
|
||||
#### Example usage of image_carousel
|
||||
|
||||
|
@ -767,7 +767,7 @@ Creates a button where a user can upload an image to panoramax
|
|||
| label | _undefined_ | The text to show on the button |
|
||||
| disable_blur | _undefined_ | If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L90](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L90)
|
||||
|
||||
#### Example usage of image_upload
|
||||
|
||||
|
@ -782,7 +782,7 @@ A component showing nearby images loaded from various online services such as Ma
|
|||
| mode | closed | Either `open` or `closed`. If `open`, then the image carousel will always be shown |
|
||||
| readonly | _undefined_ | If 'readonly' or 'yes', will not show the 'link'-button |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L15](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L15)
|
||||
|
||||
#### Example usage of nearby_images
|
||||
|
||||
|
@ -800,7 +800,7 @@ Adds an image to a node
|
|||
-----|-----|----- |
|
||||
| Id-key | id | The property name where the ID of the note to close can be found |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L123](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L123)
|
||||
|
||||
#### Example usage of add_image_to_note
|
||||
|
||||
|
@ -814,7 +814,7 @@ A textfield to add a comment to a node (with the option to close the note).
|
|||
-----|-----|----- |
|
||||
| Id-key | id | The property name where the ID of the note to close can be found |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L81](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L81)
|
||||
|
||||
#### Example usage of add_note_comment
|
||||
|
||||
|
@ -833,7 +833,7 @@ Button to close a note. A predefined text can be defined to close the note with.
|
|||
| minZoom | _undefined_ | If set, only show the closenote button if zoomed in enough |
|
||||
| zoomButton | _undefined_ | Text to show if not zoomed in enough |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L22](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L22)
|
||||
|
||||
#### Example usage of close_note
|
||||
|
||||
|
@ -843,7 +843,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L102](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L102)
|
||||
|
||||
#### Example usage of open_note
|
||||
|
||||
|
@ -858,7 +858,7 @@ Visualises the comments for notes
|
|||
| commentsKey | comments | The property name of the comments, which should be stringified json |
|
||||
| start | 0 | Drop the first 'start' comments |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L143](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L143)
|
||||
|
||||
#### Example usage of visualize_note_comments
|
||||
|
||||
|
@ -878,7 +878,7 @@ Invites the contributor to leave a review. Somewhat small UI-element until inter
|
|||
| fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value |
|
||||
| question | _undefined_ | The question to ask during the review |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L15](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L15)
|
||||
|
||||
#### Example usage of create_review
|
||||
|
||||
|
@ -893,7 +893,7 @@ Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs
|
|||
| 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 |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L62](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L62)
|
||||
|
||||
#### Example usage of list_reviews
|
||||
|
||||
|
@ -908,7 +908,7 @@ Shows stars which represent the average rating on mangrove.
|
|||
| subjectKey | name | The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews. |
|
||||
| fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L97](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L97)
|
||||
|
||||
#### Example usage of rating
|
||||
|
||||
|
@ -924,7 +924,7 @@ A pragmatic combination of `create_review` and `list_reviews`
|
|||
| fallback | _undefined_ | The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value |
|
||||
| question | _undefined_ | The question to ask in the review form. Optional |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L155](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L155)
|
||||
|
||||
#### Example usage of reviews
|
||||
|
||||
|
@ -942,7 +942,7 @@ A button which clears the locally downloaded data and the service worker. Login
|
|||
-----|-----|----- |
|
||||
| text | _undefined_ | The text to show on the button |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L111](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L111)
|
||||
|
||||
#### Example usage of clear_caches
|
||||
|
||||
|
@ -952,7 +952,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
A button to remove the travelled track information from the device
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L221](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L221)
|
||||
|
||||
#### Example usage of clear_location_history
|
||||
|
||||
|
@ -962,7 +962,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows which questions are disabled for every layer. Used in 'settings'
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L43](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L43)
|
||||
|
||||
#### Example usage of disabled_questions
|
||||
|
||||
|
@ -972,7 +972,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows the current tags of the GPS-representing object, used for debugging
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L66](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L66)
|
||||
|
||||
#### Example usage of gps_all_tags
|
||||
|
||||
|
@ -982,7 +982,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows the current tags of the GPS-representing object, used for debugging
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L55](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L55)
|
||||
|
||||
#### Example usage of gyroscope_all_tags
|
||||
|
||||
|
@ -996,7 +996,7 @@ Only makes sense in the usersettings. Allows to import a mangrove public key and
|
|||
-----|-----|----- |
|
||||
| text | _undefined_ | The text that is shown on the button |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L133](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L133)
|
||||
|
||||
#### Example usage of import_mangrove_key
|
||||
|
||||
|
@ -1006,7 +1006,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
A component to set the language of the user interface
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L21](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L21)
|
||||
|
||||
#### Example usage of language_picker
|
||||
|
||||
|
@ -1021,7 +1021,7 @@ Show a login button
|
|||
| force | _undefined_ | Always show this button, even if logged in |
|
||||
| message | _undefined_ | Message to display on the button |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L134](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L134)
|
||||
|
||||
#### Example usage of login_button
|
||||
|
||||
|
@ -1031,7 +1031,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows a button where the user can log out
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L198](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L198)
|
||||
|
||||
#### Example usage of logout
|
||||
|
||||
|
@ -1041,7 +1041,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
A module showing the pending changes, with the option to clear the pending changes
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L210](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L210)
|
||||
|
||||
#### Example usage of pending_changes
|
||||
|
||||
|
@ -1056,7 +1056,7 @@ A QR-code which shares the current URL and adds the login token. Anyone with thi
|
|||
| text | _undefined_ | Extra text on the side of the QR-code |
|
||||
| textClass | _undefined_ | CSS class of the the side text |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L163](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L163)
|
||||
|
||||
#### Example usage of qr_login
|
||||
|
||||
|
@ -1066,7 +1066,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows the current state of storage
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L87](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L87)
|
||||
|
||||
#### Example usage of storage_all_tags
|
||||
|
||||
|
@ -1086,7 +1086,7 @@ A collapsable group (accordion)
|
|||
| labels | _undefined_ | A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion |
|
||||
| blacklist | _undefined_ | A `;`-separated list of either identifiers or label names. Matching tagrenderings will _not_ be included, even if they are in `labels` |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L174](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L174)
|
||||
|
||||
#### Example usage of group
|
||||
|
||||
|
@ -1102,7 +1102,7 @@ Given an embedded tagRendering (read only) and a key, will read the keyname as a
|
|||
| tagrendering | _undefined_ | An entire tagRenderingConfig |
|
||||
| classes | _undefined_ | CSS-classes to apply on every individual item. Seperated by `space` |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L90](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L90)
|
||||
|
||||
#### Example usage of multi
|
||||
|
||||
|
@ -1124,7 +1124,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Opens the current view in the iD-editor
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L216](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L216)
|
||||
|
||||
#### Example usage of open_in_iD
|
||||
|
||||
|
@ -1134,7 +1134,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Opens the current view in the JOSM-editor
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L230](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L230)
|
||||
|
||||
#### Example usage of open_in_josm
|
||||
|
||||
|
@ -1149,7 +1149,7 @@ Shows a tagRendering from a different object as if this was the object itself
|
|||
| featureId | _undefined_ | The key of the attribute which contains the id of the feature from which to use the tags |
|
||||
| tagRenderingId | _undefined_ | The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L17](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L17)
|
||||
|
||||
#### Example usage of steal
|
||||
|
||||
|
@ -1167,7 +1167,7 @@ Converts a fediverse username or link into a clickable link
|
|||
-----|-----|----- |
|
||||
| key | _undefined_ | The attribute-name containing the link |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L16](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L16)
|
||||
|
||||
#### Example usage of fediverse_link
|
||||
|
||||
|
@ -1186,7 +1186,7 @@ Construct a link. By using the 'special' visualisation notation, translations sh
|
|||
| arialabel | _undefined_ | If set, this text will be used as aria-label |
|
||||
| icon | _undefined_ | If set, show this icon next to the link. You might want to combine this with `class: button` |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L139](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L139)
|
||||
|
||||
#### Example usage of link
|
||||
|
||||
|
@ -1200,7 +1200,7 @@ Adds a button to open mapillary on the specified location
|
|||
-----|-----|----- |
|
||||
| zoom | 18 | The startzoom of mapillary |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/MapillaryLinkVis.ts#L8](/src/UI/Popup/MapillaryLinkVis.ts#L8)
|
||||
|
||||
#### Example usage of mapillary_link
|
||||
|
||||
|
@ -1217,7 +1217,7 @@ Creates a `mailto`-link where some fields are already set and correctly escaped.
|
|||
| body | _undefined_ | The text in the email |
|
||||
| button_text | _undefined_ | The text shown on the button in the UI |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L106](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L106)
|
||||
|
||||
#### Example usage of send_email
|
||||
|
||||
|
@ -1231,7 +1231,7 @@ Shows the label of the corresponding wikidata-item
|
|||
-----|-----|----- |
|
||||
| keyToShowWikidataFor | wikidata | Use the wikidata entry from this key to show the label |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L66](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L66)
|
||||
|
||||
#### Example usage of wikidata_label
|
||||
|
||||
|
@ -1245,7 +1245,7 @@ A box showing the corresponding wikipedia article(s) - based on the **wikidata**
|
|||
-----|-----|----- |
|
||||
| keyToShowWikipediaFor | wikidata;wikipedia | Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L38](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L38)
|
||||
|
||||
#### Example usage of wikipedia
|
||||
|
||||
|
@ -1259,7 +1259,7 @@ Various elements
|
|||
|
||||
Exports the selected feature as GeoJson-file
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataExportVisualisations.ts#L42](/src/UI/Popup/DataExportVisualisations.ts#L42)
|
||||
|
||||
#### Example usage of export_as_geojson
|
||||
|
||||
|
@ -1269,7 +1269,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Exports the selected feature as GPX-file
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataExportVisualisations.ts#L10](/src/UI/Popup/DataExportVisualisations.ts#L10)
|
||||
|
||||
#### Example usage of export_as_gpx
|
||||
|
||||
|
@ -1286,7 +1286,7 @@ Create a histogram for a list of given values, read from the properties.
|
|||
| countHeader | _empty string_ | This text will be placed above the bars |
|
||||
| colors* | _undefined_ | (Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33` |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/HistogramViz.ts#L6](/src/UI/Popup/HistogramViz.ts#L6)
|
||||
|
||||
#### Example usage of histogram
|
||||
|
||||
|
@ -1305,7 +1305,7 @@ The language element allows to show and pick all known (modern) languages. The k
|
|||
| render_all | {list()} | The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single |
|
||||
| no_known_languages | _undefined_ | The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/LanguageElement/LanguageElement.ts#L9](/src/UI/Popup/LanguageElement/LanguageElement.ts#L9)
|
||||
|
||||
#### Example usage of language_chooser
|
||||
|
||||
|
@ -1333,7 +1333,7 @@ A button to apply the tagging of this object onto a list of other features. This
|
|||
| autoapply | _undefined_ | A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown |
|
||||
| overwrite | _undefined_ | If set to 'true', the tags on the other objects will always be overwritten. The default behaviour will be to only change the tags on other objects if they are either undefined or had the same value before the change |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/MultiApplyViz.ts#L7](/src/UI/Popup/MultiApplyViz.ts#L7)
|
||||
|
||||
#### Example usage of multi_apply
|
||||
|
||||
|
@ -1343,7 +1343,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L221](/src/UI/Popup/DataVisualisations.ts#L221)
|
||||
|
||||
#### Example usage of preset_description
|
||||
|
||||
|
@ -1353,7 +1353,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
An editable tag rendering which allows to change the type
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L238](/src/UI/Popup/DataVisualisations.ts#L238)
|
||||
|
||||
#### Example usage of preset_type_select
|
||||
|
||||
|
@ -1367,7 +1367,7 @@ Shows a (json of) tags in a human-readable way + links to the wiki
|
|||
-----|-----|----- |
|
||||
| key | value | The key to look for the tags |
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/DataVisualisations.ts#L291](/src/UI/Popup/DataVisualisations.ts#L291)
|
||||
|
||||
#### Example usage of tags
|
||||
|
||||
|
@ -1377,7 +1377,7 @@ Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplet
|
|||
|
||||
Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored.
|
||||
|
||||
Defined in [/src/UI/SpecialVisualization.ts#L98](/home/pietervdvn/git/MapComplete/src/UI/SpecialVisualization.ts)
|
||||
Defined in [/src/UI/Popup/UploadToOsmViz.ts#L11](/src/UI/Popup/UploadToOsmViz.ts#L11)
|
||||
|
||||
#### Example usage of upload_to_osm
|
||||
|
||||
|
|
168
scripts/SourceOverview.ts
Normal file
168
scripts/SourceOverview.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
import { RasterLayerProperties } from "../src/Models/RasterLayerProperties"
|
||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers"
|
||||
import { Utils } from "../src/Utils"
|
||||
import * as eli from "../public/assets/data/editor-layer-index.json"
|
||||
import * as layers_global from "../src/assets/global-raster-layers.json"
|
||||
import eli_global from "../src/assets/generated/editor-layer-index-global.json"
|
||||
import bing from "../src/assets/bing.json"
|
||||
import Constants from "../src/Models/Constants"
|
||||
import ThemeConfig from "../src/Models/ThemeConfig/ThemeConfig"
|
||||
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
|
||||
import SpecialVisualizations from "../src/UI/SpecialVisualizations"
|
||||
import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils"
|
||||
import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { ImmutableStore } from "../src/Logic/UIEventSource"
|
||||
|
||||
export interface SourceInfo {
|
||||
host: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates what URLs the webapp might access, and why
|
||||
*/
|
||||
export class SourceOverview {
|
||||
private eliUrlsCached: string[]
|
||||
|
||||
public async getOverview( layout: ThemeConfig,
|
||||
layoutJson: ThemeConfigJson,
|
||||
options: {
|
||||
scriptSrcs: string[]
|
||||
}): Promise<string[]> {
|
||||
const apiUrls: string[] = [
|
||||
...Constants.allServers,
|
||||
"https://www.openstreetmap.org",
|
||||
"https://api.openstreetmap.org",
|
||||
"https://pietervdvn.goatcounter.com",
|
||||
"https://api.panoramax.xyz",
|
||||
"https://panoramax.mapcomplete.org",
|
||||
"https://data.velopark.be",
|
||||
"https://data.mapcomplete.org",
|
||||
].concat(...(await this.eliUrls()))
|
||||
|
||||
for (const sv of SpecialVisualizations.specialVisualizations) {
|
||||
if (typeof sv.needsUrls === "function") {
|
||||
// Handled below
|
||||
continue
|
||||
}
|
||||
apiUrls.push(...(sv.needsUrls ?? []))
|
||||
}
|
||||
|
||||
|
||||
const usedSpecialVisualisations = [].concat(
|
||||
...layoutJson.layers.map((l) =>
|
||||
ValidationUtils.getAllSpecialVisualisations(
|
||||
<QuestionableTagRenderingConfigJson[]>(<LayerConfigJson>l).tagRenderings ?? []
|
||||
)
|
||||
)
|
||||
)
|
||||
for (const usedSpecialVisualisation of usedSpecialVisualisations) {
|
||||
if (typeof usedSpecialVisualisation === "string") {
|
||||
continue
|
||||
}
|
||||
const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
|
||||
if (typeof neededUrls === "function") {
|
||||
let needed: string | string[] = neededUrls(usedSpecialVisualisation.args)
|
||||
if (typeof needed === "string") {
|
||||
needed = [needed]
|
||||
}
|
||||
apiUrls.push(...needed)
|
||||
}
|
||||
}
|
||||
|
||||
apiUrls.push("https://schema.org")
|
||||
const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt(
|
||||
new ImmutableStore({ lon: 0, lat: 0 })
|
||||
).store.data
|
||||
{
|
||||
const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector")
|
||||
const vectorSources = vectorLayers.map((l) => l.properties.url)
|
||||
vectorSources.push(...vectorLayers.map((l) => l.properties.style))
|
||||
apiUrls.push(
|
||||
...vectorSources.map((url) => {
|
||||
if (url?.startsWith("pmtiles://")) {
|
||||
return url.substring("pmtiles://".length)
|
||||
}
|
||||
return url
|
||||
})
|
||||
)
|
||||
}
|
||||
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
||||
|
||||
return apiUrls.concat(...geojsonSources)
|
||||
|
||||
}
|
||||
|
||||
private async eliUrls(): Promise<string[]> {
|
||||
if (this.eliUrlsCached) {
|
||||
return this.eliUrlsCached
|
||||
}
|
||||
const urls: string[] = []
|
||||
const regex = /{switch:([^}]+)}/
|
||||
const rasterLayers: { properties: RasterLayerProperties }[] = [
|
||||
AvailableRasterLayers.defaultBackgroundLayer,
|
||||
...eli.features,
|
||||
bing,
|
||||
...eli_global.map((properties) => ({ properties })),
|
||||
...layers_global.layers.map((properties) => ({ properties })),
|
||||
]
|
||||
for (const feature of rasterLayers) {
|
||||
const f = <RasterLayerPolygon>feature
|
||||
const url = f.properties.url
|
||||
const match = url.match(regex)
|
||||
if (match) {
|
||||
const domains = match[1].split(",")
|
||||
const subpart = match[0]
|
||||
urls.push(...domains.map((d) => url.replace(subpart, d)))
|
||||
} else {
|
||||
urls.push(url)
|
||||
}
|
||||
|
||||
if (f.properties.type === "vector") {
|
||||
// We also need to whitelist eventual sources
|
||||
let url = f.properties.url
|
||||
urls.push(...(f.properties["connect-src"] ?? []))
|
||||
if (url.startsWith("pmtiles://")) {
|
||||
url = url.substring("pmtiles://".length)
|
||||
}
|
||||
if (url.endsWith(".pmtiles")) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
console.log("Downloading ", url)
|
||||
const styleSpec = await Utils.downloadJsonCached(url, 1000 * 120, {
|
||||
Origin: "https://mapcomplete.org",
|
||||
})
|
||||
for (const key of Object.keys(styleSpec?.["sources"] ?? {})) {
|
||||
const url = styleSpec["sources"][key].url
|
||||
if (!url) {
|
||||
continue
|
||||
}
|
||||
let urlClipped = url
|
||||
if (url.indexOf("?") > 0) {
|
||||
urlClipped = url?.substring(0, url.indexOf("?"))
|
||||
}
|
||||
console.log("Source url ", key, url)
|
||||
urls.push(url)
|
||||
if (urlClipped.endsWith(".json")) {
|
||||
const tileInfo = await Utils.downloadJsonCached(url, 1000 * 120, {
|
||||
Origin: "https://mapcomplete.org",
|
||||
})
|
||||
urls.push(tileInfo["tiles"] ?? [])
|
||||
}
|
||||
}
|
||||
urls.push(...(styleSpec["tiles"] ?? []))
|
||||
urls.push(styleSpec["sprite"])
|
||||
urls.push(styleSpec["glyphs"])
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"ERROR: could not download a resource, some sprites might not be whitelisted and thus not load"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.eliUrlsCached = urls
|
||||
return Utils.NoNull(urls).sort()
|
||||
}
|
||||
|
||||
}
|
|
@ -7,21 +7,10 @@ import ThemeConfig from "../src/Models/ThemeConfig/ThemeConfig"
|
|||
import xml2js from "xml2js"
|
||||
import ScriptUtils from "./ScriptUtils"
|
||||
import { Utils } from "../src/Utils"
|
||||
import SpecialVisualizations from "../src/UI/SpecialVisualizations"
|
||||
import Constants from "../src/Models/Constants"
|
||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers"
|
||||
import { ImmutableStore } from "../src/Logic/UIEventSource"
|
||||
import * as eli from "../public/assets/data/editor-layer-index.json"
|
||||
import * as layers_global from "../src/assets/global-raster-layers.json"
|
||||
import eli_global from "../src/assets/generated/editor-layer-index-global.json"
|
||||
import bing from "../src/assets/bing.json"
|
||||
|
||||
import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils"
|
||||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import Script from "./Script"
|
||||
import crypto from "crypto"
|
||||
import { RasterLayerProperties } from "../src/Models/RasterLayerProperties"
|
||||
import { SourceOverview } from "./SourceOverview"
|
||||
|
||||
const sharp = require("sharp")
|
||||
|
||||
|
@ -36,8 +25,6 @@ class GenerateLayouts extends Script {
|
|||
.join("\n")
|
||||
private readonly removeOtherLanguagesHash =
|
||||
"sha256-" + crypto.createHash("sha256").update(this.removeOtherLanguages).digest("base64")
|
||||
private previousSrc: Set<string> = new Set<string>()
|
||||
private eliUrlsCached: string[]
|
||||
private date = new Date().toISOString()
|
||||
private branchName: string = undefined
|
||||
private alreadyWritten: Set<string> = new Set<string>()
|
||||
|
@ -265,78 +252,7 @@ class GenerateLayouts extends Script {
|
|||
return values.join("\n")
|
||||
}
|
||||
|
||||
async eliUrls(): Promise<string[]> {
|
||||
if (this.eliUrlsCached) {
|
||||
return this.eliUrlsCached
|
||||
}
|
||||
const urls: string[] = []
|
||||
const regex = /{switch:([^}]+)}/
|
||||
const rasterLayers: { properties: RasterLayerProperties }[] = [
|
||||
AvailableRasterLayers.defaultBackgroundLayer,
|
||||
...eli.features,
|
||||
bing,
|
||||
...eli_global.map((properties) => ({ properties })),
|
||||
...layers_global.layers.map((properties) => ({ properties })),
|
||||
]
|
||||
for (const feature of rasterLayers) {
|
||||
const f = <RasterLayerPolygon>feature
|
||||
const url = f.properties.url
|
||||
const match = url.match(regex)
|
||||
if (match) {
|
||||
const domains = match[1].split(",")
|
||||
const subpart = match[0]
|
||||
urls.push(...domains.map((d) => url.replace(subpart, d)))
|
||||
} else {
|
||||
urls.push(url)
|
||||
}
|
||||
|
||||
if (f.properties.type === "vector") {
|
||||
// We also need to whitelist eventual sources
|
||||
let url = f.properties.url
|
||||
urls.push(...(f.properties["connect-src"] ?? []))
|
||||
if (url.startsWith("pmtiles://")) {
|
||||
url = url.substring("pmtiles://".length)
|
||||
}
|
||||
if (url.endsWith(".pmtiles")) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
console.log("Downloading ", url)
|
||||
const styleSpec = await Utils.downloadJsonCached(url, 1000 * 120, {
|
||||
Origin: "https://mapcomplete.org",
|
||||
})
|
||||
for (const key of Object.keys(styleSpec?.["sources"] ?? {})) {
|
||||
const url = styleSpec["sources"][key].url
|
||||
if (!url) {
|
||||
continue
|
||||
}
|
||||
let urlClipped = url
|
||||
if (url.indexOf("?") > 0) {
|
||||
urlClipped = url?.substring(0, url.indexOf("?"))
|
||||
}
|
||||
console.log("Source url ", key, url)
|
||||
urls.push(url)
|
||||
if (urlClipped.endsWith(".json")) {
|
||||
const tileInfo = await Utils.downloadJsonCached(url, 1000 * 120, {
|
||||
Origin: "https://mapcomplete.org",
|
||||
})
|
||||
urls.push(tileInfo["tiles"] ?? [])
|
||||
}
|
||||
}
|
||||
urls.push(...(styleSpec["tiles"] ?? []))
|
||||
urls.push(styleSpec["sprite"])
|
||||
urls.push(styleSpec["glyphs"])
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"ERROR: could not download a resource, some sprites might not be whitelisted and thus not load"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.eliUrlsCached = urls
|
||||
return Utils.NoNull(urls).sort()
|
||||
}
|
||||
|
||||
private readonly sourceOverview = new SourceOverview()
|
||||
async generateCsp(
|
||||
layout: ThemeConfig,
|
||||
layoutJson: ThemeConfigJson,
|
||||
|
@ -344,66 +260,10 @@ class GenerateLayouts extends Script {
|
|||
scriptSrcs: string[]
|
||||
}
|
||||
): Promise<string> {
|
||||
const apiUrls: string[] = [
|
||||
...Constants.allServers,
|
||||
"https://www.openstreetmap.org",
|
||||
"https://api.openstreetmap.org",
|
||||
"https://pietervdvn.goatcounter.com",
|
||||
"https://api.panoramax.xyz",
|
||||
"https://panoramax.mapcomplete.org",
|
||||
"https://data.velopark.be",
|
||||
"https://data.mapcomplete.org",
|
||||
].concat(...(await this.eliUrls()))
|
||||
|
||||
SpecialVisualizations.specialVisualizations.forEach((sv) => {
|
||||
if (typeof sv.needsUrls === "function") {
|
||||
// Handled below
|
||||
return
|
||||
}
|
||||
apiUrls.push(...(sv.needsUrls ?? []))
|
||||
})
|
||||
|
||||
const usedSpecialVisualisations = [].concat(
|
||||
...layoutJson.layers.map((l) =>
|
||||
ValidationUtils.getAllSpecialVisualisations(
|
||||
<QuestionableTagRenderingConfigJson[]>(<LayerConfigJson>l).tagRenderings ?? []
|
||||
)
|
||||
)
|
||||
)
|
||||
for (const usedSpecialVisualisation of usedSpecialVisualisations) {
|
||||
if (typeof usedSpecialVisualisation === "string") {
|
||||
continue
|
||||
}
|
||||
const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
|
||||
if (typeof neededUrls === "function") {
|
||||
let needed: string | string[] = neededUrls(usedSpecialVisualisation.args)
|
||||
if (typeof needed === "string") {
|
||||
needed = [needed]
|
||||
}
|
||||
apiUrls.push(...needed)
|
||||
}
|
||||
}
|
||||
|
||||
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
||||
const urls = await this.sourceOverview.getOverview(layout, layoutJson, options)
|
||||
const hosts = new Set<string>()
|
||||
hosts.add("https://schema.org")
|
||||
const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt(
|
||||
new ImmutableStore({ lon: 0, lat: 0 })
|
||||
).store.data
|
||||
{
|
||||
const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector")
|
||||
const vectorSources = vectorLayers.map((l) => l.properties.url)
|
||||
vectorSources.push(...vectorLayers.map((l) => l.properties.style))
|
||||
apiUrls.push(
|
||||
...vectorSources.map((url) => {
|
||||
if (url?.startsWith("pmtiles://")) {
|
||||
return url.substring("pmtiles://".length)
|
||||
}
|
||||
return url
|
||||
})
|
||||
)
|
||||
}
|
||||
for (let connectSource of apiUrls.concat(geojsonSources)) {
|
||||
|
||||
for (let connectSource of urls) {
|
||||
if (!connectSource) {
|
||||
continue
|
||||
}
|
||||
|
@ -425,17 +285,12 @@ class GenerateLayouts extends Script {
|
|||
}
|
||||
|
||||
const connectSrc = Array.from(hosts).sort()
|
||||
|
||||
const newSrcs = connectSrc.filter((newItem) => !this.previousSrc.has(newItem))
|
||||
|
||||
console.log(
|
||||
"Got",
|
||||
hosts.size,
|
||||
"connect-src items for theme",
|
||||
layout.id,
|
||||
newSrcs.length > 0 ? "(extra sources: " + newSrcs.join(" ") + ")" : ""
|
||||
)
|
||||
this.previousSrc = hosts
|
||||
|
||||
const csp: Record<string, string> = {
|
||||
"default-src": "'self'",
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import {
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import { Feature } from "geojson"
|
||||
import AutoApplyButton from "./AutoApplyButton.svelte"
|
||||
|
||||
export interface AutoAction extends SpecialVisualization {
|
||||
export abstract class AutoAction extends SpecialVisualization {
|
||||
supportsAutoAction: boolean
|
||||
|
||||
applyActionOn(
|
||||
abstract applyActionOn(
|
||||
feature: Feature,
|
||||
state: {
|
||||
theme: ThemeConfig
|
||||
|
@ -26,8 +22,8 @@ export interface AutoAction extends SpecialVisualization {
|
|||
): Promise<void>
|
||||
}
|
||||
|
||||
export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
|
||||
public readonly docs: string
|
||||
export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
||||
public readonly docs: string = ""
|
||||
public readonly funcName: string = "auto_apply"
|
||||
public readonly needsUrls = []
|
||||
public readonly group = "data_import"
|
||||
|
@ -65,6 +61,7 @@ export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
|
|||
]
|
||||
|
||||
constructor(allSpecialVisualisations: SpecialVisualization[]) {
|
||||
super()
|
||||
this.docs = AutoApplyButtonVis.generateDocs(
|
||||
allSpecialVisualisations
|
||||
.filter((sv) => sv["supportsAutoAction"] === true)
|
||||
|
@ -124,7 +121,5 @@ export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
|
|||
return new SvelteUIElement(AutoApplyButton, { state, ids: stableIds, options })
|
||||
}
|
||||
|
||||
getLayerDependencies(args: string[]): string[] {
|
||||
return [args[0]]
|
||||
}
|
||||
getLayerDependencies = (args: string[]): string[] => [args[0]]
|
||||
}
|
||||
|
|
67
src/UI/Popup/DataExportVisualisations.ts
Normal file
67
src/UI/Popup/DataExportVisualisations.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ExportFeatureButton from "./ExportFeatureButton.svelte"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
|
||||
class ExportAsGpxVis extends SpecialVisualizationSvelte {
|
||||
funcName = "export_as_gpx"
|
||||
docs = "Exports the selected feature as GPX-file"
|
||||
args = []
|
||||
needsUrls = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
) {
|
||||
if (feature.geometry.type !== "LineString") {
|
||||
return undefined
|
||||
}
|
||||
const t = Translations.t.general.download
|
||||
|
||||
return new SvelteUIElement(ExportFeatureButton, {
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
mimetype: "{gpx=application/gpx+xml}",
|
||||
extension: "gpx",
|
||||
construct: (feature: Feature<LineString>, title: string) =>
|
||||
GeoOperations.toGpx(feature, title),
|
||||
helpertext: t.downloadGpxHelper,
|
||||
maintext: t.downloadFeatureAsGpx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
|
||||
funcName = "export_as_geojson"
|
||||
docs = "Exports the selected feature as GeoJson-file"
|
||||
args = []
|
||||
|
||||
constr(state, tags, args, feature, layer) {
|
||||
const t = Translations.t.general.download
|
||||
return new SvelteUIElement(ExportFeatureButton, {
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
mimetype: "application/vnd.geo+json",
|
||||
extension: "geojson",
|
||||
construct: (feature: Feature<LineString>) =>
|
||||
JSON.stringify(feature, null, " "),
|
||||
maintext: t.downloadFeatureAsGeojson,
|
||||
helpertext: t.downloadGeoJsonHelper,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class DataExportVisualisations {
|
||||
public static initList(): SpecialVisualization[] {
|
||||
return [new ExportAsGpxVis(), new ExportAsGeojsonVis()]
|
||||
}
|
||||
}
|
352
src/UI/Popup/DataVisualisations.ts
Normal file
352
src/UI/Popup/DataVisualisations.ts
Normal file
|
@ -0,0 +1,352 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { HistogramViz } from "./HistogramViz"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import DirectionIndicator from "../Base/DirectionIndicator.svelte"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Constants from "../../Models/Constants"
|
||||
import opening_hours from "opening_hours"
|
||||
import { OH } from "../OpeningHours/OpeningHours"
|
||||
import OpeningHoursWithError from "../OpeningHours/Visualisation/OpeningHoursWithError.svelte"
|
||||
import NextChangeViz from "../OpeningHours/NextChangeViz.svelte"
|
||||
import { Unit } from "../../Models/Unit"
|
||||
import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte"
|
||||
import { LanguageElement } from "./LanguageElement/LanguageElement"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
|
||||
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
|
||||
class DirectionIndicatorVis extends SpecialVisualization {
|
||||
funcName = "direction_indicator"
|
||||
args = []
|
||||
|
||||
docs = "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object"
|
||||
group = "data"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||
}
|
||||
}
|
||||
|
||||
class DirectionAbsolute extends SpecialVisualization {
|
||||
funcName = "direction_absolute"
|
||||
docs = "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The attribute containing the degrees",
|
||||
defaultValue: "_direction:centerpoint",
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
doc: "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction",
|
||||
defaultValue: "0",
|
||||
},
|
||||
]
|
||||
group = "data"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
): BaseUIElement {
|
||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||
const offset = args[1] === "" ? 0 : Number(args[1])
|
||||
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => {
|
||||
console.log("Direction value", tags[key], key)
|
||||
return tags[key]
|
||||
})
|
||||
.mapD((value) => {
|
||||
const dir = GeoOperations.bearingToHuman(
|
||||
GeoOperations.parseBearing(value) + offset,
|
||||
)
|
||||
console.log("Human dir", dir)
|
||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OpeningHoursTableVis extends SpecialVisualizationSvelte {
|
||||
funcName = "opening_hours_table"
|
||||
docs = "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'."
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
defaultValue: "opening_hours",
|
||||
type: "key",
|
||||
doc: "The tagkey from which the table is constructed.",
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
|
||||
},
|
||||
{
|
||||
name: "postfix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
|
||||
},
|
||||
]
|
||||
group = "data"
|
||||
needsUrls = [Constants.countryCoderEndpoint]
|
||||
example =
|
||||
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`"
|
||||
|
||||
constr(state, tagSource: UIEventSource<any>, args) {
|
||||
const [key, prefix, postfix] = args
|
||||
const openingHoursStore: Store<opening_hours | "error" | undefined> =
|
||||
OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
|
||||
return new SvelteUIElement(OpeningHoursWithError, {
|
||||
tags: tagSource,
|
||||
key,
|
||||
opening_hours_obj: openingHoursStore,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class OpeningHoursState extends SpecialVisualizationSvelte {
|
||||
group = "data"
|
||||
funcName = "opening_hours_state"
|
||||
docs = "A small element, showing if the POI is currently open and when the next change is"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
defaultValue: "opening_hours",
|
||||
doc: "The tagkey from which the opening hours are read.",
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
|
||||
},
|
||||
{
|
||||
name: "postfix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
): SvelteUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
const postfix = args[2]
|
||||
return new SvelteUIElement(NextChangeViz, {
|
||||
state,
|
||||
keyToUse,
|
||||
tags,
|
||||
prefix,
|
||||
postfix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Canonical extends SpecialVisualization {
|
||||
group = "data"
|
||||
funcName = "canonical"
|
||||
|
||||
docs = "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. "
|
||||
example =
|
||||
"If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ..."
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The key of the tag to give the canonical text for",
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tagSource, args) {
|
||||
const key = args[0]
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => tags[key])
|
||||
.map((value) => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const allUnits: Unit[] = [].concat(
|
||||
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []),
|
||||
)
|
||||
const unit = allUnits.filter((unit) =>
|
||||
unit.isApplicableToKey(key),
|
||||
)[0]
|
||||
if (unit === undefined) {
|
||||
return value
|
||||
}
|
||||
const getCountry = () => tagSource.data._country
|
||||
return unit.asHumanLongValue(value, getCountry)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StatisticsVis extends SpecialVisualizationSvelte {
|
||||
funcName = "statistics"
|
||||
group = "data"
|
||||
docs = "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer"
|
||||
args = []
|
||||
|
||||
constr(state) {
|
||||
|
||||
return new SvelteUIElement(AllFeaturesStatistics, { state })
|
||||
}
|
||||
}
|
||||
|
||||
class PresetDescription extends SpecialVisualization {
|
||||
funcName = "preset_description"
|
||||
docs = "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty"
|
||||
args = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
): BaseUIElement {
|
||||
const translation = tagSource.map((tags) => {
|
||||
const layer = state.theme.getMatchingLayer(tags)
|
||||
return layer?.getMostMatchingPreset(tags)?.description
|
||||
})
|
||||
return new VariableUiElement(translation)
|
||||
}
|
||||
}
|
||||
|
||||
class PresetTypeSelect extends SpecialVisualizationSvelte {
|
||||
funcName = "preset_type_select"
|
||||
docs = "An editable tag rendering which allows to change the type"
|
||||
args = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const t = Translations.t.preset_type
|
||||
const question: QuestionableTagRenderingConfigJson = {
|
||||
id: layer.id + "-type",
|
||||
question: t.question.translations,
|
||||
mappings: layer.presets.map((pr) => ({
|
||||
if: new And(pr.tags).asJson(),
|
||||
icon: "auto",
|
||||
then: (pr.description ? t.typeDescription : t.typeTitle).Subs({
|
||||
title: pr.title,
|
||||
description: pr.description,
|
||||
}).translations,
|
||||
})),
|
||||
}
|
||||
const config = new TagRenderingConfig(question)
|
||||
return new SvelteUIElement(TagRenderingEditable, {
|
||||
config,
|
||||
tags,
|
||||
selectedElement,
|
||||
state,
|
||||
layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class AllTagsVis extends SpecialVisualizationSvelte {
|
||||
funcName = "all_tags"
|
||||
docs = "Prints all key-value pairs of the object - used for debugging"
|
||||
args = []
|
||||
group = "data"
|
||||
|
||||
constr(
|
||||
state,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
_,
|
||||
__,
|
||||
layer: LayerConfig,
|
||||
) {
|
||||
return new SvelteUIElement(AllTagsPanel, { tags, layer })
|
||||
}
|
||||
}
|
||||
|
||||
class TagsVis extends SpecialVisualization {
|
||||
funcName = "tags"
|
||||
docs = "Shows a (json of) tags in a human-readable way + links to the wiki"
|
||||
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
defaultValue: "value",
|
||||
doc: "The key to look for the tags",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "value"
|
||||
return new VariableUiElement(
|
||||
tagSource.map((tags) => {
|
||||
let value = tags[key]
|
||||
if (!value) {
|
||||
return new FixedUiElement("No tags found").SetClass("font-bold")
|
||||
}
|
||||
if (typeof value === "string" && value.startsWith("{")) {
|
||||
value = JSON.parse(value)
|
||||
}
|
||||
try {
|
||||
const parsed = TagUtils.Tag(value)
|
||||
return parsed.asHumanString(true, false, {})
|
||||
} catch (e) {
|
||||
return new FixedUiElement(
|
||||
"Could not parse this tag: " +
|
||||
JSON.stringify(value) +
|
||||
" due to " +
|
||||
e,
|
||||
).SetClass("alert")
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class DataVisualisations {
|
||||
public static initList(): SpecialVisualization[] {
|
||||
return [
|
||||
new HistogramViz(),
|
||||
new StatisticsVis(),
|
||||
new DirectionAbsolute(),
|
||||
new DirectionIndicatorVis(),
|
||||
new OpeningHoursTableVis(),
|
||||
new OpeningHoursState(),
|
||||
new Canonical(),
|
||||
new LanguageElement(),
|
||||
new PresetDescription(),
|
||||
new PresetTypeSelect(),
|
||||
new AllTagsVis(),
|
||||
new TagsVis(),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
|
|||
import Histogram from "../BigComponents/Histogram"
|
||||
import { Feature } from "geojson"
|
||||
|
||||
export class HistogramViz implements SpecialVisualization {
|
||||
export class HistogramViz extends SpecialVisualization {
|
||||
funcName = "histogram"
|
||||
docs = "Create a histogram for a list of given values, read from the properties."
|
||||
needsUrls = []
|
||||
|
|
|
@ -13,6 +13,7 @@ import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource
|
|||
import { Changes } from "../../../Logic/Osm/Changes"
|
||||
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
|
||||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
export interface ConflateFlowArguments extends ImportFlowArguments {
|
||||
way_to_conflate: string
|
||||
|
@ -21,7 +22,7 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
|
|||
snap_onto_layers?: string
|
||||
}
|
||||
|
||||
export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction {
|
||||
export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction {
|
||||
supportsAutoAction: boolean = true
|
||||
needsUrls = []
|
||||
group = "data_import"
|
||||
|
@ -85,7 +86,7 @@ export default class ConflateImportButtonViz implements SpecialVisualization, Au
|
|||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
|
@ -111,7 +112,5 @@ export default class ConflateImportButtonViz implements SpecialVisualization, Au
|
|||
})
|
||||
}
|
||||
|
||||
getLayerDependencies(args: string[]) {
|
||||
return ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
|
||||
}
|
||||
getLayerDependencies = (args: string[]) => ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
|
||||
}
|
||||
|
|
|
@ -9,53 +9,46 @@ import { Utils } from "../../../Utils"
|
|||
import { ImportFlowUtils } from "./ImportFlow"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
/**
|
||||
* The wrapper to make the special visualisation for the PointImportFlow
|
||||
*/
|
||||
export class PointImportButtonViz implements SpecialVisualization {
|
||||
public readonly funcName: string
|
||||
public readonly docs: string | BaseUIElement
|
||||
public readonly example?: string
|
||||
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[]
|
||||
export class PointImportButtonViz extends SpecialVisualization {
|
||||
public readonly funcName = "import_button"
|
||||
public readonly docs: string = "This button will copy the point from an external dataset into OpenStreetMap" +
|
||||
ImportFlowUtils.documentationGeneral
|
||||
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[] = [
|
||||
...ImportFlowUtils.generalArguments,
|
||||
{
|
||||
name: "snap_onto_layers",
|
||||
doc: "If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
},
|
||||
{
|
||||
name: "max_snap_distance",
|
||||
doc: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
|
||||
defaultValue: "5",
|
||||
},
|
||||
{
|
||||
name: "note_id",
|
||||
doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'",
|
||||
},
|
||||
{
|
||||
name: "maproulette_id",
|
||||
doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.",
|
||||
},
|
||||
{
|
||||
name: "to_point",
|
||||
doc: "If set, a feature will be converted to a centerpoint",
|
||||
},
|
||||
]
|
||||
public needsUrls = []
|
||||
group = "data_import"
|
||||
|
||||
constructor() {
|
||||
this.funcName = "import_button"
|
||||
|
||||
this.docs =
|
||||
"This button will copy the point from an external dataset into OpenStreetMap" +
|
||||
ImportFlowUtils.documentationGeneral
|
||||
this.args = [
|
||||
...ImportFlowUtils.generalArguments,
|
||||
{
|
||||
name: "snap_onto_layers",
|
||||
doc: "If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
},
|
||||
{
|
||||
name: "max_snap_distance",
|
||||
doc: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
|
||||
defaultValue: "5",
|
||||
},
|
||||
{
|
||||
name: "note_id",
|
||||
doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'",
|
||||
},
|
||||
{
|
||||
name: "maproulette_id",
|
||||
doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.",
|
||||
},
|
||||
{
|
||||
name: "to_point",
|
||||
doc: "If set, a feature will be converted to a centerpoint",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Feature, Point } from "geojson"
|
|||
import Maproulette from "../../../Logic/Maproulette"
|
||||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import ThemeViewState from "../../../Models/ThemeViewState"
|
||||
import { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
|
||||
export interface PointImportFlowArguments extends ImportFlowArguments {
|
||||
max_snap_distance?: string
|
||||
|
@ -21,7 +21,7 @@ export class PointImportFlowState extends ImportFlow<PointImportFlowArguments> {
|
|||
public readonly startCoordinate: [number, number]
|
||||
|
||||
constructor(
|
||||
state: ThemeViewState,
|
||||
state: SpecialVisualizationState,
|
||||
originalFeature: Feature<Point>,
|
||||
args: PointImportFlowArguments,
|
||||
tagsToApply: Store<Tag[]>,
|
||||
|
|
|
@ -14,11 +14,12 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
|
|||
import { Changes } from "../../../Logic/Osm/Changes"
|
||||
import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource"
|
||||
import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
/**
|
||||
* Wrapper around 'WayImportFlow' to make it a special visualisation
|
||||
*/
|
||||
export default class WayImportButtonViz implements AutoAction, SpecialVisualization {
|
||||
export default class WayImportButtonViz extends SpecialVisualization implements AutoAction {
|
||||
public readonly funcName: string = "import_way_button"
|
||||
needsUrls = []
|
||||
group = "data_import"
|
||||
|
@ -62,7 +63,7 @@ export default class WayImportButtonViz implements AutoAction, SpecialVisualizat
|
|||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
_: LayerConfig
|
||||
|
@ -125,7 +126,5 @@ export default class WayImportButtonViz implements AutoAction, SpecialVisualizat
|
|||
await state.changes.applyAction(action)
|
||||
}
|
||||
|
||||
getLayerDependencies(args: string[]) {
|
||||
return ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
|
||||
}
|
||||
getLayerDependencies = (args: string[]) => ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Feature } from "geojson"
|
|||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
|
||||
|
||||
export class LanguageElement implements SpecialVisualization {
|
||||
export class LanguageElement extends SpecialVisualization {
|
||||
funcName: string = "language_chooser"
|
||||
needsUrls = []
|
||||
|
||||
|
@ -45,7 +45,7 @@ export class LanguageElement implements SpecialVisualization {
|
|||
},
|
||||
]
|
||||
|
||||
example: `
|
||||
example= `
|
||||
\`\`\`json
|
||||
{"special":
|
||||
"type": "language_chooser",
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Feature } from "geojson"
|
|||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
|
||||
|
||||
export class MapillaryLinkVis implements SpecialVisualizationSvelte {
|
||||
export class MapillaryLinkVis extends SpecialVisualizationSvelte {
|
||||
funcName = "mapillary_link"
|
||||
group = "web_and_communication"
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
|
|||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MultiApplyButton from "./MultiApplyButton.svelte"
|
||||
|
||||
export class MultiApplyViz implements SpecialVisualization {
|
||||
export class MultiApplyViz extends SpecialVisualization {
|
||||
funcName = "multi_apply"
|
||||
needsUrls = []
|
||||
docs =
|
||||
|
|
|
@ -10,7 +10,7 @@ import SvelteUIElement from "../Base/SvelteUIElement"
|
|||
import PlantNet from "../PlantNet/PlantNet.svelte"
|
||||
import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
|
||||
|
||||
export class PlantNetDetectionViz implements SpecialVisualization {
|
||||
export class PlantNetDetectionViz extends SpecialVisualization {
|
||||
funcName = "plantnet_detection"
|
||||
needsUrls = [PlantNetCode.baseUrl]
|
||||
group = "data_import"
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../Specia
|
|||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ShareButton from "../Base/ShareButton.svelte"
|
||||
|
||||
export class ShareLinkViz implements SpecialVisualizationSvelte {
|
||||
export class ShareLinkViz extends SpecialVisualizationSvelte {
|
||||
funcName = "share_link"
|
||||
group = "default"
|
||||
docs = "Creates a link that (attempts to) open the native 'share'-screen"
|
||||
|
|
|
@ -8,7 +8,7 @@ import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI.svelte"
|
|||
/**
|
||||
* Wrapper around 'UploadTraceToOsmUI'
|
||||
*/
|
||||
export class UploadToOsmViz implements SpecialVisualization {
|
||||
export class UploadToOsmViz extends SpecialVisualization {
|
||||
funcName = "upload_to_osm"
|
||||
docs =
|
||||
"Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import Maproulette from "../../Logic/Maproulette"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MaprouletteSetStatus from "../MapRoulette/MaprouletteSetStatus.svelte"
|
||||
import { PointImportButtonViz } from "../Popup/ImportButtons/PointImportButtonViz"
|
||||
import WayImportButtonViz from "../Popup/ImportButtons/WayImportButtonViz"
|
||||
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
|
||||
|
@ -17,6 +16,271 @@ import ComparisonTool from "../Comparison/ComparisonTool.svelte"
|
|||
import { Utils } from "../../Utils"
|
||||
import TagApplyViz from "./TagApplyViz"
|
||||
|
||||
class MaprouletteSetStatus extends SpecialVisualizationSvelte {
|
||||
funcName = "maproulette_set_status"
|
||||
group = "data_import"
|
||||
docs = "Change the status of the given MapRoulette task"
|
||||
needsUrls = [Maproulette.defaultEndpoint]
|
||||
example =
|
||||
" The following example sets the status to '2' (false positive)\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"id\": \"mark_duplicate\",\n" +
|
||||
" \"render\": {\n" +
|
||||
" \"special\": {\n" +
|
||||
" \"type\": \"maproulette_set_status\",\n" +
|
||||
" \"message\": {\n" +
|
||||
" \"en\": \"Mark as not found or false positive\"\n" +
|
||||
" },\n" +
|
||||
" \"status\": \"2\",\n" +
|
||||
" \"image\": \"close\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```"
|
||||
args = [
|
||||
{
|
||||
name: "message",
|
||||
doc: "A message to show to the user",
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
doc: "Image to show",
|
||||
defaultValue: "confirm",
|
||||
},
|
||||
{
|
||||
name: "message_confirm",
|
||||
doc: "What to show when the task is closed, either by the user or was already closed.",
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
doc: "A statuscode to apply when the button is clicked. 1 = `close`, 2 = `false_positive`, 3 = `skip`, 4 = `deleted`, 5 = `already fixed` (on the map, e.g. for duplicates), 6 = `too hard`",
|
||||
defaultValue: "1",
|
||||
},
|
||||
{
|
||||
name: "maproulette_id",
|
||||
doc: "The property name containing the maproulette id",
|
||||
defaultValue: "mr_taskId",
|
||||
},
|
||||
{
|
||||
name: "ask_feedback",
|
||||
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
|
||||
defaultValue: "",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tagsSource, args) {
|
||||
let [
|
||||
message,
|
||||
image,
|
||||
message_closed,
|
||||
statusToSet,
|
||||
maproulette_id_key,
|
||||
askFeedback,
|
||||
] = args
|
||||
if (image === "") {
|
||||
image = "confirm"
|
||||
}
|
||||
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
|
||||
maproulette_id_key = "mr_taskId"
|
||||
}
|
||||
statusToSet = statusToSet ?? "1"
|
||||
return new SvelteUIElement(MaprouletteSetStatus, {
|
||||
state,
|
||||
tags: tagsSource,
|
||||
message,
|
||||
image,
|
||||
message_closed,
|
||||
statusToSet,
|
||||
maproulette_id_key,
|
||||
askFeedback,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class LinkedDataFromWebsite extends SpecialVisualization {
|
||||
funcName = "linked_data_from_website"
|
||||
group = "data_import"
|
||||
docs = "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM. Note: this element is added by default"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
defaultValue: "website",
|
||||
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
|
||||
},
|
||||
{
|
||||
name: "useProxy",
|
||||
defaultValue: "yes",
|
||||
doc: "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly",
|
||||
},
|
||||
{
|
||||
name: "host",
|
||||
doc: "If not using a proxy, define what host the website is allowed to connect to",
|
||||
},
|
||||
{
|
||||
name: "mode",
|
||||
doc: "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM",
|
||||
},
|
||||
{
|
||||
name: "collapsed",
|
||||
defaultValue: "yes",
|
||||
doc: "If the containing accordion should be closed",
|
||||
},
|
||||
]
|
||||
needsUrls = [Constants.linkedDataProxy, "http://www.schema.org"]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "website"
|
||||
const useProxy = argument[1] !== "no"
|
||||
const readonly = argument[3] === "readonly"
|
||||
const isClosed = (argument[4] ?? "yes") === "yes"
|
||||
|
||||
const countryStore: Store<string | undefined> = tags.mapD(
|
||||
(tags) => tags._country,
|
||||
)
|
||||
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
|
||||
if (!tags[key] || tags[key] === "undefined") {
|
||||
return null
|
||||
}
|
||||
return tags[key]
|
||||
})
|
||||
const externalData: Store<{ success: GeoJsonProperties } | { error }> =
|
||||
sourceUrl.bindD(
|
||||
(url) => {
|
||||
const country = countryStore.data
|
||||
if (url.startsWith("https://data.velopark.be/")) {
|
||||
return Stores.FromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
const loadAll =
|
||||
layer.id.toLowerCase().indexOf("maproulette") >=
|
||||
0 // Dirty hack
|
||||
const features =
|
||||
await LinkedDataLoader.fetchVeloparkEntry(
|
||||
url,
|
||||
loadAll,
|
||||
)
|
||||
const feature =
|
||||
features.find(
|
||||
(f) => f.properties["ref:velopark"] === url,
|
||||
) ?? features[0]
|
||||
const properties = feature.properties
|
||||
properties["ref:velopark"] = url
|
||||
console.log(
|
||||
"Got properties from velopark:",
|
||||
properties,
|
||||
)
|
||||
return properties
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
})(),
|
||||
)
|
||||
}
|
||||
if (country === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return Stores.FromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
useProxy ? "proxy" : "fetch-lod",
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
||||
url,
|
||||
"is",
|
||||
e,
|
||||
)
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
"fetch-raw",
|
||||
)
|
||||
}
|
||||
})(),
|
||||
)
|
||||
},
|
||||
[countryStore],
|
||||
)
|
||||
|
||||
externalData.addCallbackAndRunD((lod) =>
|
||||
console.log("linked_data_from_website received the following data:", lod),
|
||||
)
|
||||
|
||||
return new Toggle(
|
||||
new SvelteUIElement(ComparisonTool, {
|
||||
feature,
|
||||
state,
|
||||
tags,
|
||||
layer,
|
||||
externalData,
|
||||
sourceUrl,
|
||||
readonly,
|
||||
collapsed: isClosed,
|
||||
}),
|
||||
undefined,
|
||||
sourceUrl.map((url) => !!url),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CompareData extends SpecialVisualization {
|
||||
funcName = "compare_data"
|
||||
group = "data_import"
|
||||
needsUrls = (args) => args[1].split(";")
|
||||
args = [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
doc: "The attribute containing the url where to fetch more data",
|
||||
},
|
||||
{
|
||||
name: "host",
|
||||
required: true,
|
||||
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
|
||||
},
|
||||
{
|
||||
name: "readonly",
|
||||
required: false,
|
||||
doc: "If 'yes', will not show 'apply'-buttons",
|
||||
},
|
||||
]
|
||||
docs = "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const url = args[0]
|
||||
const readonly = args[3] === "yes"
|
||||
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
|
||||
return new SvelteUIElement(ComparisonTool, {
|
||||
url,
|
||||
state,
|
||||
tags: tagSource,
|
||||
layer,
|
||||
feature,
|
||||
readonly,
|
||||
externalData,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
export class DataImportSpecialVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
return [
|
||||
|
@ -25,266 +289,9 @@ export class DataImportSpecialVisualisations {
|
|||
new WayImportButtonViz(),
|
||||
new ConflateImportButtonViz(),
|
||||
new PlantNetDetectionViz(),
|
||||
{
|
||||
funcName: "maproulette_set_status",
|
||||
group: "data_import",
|
||||
docs: "Change the status of the given MapRoulette task",
|
||||
needsUrls: [Maproulette.defaultEndpoint],
|
||||
example:
|
||||
" The following example sets the status to '2' (false positive)\n" +
|
||||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
' "id": "mark_duplicate",\n' +
|
||||
' "render": {\n' +
|
||||
' "special": {\n' +
|
||||
' "type": "maproulette_set_status",\n' +
|
||||
' "message": {\n' +
|
||||
' "en": "Mark as not found or false positive"\n' +
|
||||
" },\n" +
|
||||
' "status": "2",\n' +
|
||||
' "image": "close"\n' +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```",
|
||||
args: [
|
||||
{
|
||||
name: "message",
|
||||
doc: "A message to show to the user",
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
doc: "Image to show",
|
||||
defaultValue: "confirm",
|
||||
},
|
||||
{
|
||||
name: "message_confirm",
|
||||
doc: "What to show when the task is closed, either by the user or was already closed.",
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
doc: "A statuscode to apply when the button is clicked. 1 = `close`, 2 = `false_positive`, 3 = `skip`, 4 = `deleted`, 5 = `already fixed` (on the map, e.g. for duplicates), 6 = `too hard`",
|
||||
defaultValue: "1",
|
||||
},
|
||||
{
|
||||
name: "maproulette_id",
|
||||
doc: "The property name containing the maproulette id",
|
||||
defaultValue: "mr_taskId",
|
||||
},
|
||||
{
|
||||
name: "ask_feedback",
|
||||
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
|
||||
defaultValue: "",
|
||||
},
|
||||
],
|
||||
|
||||
constr: (state, tagsSource, args) => {
|
||||
let [
|
||||
message,
|
||||
image,
|
||||
message_closed,
|
||||
statusToSet,
|
||||
maproulette_id_key,
|
||||
askFeedback,
|
||||
] = args
|
||||
if (image === "") {
|
||||
image = "confirm"
|
||||
}
|
||||
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
|
||||
maproulette_id_key = "mr_taskId"
|
||||
}
|
||||
statusToSet = statusToSet ?? "1"
|
||||
return new SvelteUIElement(MaprouletteSetStatus, {
|
||||
state,
|
||||
tags: tagsSource,
|
||||
message,
|
||||
image,
|
||||
message_closed,
|
||||
statusToSet,
|
||||
maproulette_id_key,
|
||||
askFeedback,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "linked_data_from_website",
|
||||
group: "data_import",
|
||||
docs: "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM. Note: this element is added by default",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
defaultValue: "website",
|
||||
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
|
||||
},
|
||||
{
|
||||
name: "useProxy",
|
||||
defaultValue: "yes",
|
||||
doc: "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly",
|
||||
},
|
||||
{
|
||||
name: "host",
|
||||
doc: "If not using a proxy, define what host the website is allowed to connect to",
|
||||
},
|
||||
{
|
||||
name: "mode",
|
||||
doc: "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM",
|
||||
},
|
||||
{
|
||||
name: "collapsed",
|
||||
defaultValue: "yes",
|
||||
doc: "If the containing accordion should be closed",
|
||||
},
|
||||
],
|
||||
needsUrls: [Constants.linkedDataProxy, "http://www.schema.org"],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "website"
|
||||
const useProxy = argument[1] !== "no"
|
||||
const readonly = argument[3] === "readonly"
|
||||
const isClosed = (argument[4] ?? "yes") === "yes"
|
||||
|
||||
const countryStore: Store<string | undefined> = tags.mapD(
|
||||
(tags) => tags._country
|
||||
)
|
||||
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
|
||||
if (!tags[key] || tags[key] === "undefined") {
|
||||
return null
|
||||
}
|
||||
return tags[key]
|
||||
})
|
||||
const externalData: Store<{ success: GeoJsonProperties } | { error }> =
|
||||
sourceUrl.bindD(
|
||||
(url) => {
|
||||
const country = countryStore.data
|
||||
if (url.startsWith("https://data.velopark.be/")) {
|
||||
return Stores.FromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
const loadAll =
|
||||
layer.id.toLowerCase().indexOf("maproulette") >=
|
||||
0 // Dirty hack
|
||||
const features =
|
||||
await LinkedDataLoader.fetchVeloparkEntry(
|
||||
url,
|
||||
loadAll
|
||||
)
|
||||
const feature =
|
||||
features.find(
|
||||
(f) => f.properties["ref:velopark"] === url
|
||||
) ?? features[0]
|
||||
const properties = feature.properties
|
||||
properties["ref:velopark"] = url
|
||||
console.log(
|
||||
"Got properties from velopark:",
|
||||
properties
|
||||
)
|
||||
return properties
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
})()
|
||||
)
|
||||
}
|
||||
if (country === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return Stores.FromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
useProxy ? "proxy" : "fetch-lod"
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
||||
url,
|
||||
"is",
|
||||
e
|
||||
)
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
"fetch-raw"
|
||||
)
|
||||
}
|
||||
})()
|
||||
)
|
||||
},
|
||||
[countryStore]
|
||||
)
|
||||
|
||||
externalData.addCallbackAndRunD((lod) =>
|
||||
console.log("linked_data_from_website received the following data:", lod)
|
||||
)
|
||||
|
||||
return new Toggle(
|
||||
new SvelteUIElement(ComparisonTool, {
|
||||
feature,
|
||||
state,
|
||||
tags,
|
||||
layer,
|
||||
externalData,
|
||||
sourceUrl,
|
||||
readonly,
|
||||
collapsed: isClosed,
|
||||
}),
|
||||
undefined,
|
||||
sourceUrl.map((url) => !!url)
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "compare_data",
|
||||
group: "data_import",
|
||||
needsUrls: (args) => args[1].split(";"),
|
||||
args: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
doc: "The attribute containing the url where to fetch more data",
|
||||
},
|
||||
{
|
||||
name: "host",
|
||||
required: true,
|
||||
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
|
||||
},
|
||||
{
|
||||
name: "readonly",
|
||||
required: false,
|
||||
doc: "If 'yes', will not show 'apply'-buttons",
|
||||
},
|
||||
],
|
||||
docs: "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
const url = args[0]
|
||||
const readonly = args[3] === "yes"
|
||||
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
|
||||
return new SvelteUIElement(ComparisonTool, {
|
||||
url,
|
||||
state,
|
||||
tags: tagSource,
|
||||
layer,
|
||||
feature,
|
||||
readonly,
|
||||
externalData,
|
||||
})
|
||||
},
|
||||
},
|
||||
new MaprouletteSetStatus(),
|
||||
new LinkedDataFromWebsite(),
|
||||
new CompareData(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,50 +6,56 @@ import SvelteUIElement from "../Base/SvelteUIElement"
|
|||
import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte"
|
||||
import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte"
|
||||
|
||||
class FavouriteStatus extends SpecialVisualizationSvelte {
|
||||
funcName = "favourite_status"
|
||||
|
||||
docs = "A button that allows a (logged in) contributor to mark a location as a favourite location"
|
||||
args = []
|
||||
group = "favourites"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class FavouriteIcon extends SpecialVisualizationSvelte {
|
||||
funcName = "favourite_icon"
|
||||
group = "favourites"
|
||||
docs = "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon"
|
||||
args = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class FavouriteVisualisations {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
return [
|
||||
{
|
||||
funcName: "favourite_status",
|
||||
|
||||
docs: "A button that allows a (logged in) contributor to mark a location as a favourite location",
|
||||
args: [],
|
||||
group: "favourites",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "favourite_icon",
|
||||
group: "favourites",
|
||||
docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
new FavouriteStatus(),
|
||||
new FavouriteIcon(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../Specia
|
|||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ImageCarousel from "../Image/ImageCarousel.svelte"
|
||||
import { Imgur } from "../../Logic/ImageProviders/Imgur"
|
||||
import UploadImage from "../Image/UploadImage.svelte"
|
||||
import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
@ -11,8 +10,9 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
|||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import NearbyImages from "../Image/NearbyImages.svelte"
|
||||
import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualizationSvelte {
|
||||
class NearbyImageVis extends SpecialVisualizationSvelte {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
args = [
|
||||
{
|
||||
|
@ -54,79 +54,85 @@ class NearbyImageVis implements SpecialVisualizationSvelte {
|
|||
}
|
||||
}
|
||||
|
||||
class ImageCarouselVis extends SpecialVisualizationSvelte {
|
||||
funcName = "image_carousel"
|
||||
group = "images"
|
||||
docs = "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)"
|
||||
args = [
|
||||
{
|
||||
name: "image_key",
|
||||
type: "key",
|
||||
defaultValue: AllImageProviders.defaultKeys.join(";"),
|
||||
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
|
||||
},
|
||||
]
|
||||
needsUrls = AllImageProviders.apiUrls
|
||||
|
||||
constr(state, tags, args, feature) {
|
||||
let imagePrefixes: string[] = undefined
|
||||
if (args.length > 0) {
|
||||
imagePrefixes = [].concat(...args.map((a) => a.split(";")))
|
||||
}
|
||||
const images = AllImageProviders.loadImagesFor(tags, imagePrefixes)
|
||||
const estimated = tags.mapD((tags) =>
|
||||
AllImageProviders.estimateNumberOfImages(tags, imagePrefixes),
|
||||
)
|
||||
return new SvelteUIElement(ImageCarousel, {
|
||||
state,
|
||||
tags,
|
||||
images,
|
||||
estimated,
|
||||
feature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ImageUpload extends SpecialVisualizationSvelte {
|
||||
funcName = "image_upload"
|
||||
group = "images"
|
||||
docs = "Creates a button where a user can upload an image to panoramax"
|
||||
needsUrls = [Constants.panoramax.url]
|
||||
args = [
|
||||
{
|
||||
type: "key",
|
||||
name: "image_key",
|
||||
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
|
||||
defaultValue: "panoramax",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "label",
|
||||
doc: "The text to show on the button",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "disable_blur",
|
||||
doc: "If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly",
|
||||
required: false,
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tags, args, feature) {
|
||||
const targetKey = args[0] === "" ? undefined : args[0]
|
||||
const noBlur = args[3]?.toLowerCase()?.trim()
|
||||
return new SvelteUIElement(UploadImage, {
|
||||
state,
|
||||
tags,
|
||||
targetKey,
|
||||
feature,
|
||||
labelText: args[1],
|
||||
image: args[2],
|
||||
noBlur: noBlur === "true" || noBlur === "yes",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageVisualisations {
|
||||
static initList(): SpecialVisualizationSvelte[] {
|
||||
return [
|
||||
new NearbyImageVis(),
|
||||
{
|
||||
funcName: "image_carousel",
|
||||
group: "images",
|
||||
docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
|
||||
args: [
|
||||
{
|
||||
name: "image_key",
|
||||
type: "key",
|
||||
defaultValue: AllImageProviders.defaultKeys.join(";"),
|
||||
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
|
||||
},
|
||||
],
|
||||
needsUrls: AllImageProviders.apiUrls,
|
||||
constr: (state, tags, args, feature) => {
|
||||
let imagePrefixes: string[] = undefined
|
||||
if (args.length > 0) {
|
||||
imagePrefixes = [].concat(...args.map((a) => a.split(";")))
|
||||
}
|
||||
const images = AllImageProviders.loadImagesFor(tags, imagePrefixes)
|
||||
const estimated = tags.mapD((tags) =>
|
||||
AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)
|
||||
)
|
||||
return new SvelteUIElement(ImageCarousel, {
|
||||
state,
|
||||
tags,
|
||||
images,
|
||||
estimated,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "image_upload",
|
||||
group: "images",
|
||||
docs: "Creates a button where a user can upload an image to IMGUR",
|
||||
needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls],
|
||||
args: [
|
||||
{
|
||||
type: "key",
|
||||
name: "image_key",
|
||||
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
|
||||
defaultValue: "panoramax",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "label",
|
||||
doc: "The text to show on the button",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "disable_blur",
|
||||
doc: "If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
constr: (state, tags, args, feature) => {
|
||||
const targetKey = args[0] === "" ? undefined : args[0]
|
||||
const noBlur = args[3]?.toLowerCase()?.trim()
|
||||
return new SvelteUIElement(UploadImage, {
|
||||
state,
|
||||
tags,
|
||||
targetKey,
|
||||
feature,
|
||||
labelText: args[1],
|
||||
image: args[2],
|
||||
noBlur: noBlur === "true" || noBlur === "yes",
|
||||
})
|
||||
},
|
||||
},
|
||||
new ImageCarouselVis(),
|
||||
new ImageUpload(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { VariableUiElement } from "../Base/VariableUIElement"
|
|||
import Combine from "../Base/Combine"
|
||||
import NoteCommentElement from "../Popup/Notes/NoteCommentElement.svelte"
|
||||
|
||||
class CloseNoteViz implements SpecialVisualizationSvelte {
|
||||
class CloseNoteViz extends SpecialVisualizationSvelte {
|
||||
public readonly funcName = "close_note"
|
||||
public readonly needsUrls = [Constants.osmAuthConfig.url]
|
||||
public readonly docs =
|
||||
|
@ -53,16 +53,16 @@ class CloseNoteViz implements SpecialVisualizationSvelte {
|
|||
doc: "Text to show if not zoomed in enough",
|
||||
},
|
||||
]
|
||||
public readonly group: "notes"
|
||||
public readonly group = "notes"
|
||||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): SvelteUIElement {
|
||||
const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs(
|
||||
this.args,
|
||||
args
|
||||
args,
|
||||
)
|
||||
|
||||
return new SvelteUIElement(CloseNoteButton, {
|
||||
|
@ -78,7 +78,7 @@ class CloseNoteViz implements SpecialVisualizationSvelte {
|
|||
}
|
||||
}
|
||||
|
||||
class AddNoteCommentViz implements SpecialVisualizationSvelte {
|
||||
class AddNoteCommentViz extends SpecialVisualizationSvelte {
|
||||
funcName = "add_note_comment"
|
||||
needsUrls = [Constants.osmAuthConfig.url]
|
||||
docs = "A textfield to add a comment to a node (with the option to close the note)."
|
||||
|
@ -89,100 +89,109 @@ class AddNoteCommentViz implements SpecialVisualizationSvelte {
|
|||
defaultValue: "id",
|
||||
},
|
||||
]
|
||||
public readonly group: "notes"
|
||||
public readonly group = "notes"
|
||||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(AddNoteComment, { state, tags })
|
||||
}
|
||||
}
|
||||
|
||||
class OpenNote extends SpecialVisualizationSvelte {
|
||||
funcName = "open_note"
|
||||
args = []
|
||||
group = "notes"
|
||||
needsUrls = [Constants.osmAuthConfig.url]
|
||||
docs = "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
): SvelteUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
return new SvelteUIElement(CreateNewNote, {
|
||||
state,
|
||||
coordinate: new UIEventSource({ lon, lat }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class AddImageToNote extends SpecialVisualizationSvelte {
|
||||
funcName = "add_image_to_note"
|
||||
docs = "Adds an image to a node"
|
||||
args = [
|
||||
{
|
||||
name: "Id-key",
|
||||
doc: "The property name where the ID of the note to close can be found",
|
||||
defaultValue: "id",
|
||||
},
|
||||
]
|
||||
group = "notes"
|
||||
needsUrls = [Imgur.apiUrl, ...Imgur.supportingUrls]
|
||||
|
||||
constr(state, tags, args, feature) {
|
||||
const id = tags.data[args[0] ?? "id"]
|
||||
tags = state.featureProperties.getStore(id)
|
||||
return new SvelteUIElement(UploadImage, { state, tags, feature })
|
||||
}
|
||||
}
|
||||
|
||||
class VisualiseNoteComment extends SpecialVisualization {
|
||||
funcName = "visualize_note_comments"
|
||||
group = "notes"
|
||||
docs = "Visualises the comments for notes"
|
||||
args = [
|
||||
{
|
||||
name: "commentsKey",
|
||||
doc: "The property name of the comments, which should be stringified json",
|
||||
defaultValue: "comments",
|
||||
},
|
||||
{
|
||||
name: "start",
|
||||
doc: "Drop the first 'start' comments",
|
||||
defaultValue: "0",
|
||||
},
|
||||
]
|
||||
needsUrls = [Constants.osmAuthConfig.url]
|
||||
|
||||
constr(state, tags, args) {
|
||||
return new VariableUiElement(
|
||||
tags
|
||||
.map((tags) => tags[args[0]])
|
||||
.map((commentsStr) => {
|
||||
const comments: { text: string }[] = JSON.parse(commentsStr)
|
||||
const startLoc = Number(args[1] ?? 0)
|
||||
if (!isNaN(startLoc) && startLoc > 0) {
|
||||
comments.splice(0, startLoc)
|
||||
}
|
||||
return new Combine(
|
||||
comments
|
||||
.filter((c) => c.text !== "")
|
||||
.map(
|
||||
(comment) =>
|
||||
new SvelteUIElement(NoteCommentElement, {
|
||||
comment,
|
||||
state,
|
||||
}),
|
||||
),
|
||||
).SetClass("flex flex-col")
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class NoteVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
return [
|
||||
new AddNoteCommentViz(),
|
||||
new CloseNoteViz(),
|
||||
{
|
||||
funcName: "open_note",
|
||||
args: [],
|
||||
group: "notes",
|
||||
needsUrls: [Constants.osmAuthConfig.url],
|
||||
docs: "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): SvelteUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
return new SvelteUIElement(CreateNewNote, {
|
||||
state,
|
||||
coordinate: new UIEventSource({ lon, lat }),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "add_image_to_note",
|
||||
docs: "Adds an image to a node",
|
||||
args: [
|
||||
{
|
||||
name: "Id-key",
|
||||
doc: "The property name where the ID of the note to close can be found",
|
||||
defaultValue: "id",
|
||||
},
|
||||
],
|
||||
group: "notes",
|
||||
needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls],
|
||||
|
||||
constr: (state, tags, args, feature) => {
|
||||
const id = tags.data[args[0] ?? "id"]
|
||||
tags = state.featureProperties.getStore(id)
|
||||
return new SvelteUIElement(UploadImage, { state, tags, feature })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "visualize_note_comments",
|
||||
group: "notes",
|
||||
docs: "Visualises the comments for notes",
|
||||
args: [
|
||||
{
|
||||
name: "commentsKey",
|
||||
doc: "The property name of the comments, which should be stringified json",
|
||||
defaultValue: "comments",
|
||||
},
|
||||
{
|
||||
name: "start",
|
||||
doc: "Drop the first 'start' comments",
|
||||
defaultValue: "0",
|
||||
},
|
||||
],
|
||||
needsUrls: [Constants.osmAuthConfig.url],
|
||||
constr: (state, tags, args) =>
|
||||
new VariableUiElement(
|
||||
tags
|
||||
.map((tags) => tags[args[0]])
|
||||
.map((commentsStr) => {
|
||||
const comments: { text: string }[] = JSON.parse(commentsStr)
|
||||
const startLoc = Number(args[1] ?? 0)
|
||||
if (!isNaN(startLoc) && startLoc > 0) {
|
||||
comments.splice(0, startLoc)
|
||||
}
|
||||
return new Combine(
|
||||
comments
|
||||
.filter((c) => c.text !== "")
|
||||
.map(
|
||||
(comment) =>
|
||||
new SvelteUIElement(NoteCommentElement, {
|
||||
comment,
|
||||
state,
|
||||
})
|
||||
)
|
||||
).SetClass("flex flex-col")
|
||||
})
|
||||
),
|
||||
},
|
||||
new OpenNote(),
|
||||
new AddImageToNote(),
|
||||
new VisualiseNoteComment(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { MangroveReviews } from "mangrove-reviews-typescript"
|
||||
import FeatureReviews from "../../Logic/Web/MangroveReviews"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
|
@ -12,179 +12,190 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
|||
import BaseUIElement from "../BaseUIElement"
|
||||
import Combine from "../Base/Combine"
|
||||
|
||||
class CreateReview extends SpecialVisualizationSvelte {
|
||||
funcName = "create_review"
|
||||
group = "reviews"
|
||||
|
||||
docs = "Invites the contributor to leave a review. Somewhat small UI-element until interacted"
|
||||
needsUrls = [MangroveReviews.ORIGINAL_API]
|
||||
args = [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
doc: "The question to ask during the review",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tags, args, feature, layer) {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const question = args[2]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState?.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state,
|
||||
)
|
||||
return new SvelteUIElement(ReviewForm, {
|
||||
reviews,
|
||||
state,
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
question,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ListReview extends SpecialVisualizationSvelte {
|
||||
funcName = "list_reviews"
|
||||
group = "reviews"
|
||||
|
||||
docs = "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"
|
||||
needsUrls = [MangroveReviews.ORIGINAL_API]
|
||||
args = [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tags, args, feature) {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState?.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state,
|
||||
)
|
||||
return new SvelteUIElement(AllReviews, { reviews, state })
|
||||
}
|
||||
}
|
||||
|
||||
class Rating extends SpecialVisualizationSvelte {
|
||||
funcName = "rating"
|
||||
group = "reviews"
|
||||
docs = "Shows stars which represent the average rating on mangrove."
|
||||
needsUrls = [MangroveReviews.ORIGINAL_API]
|
||||
args = [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews.",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tags, args, feature) {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state,
|
||||
)
|
||||
return new SvelteUIElement(StarsBarIcon, {
|
||||
score: reviews.average,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ImportMangroveKey extends SpecialVisualizationSvelte {
|
||||
funcName = "import_mangrove_key"
|
||||
group = "settings"
|
||||
|
||||
docs = "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
doc: "The text that is shown on the button",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
_: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
): SvelteUIElement {
|
||||
const [text] = argument
|
||||
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
||||
}
|
||||
}
|
||||
|
||||
class Reviews extends SpecialVisualization{
|
||||
funcName = "reviews"
|
||||
group = "reviews"
|
||||
|
||||
example =
|
||||
"`{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"
|
||||
docs = "A pragmatic combination of `create_review` and `list_reviews`"
|
||||
args = [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
doc: "The question to ask in the review form. Optional",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new Combine([
|
||||
new CreateReview().constr(state, tagSource, args, feature, layer),
|
||||
new ListReview().constr(state, tagSource, args, feature),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export class ReviewSpecialVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
const createReview: SpecialVisualization & { group } = {
|
||||
funcName: "create_review",
|
||||
group: "reviews",
|
||||
|
||||
docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted",
|
||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||
args: [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
doc: "The question to ask during the review",
|
||||
},
|
||||
],
|
||||
constr: (state, tags, args, feature, layer) => {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const question = args[2]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState?.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state
|
||||
)
|
||||
return new SvelteUIElement(ReviewForm, {
|
||||
reviews,
|
||||
state,
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
question,
|
||||
})
|
||||
},
|
||||
}
|
||||
const listReviews: SpecialVisualization & { group } = {
|
||||
funcName: "list_reviews",
|
||||
group: "reviews",
|
||||
|
||||
docs: "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",
|
||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||
args: [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
],
|
||||
constr: (state, tags, args, feature) => {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState?.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state
|
||||
)
|
||||
return new SvelteUIElement(AllReviews, { reviews, state })
|
||||
},
|
||||
}
|
||||
return [
|
||||
{
|
||||
funcName: "rating",
|
||||
group: "reviews",
|
||||
docs: "Shows stars which represent the average rating on mangrove.",
|
||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||
args: [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews.",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
],
|
||||
constr: (state, tags, args, feature) => {
|
||||
const nameKey = args[0] ?? "name"
|
||||
const fallbackName = args[1]
|
||||
const reviews = FeatureReviews.construct(
|
||||
feature,
|
||||
tags,
|
||||
state.userRelatedState.mangroveIdentity,
|
||||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
},
|
||||
state
|
||||
)
|
||||
return new SvelteUIElement(StarsBarIcon, {
|
||||
score: reviews.average,
|
||||
})
|
||||
},
|
||||
},
|
||||
createReview,
|
||||
listReviews,
|
||||
{
|
||||
funcName: "import_mangrove_key",
|
||||
group: "settings",
|
||||
|
||||
docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
doc: "The text that is shown on the button",
|
||||
},
|
||||
],
|
||||
needsUrls: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
_: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): SvelteUIElement {
|
||||
const [text] = argument
|
||||
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "reviews",
|
||||
group: "reviews",
|
||||
|
||||
example:
|
||||
"`{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",
|
||||
docs: "A pragmatic combination of `create_review` and `list_reviews`",
|
||||
args: [
|
||||
{
|
||||
name: "subjectKey",
|
||||
defaultValue: "name",
|
||||
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
|
||||
},
|
||||
{
|
||||
name: "fallback",
|
||||
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
doc: "The question to ask in the review form. Optional",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new Combine([
|
||||
createReview.constr(state, tagSource, args, feature, layer),
|
||||
listReviews.constr(state, tagSource, args, feature, layer),
|
||||
])
|
||||
},
|
||||
},
|
||||
new Rating(),
|
||||
new CreateReview(),
|
||||
new ListReview(), new ImportMangroveKey(),
|
||||
new Reviews(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import DisabledQuestions from "../Popup/DisabledQuestions.svelte"
|
||||
import Constants from "../../Models/Constants"
|
||||
import LogoutButton from "../Base/LogoutButton.svelte"
|
||||
import LoginButton from "../Base/LoginButton.svelte"
|
||||
|
@ -17,191 +16,234 @@ import { Utils } from "../../Utils"
|
|||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import QrCode from "../Popup/QrCode.svelte"
|
||||
import ClearGPSHistory from "../BigComponents/ClearGPSHistory.svelte"
|
||||
|
||||
class LanguagePickerVis extends SpecialVisualizationSvelte {
|
||||
funcName = "language_picker"
|
||||
args = []
|
||||
group = "settings"
|
||||
docs = "A component to set the language of the user interface"
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) =>
|
||||
showTranslations
|
||||
? LanguageUtils.usedLanguagesSorted
|
||||
: state?.theme?.language ?? LanguageUtils.usedLanguagesSorted,
|
||||
)
|
||||
return new SvelteUIElement(LanguagePicker, {
|
||||
assignTo: state.userRelatedState.language,
|
||||
availableLanguages,
|
||||
preferredLanguages: state.osmConnection.userDetails.map(
|
||||
(ud) => ud?.languages ?? [],
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class DisabledQuestions extends SpecialVisualizationSvelte {
|
||||
funcName = "disabled_questions"
|
||||
group = "settings"
|
||||
docs = "Shows which questions are disabled for every layer. Used in 'settings'"
|
||||
needsUrls = []
|
||||
args = []
|
||||
|
||||
constr(state) {
|
||||
return new SvelteUIElement(DisabledQuestions, { state })
|
||||
}
|
||||
}
|
||||
|
||||
class GyroscopeAllTags extends SpecialVisualizationSvelte {
|
||||
funcName = "gyroscope_all_tags"
|
||||
group = "settings"
|
||||
docs = "Shows the current tags of the GPS-representing object, used for debugging"
|
||||
args = []
|
||||
|
||||
constr(): SvelteUIElement {
|
||||
return new SvelteUIElement(OrientationDebugPanel, {})
|
||||
}
|
||||
}
|
||||
|
||||
class GpsAllTags extends SpecialVisualizationSvelte {
|
||||
funcName = "gps_all_tags"
|
||||
group = "settings"
|
||||
docs = "Shows the current tags of the GPS-representing object, used for debugging"
|
||||
args = []
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const tags = (<ThemeViewState>(
|
||||
state
|
||||
)).geolocation.currentUserLocation.features.map(
|
||||
(features) => features[0]?.properties,
|
||||
)
|
||||
return new SvelteUIElement(AllTagsPanel, {
|
||||
state,
|
||||
tags,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class StorageAlLTags extends SpecialVisualizationSvelte {
|
||||
funcName = "storage_all_tags"
|
||||
group = "settings"
|
||||
docs = "Shows the current state of storage"
|
||||
args = []
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const data = {}
|
||||
for (const key in localStorage) {
|
||||
data[key] = localStorage[key]
|
||||
}
|
||||
const tags = new UIEventSource(data)
|
||||
|
||||
navigator.storage.estimate().then((estimate) => {
|
||||
data["__usage:current:bytes"] = estimate.usage
|
||||
data["__usage:current:human"] = Utils.toHumanByteSize(estimate.usage)
|
||||
data["__usage:quota:bytes"] = estimate.quota
|
||||
data["__usage:quota:human"] = Utils.toHumanByteSize(estimate.quota)
|
||||
tags.ping()
|
||||
})
|
||||
return new SvelteUIElement(AllTagsPanel, { state, tags })
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearCachesVis extends SpecialVisualizationSvelte {
|
||||
funcName = "clear_caches"
|
||||
docs = "A button which clears the locally downloaded data and the service worker. Login status etc will be kept"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
required: true,
|
||||
doc: "The text to show on the button",
|
||||
},
|
||||
]
|
||||
group = "settings"
|
||||
|
||||
constr(
|
||||
_: SpecialVisualizationState,
|
||||
__: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(ClearCaches, {
|
||||
msg: argument[0] ?? "Clear local caches",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class LoginButtonVis extends SpecialVisualizationSvelte {
|
||||
funcName = "login_button"
|
||||
args = [
|
||||
{
|
||||
name: "force",
|
||||
doc: "Always show this button, even if logged in",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
doc: "Message to display on the button",
|
||||
},
|
||||
]
|
||||
docs = "Show a login button"
|
||||
group = "settings"
|
||||
|
||||
constr(state: SpecialVisualizationState, _, args): SvelteUIElement {
|
||||
const force = args[0].toLowerCase()
|
||||
let msg = args[1]
|
||||
if (msg === "") {
|
||||
msg = undefined
|
||||
}
|
||||
return new SvelteUIElement(LoginButton, {
|
||||
osmConnection: state.osmConnection,
|
||||
msg,
|
||||
forceShow: force === "yes" || force === "true",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class QrLogin extends SpecialVisualizationSvelte {
|
||||
funcName = "qr_login"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Extra text on the side of the QR-code",
|
||||
},
|
||||
{
|
||||
name: "textClass",
|
||||
doc: "CSS class of the the side text",
|
||||
},
|
||||
]
|
||||
docs = "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out"
|
||||
group = "settings"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const shared_oauth_cookie = state.osmConnection.getToken()
|
||||
const sideText = argument[0]
|
||||
const sideTextClass = argument[1] ?? ""
|
||||
return new SvelteUIElement(QrCode, {
|
||||
state,
|
||||
tags,
|
||||
sideText,
|
||||
sideTextClass,
|
||||
extraUrlParams: { shared_oauth_cookie },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Logout extends SpecialVisualizationSvelte {
|
||||
funcName = "logout"
|
||||
args = []
|
||||
needsUrls = [Constants.osmAuthConfig.url]
|
||||
docs = "Shows a button where the user can log out"
|
||||
group = "settings"
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
|
||||
}
|
||||
}
|
||||
|
||||
class PendingChanges extends SpecialVisualizationSvelte {
|
||||
funcName = "pending_changes"
|
||||
docs = "A module showing the pending changes, with the option to clear the pending changes"
|
||||
group = "settings"
|
||||
args = []
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
|
||||
}
|
||||
}
|
||||
|
||||
class ClearLocationHistoryVis extends SpecialVisualizationSvelte {
|
||||
group= "settings"
|
||||
funcName = "clear_location_history"
|
||||
docs = "A button to remove the travelled track information from the device"
|
||||
args = []
|
||||
|
||||
constr(state) {
|
||||
return new SvelteUIElement(ClearGPSHistory, { state })
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsVisualisations {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
return [
|
||||
{
|
||||
funcName: "language_picker",
|
||||
args: [],
|
||||
group: "settings",
|
||||
docs: "A component to set the language of the user interface",
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) =>
|
||||
showTranslations
|
||||
? LanguageUtils.usedLanguagesSorted
|
||||
: state?.theme?.language ?? LanguageUtils.usedLanguagesSorted
|
||||
)
|
||||
return new SvelteUIElement(LanguagePicker, {
|
||||
assignTo: state.userRelatedState.language,
|
||||
availableLanguages,
|
||||
preferredLanguages: state.osmConnection.userDetails.map(
|
||||
(ud) => ud?.languages ?? []
|
||||
),
|
||||
})
|
||||
},
|
||||
},
|
||||
new LanguagePickerVis(),
|
||||
|
||||
{
|
||||
funcName: "disabled_questions",
|
||||
group: "settings",
|
||||
docs: "Shows which questions are disabled for every layer. Used in 'settings'",
|
||||
needsUrls: [],
|
||||
args: [],
|
||||
constr(state) {
|
||||
return new SvelteUIElement(DisabledQuestions, { state })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "gyroscope_all_tags",
|
||||
group: "settings",
|
||||
docs: "Shows the current tags of the GPS-representing object, used for debugging",
|
||||
args: [],
|
||||
constr(): SvelteUIElement {
|
||||
return new SvelteUIElement(OrientationDebugPanel, {})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "gps_all_tags",
|
||||
group: "settings",
|
||||
docs: "Shows the current tags of the GPS-representing object, used for debugging",
|
||||
args: [],
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const tags = (<ThemeViewState>(
|
||||
state
|
||||
)).geolocation.currentUserLocation.features.map(
|
||||
(features) => features[0]?.properties
|
||||
)
|
||||
return new SvelteUIElement(AllTagsPanel, {
|
||||
state,
|
||||
tags,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "storage_all_tags",
|
||||
group: "settings",
|
||||
docs: "Shows the current state of storage",
|
||||
args: [],
|
||||
constr: function (state: SpecialVisualizationState): SvelteUIElement {
|
||||
const data = {}
|
||||
for (const key in localStorage) {
|
||||
data[key] = localStorage[key]
|
||||
}
|
||||
const tags = new UIEventSource(data)
|
||||
|
||||
navigator.storage.estimate().then((estimate) => {
|
||||
data["__usage:current:bytes"] = estimate.usage
|
||||
data["__usage:current:human"] = Utils.toHumanByteSize(estimate.usage)
|
||||
data["__usage:quota:bytes"] = estimate.quota
|
||||
data["__usage:quota:human"] = Utils.toHumanByteSize(estimate.quota)
|
||||
tags.ping()
|
||||
})
|
||||
return new SvelteUIElement(AllTagsPanel, { state, tags })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "clear_caches",
|
||||
docs: "A button which clears the locally downloaded data and the service worker. Login status etc will be kept",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
required: true,
|
||||
doc: "The text to show on the button",
|
||||
},
|
||||
],
|
||||
group: "settings",
|
||||
constr(
|
||||
_: SpecialVisualizationState,
|
||||
__: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(ClearCaches, {
|
||||
msg: argument[0] ?? "Clear local caches",
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "login_button",
|
||||
args: [
|
||||
{
|
||||
name: "force",
|
||||
doc: "Always show this button, even if logged in",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
doc: "Message to display on the button",
|
||||
},
|
||||
],
|
||||
docs: "Show a login button",
|
||||
needsUrls: [],
|
||||
group: "settings",
|
||||
constr(state: SpecialVisualizationState, _, args): SvelteUIElement {
|
||||
const force = args[0].toLowerCase()
|
||||
let msg = args[1]
|
||||
if (msg === "") {
|
||||
msg = undefined
|
||||
}
|
||||
return new SvelteUIElement(LoginButton, {
|
||||
osmConnection: state.osmConnection,
|
||||
msg,
|
||||
forceShow: force === "yes" || force === "true",
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "qr_login",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Extra text on the side of the QR-code",
|
||||
},
|
||||
{
|
||||
name: "textClass",
|
||||
doc: "CSS class of the the side text",
|
||||
},
|
||||
],
|
||||
docs: "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out",
|
||||
group: "settings",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
const shared_oauth_cookie = state.osmConnection.getToken()
|
||||
const sideText = argument[0]
|
||||
const sideTextClass = argument[1] ?? ""
|
||||
return new SvelteUIElement(QrCode, {
|
||||
state,
|
||||
tags,
|
||||
sideText,
|
||||
sideTextClass,
|
||||
extraUrlParams: { shared_oauth_cookie },
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
funcName: "logout",
|
||||
args: [],
|
||||
needsUrls: [Constants.osmAuthConfig.url],
|
||||
docs: "Shows a button where the user can log out",
|
||||
group: "settings",
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "pending_changes",
|
||||
docs: "A module showing the pending changes, with the option to clear the pending changes",
|
||||
group: "settings",
|
||||
args: [],
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
|
||||
},
|
||||
},
|
||||
new DisabledQuestions(),
|
||||
new GyroscopeAllTags(),
|
||||
new GpsAllTags(),
|
||||
new StorageAlLTags(),
|
||||
new ClearCachesVis(),
|
||||
new LoginButtonVis(),
|
||||
new QrLogin(),
|
||||
new Logout(),
|
||||
new PendingChanges(),
|
||||
new ClearLocationHistoryVis()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import Maproulette from "../../Logic/Maproulette"
|
|||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import TagApplyButton from "./TagApplyButton.svelte"
|
||||
|
||||
export default class TagApplyViz implements AutoAction, SpecialVisualization {
|
||||
export default class TagApplyViz extends SpecialVisualization implements AutoAction {
|
||||
public readonly funcName = "tag_apply"
|
||||
needsUrls = []
|
||||
group = "data_import"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
@ -14,7 +14,7 @@ import BaseUIElement from "../BaseUIElement"
|
|||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import Combine from "../Base/Combine"
|
||||
|
||||
class StealViz implements SpecialVisualization {
|
||||
class StealViz extends SpecialVisualization {
|
||||
funcName = "steal"
|
||||
group = "tagrendering_manipulation"
|
||||
|
||||
|
@ -77,7 +77,7 @@ class StealViz implements SpecialVisualization {
|
|||
)
|
||||
}
|
||||
|
||||
getLayerDependencies(args): string[] {
|
||||
getLayerDependencies = (args): string[] => {
|
||||
const [, tagRenderingId] = args
|
||||
if (tagRenderingId.indexOf(".") < 0) {
|
||||
throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot"
|
||||
|
@ -87,155 +87,165 @@ class StealViz implements SpecialVisualization {
|
|||
}
|
||||
}
|
||||
|
||||
class Multi extends SpecialVisualization {
|
||||
funcName = "multi"
|
||||
group = "tagrendering_manipulation"
|
||||
docs = "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering"
|
||||
example =
|
||||
"```json\n" +
|
||||
JSON.stringify(
|
||||
{
|
||||
render: {
|
||||
special: {
|
||||
type: "multi",
|
||||
key: "_doors_from_building_properties",
|
||||
tagrendering: {
|
||||
en: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
" ",
|
||||
) +
|
||||
"\n```"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
doc: "The property to read and to interpret as a list of properties",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "tagrendering",
|
||||
doc: "An entire tagRenderingConfig",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "classes",
|
||||
doc: "CSS-classes to apply on every individual item. Seperated by `space`",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
featureTags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
) {
|
||||
const [key, tr, classesRaw] = args
|
||||
const classes = classesRaw ?? ""
|
||||
const translation = new Translation({ "*": tr })
|
||||
return new VariableUiElement(
|
||||
featureTags.map((tags) => {
|
||||
let properties: object[]
|
||||
if (typeof tags[key] === "string") {
|
||||
properties = JSON.parse(tags[key])
|
||||
} else {
|
||||
properties = <object[]>(<unknown>tags[key])
|
||||
}
|
||||
if (!properties) {
|
||||
console.debug(
|
||||
"Could not create a special visualization for multi(",
|
||||
args.join(", ") + ")",
|
||||
"no properties found for object",
|
||||
feature.properties.id,
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
const elements = []
|
||||
for (const property of properties) {
|
||||
const subsTr = new SvelteUIElement(SpecialTranslation, {
|
||||
t: translation,
|
||||
tags: new ImmutableStore(property),
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
// clss: classes ?? "",
|
||||
}).SetClass(classes)
|
||||
elements.push(subsTr)
|
||||
}
|
||||
return elements
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Group extends SpecialVisualizationSvelte {
|
||||
funcName = "group"
|
||||
group = "tagrendering_manipulation"
|
||||
docs = "A collapsable group (accordion)"
|
||||
args = [
|
||||
{
|
||||
name: "header",
|
||||
doc: "The _identifier_ of a single tagRendering. This will be used as header",
|
||||
},
|
||||
{
|
||||
name: "labels",
|
||||
doc: "A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion",
|
||||
},
|
||||
{
|
||||
name: "blacklist",
|
||||
required: false,
|
||||
doc: "A `;`-separated list of either identifiers or label names. Matching tagrenderings will _not_ be included, even if they are in `labels`",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const [header, labelsStr, blacklistStr] = argument
|
||||
const labels = labelsStr.split(";").map((x) => x.trim())
|
||||
const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? []
|
||||
return new SvelteUIElement(GroupedView, {
|
||||
state,
|
||||
tags,
|
||||
selectedElement,
|
||||
layer,
|
||||
header,
|
||||
labels,
|
||||
blacklist,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class OpenInId extends SpecialVisualizationSvelte {
|
||||
funcName = "open_in_iD"
|
||||
docs = "Opens the current view in the iD-editor"
|
||||
args = []
|
||||
group = "tagrendering_manipulation"
|
||||
|
||||
constr(state, feature): SvelteUIElement {
|
||||
return new SvelteUIElement(OpenIdEditor, {
|
||||
mapProperties: state.mapProperties,
|
||||
objectId: feature.data.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class OpenInJosm extends SpecialVisualizationSvelte {
|
||||
funcName = "open_in_josm"
|
||||
group = "tagrendering_manipulation"
|
||||
docs = "Opens the current view in the JOSM-editor"
|
||||
args = []
|
||||
needsUrls = ["http://127.0.0.1:8111/load_and_zoom"]
|
||||
|
||||
constr(state): SvelteUIElement {
|
||||
return new SvelteUIElement(OpenJosm, { state })
|
||||
}
|
||||
}
|
||||
export default class TagrenderingManipulationSpecialVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
return [
|
||||
new StealViz(),
|
||||
{
|
||||
funcName: "multi",
|
||||
group: "tagrendering_manipulation",
|
||||
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
||||
example:
|
||||
"```json\n" +
|
||||
JSON.stringify(
|
||||
{
|
||||
render: {
|
||||
special: {
|
||||
type: "multi",
|
||||
key: "_doors_from_building_properties",
|
||||
tagrendering: {
|
||||
en: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
" "
|
||||
) +
|
||||
"\n```",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
doc: "The property to read and to interpret as a list of properties",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "tagrendering",
|
||||
doc: "An entire tagRenderingConfig",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "classes",
|
||||
doc: "CSS-classes to apply on every individual item. Seperated by `space`",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
featureTags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
) {
|
||||
const [key, tr, classesRaw] = args
|
||||
const classes = classesRaw ?? ""
|
||||
const translation = new Translation({ "*": tr })
|
||||
return new VariableUiElement(
|
||||
featureTags.map((tags) => {
|
||||
let properties: object[]
|
||||
if (typeof tags[key] === "string") {
|
||||
properties = JSON.parse(tags[key])
|
||||
} else {
|
||||
properties = <object[]>(<unknown>tags[key])
|
||||
}
|
||||
if (!properties) {
|
||||
console.debug(
|
||||
"Could not create a special visualization for multi(",
|
||||
args.join(", ") + ")",
|
||||
"no properties found for object",
|
||||
feature.properties.id
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
const elements = []
|
||||
for (const property of properties) {
|
||||
const subsTr = new SvelteUIElement(SpecialTranslation, {
|
||||
t: translation,
|
||||
tags: new ImmutableStore(property),
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
// clss: classes ?? "",
|
||||
}).SetClass(classes)
|
||||
elements.push(subsTr)
|
||||
}
|
||||
return elements
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "group",
|
||||
group: "tagrendering_manipulation",
|
||||
docs: "A collapsable group (accordion)",
|
||||
args: [
|
||||
{
|
||||
name: "header",
|
||||
doc: "The _identifier_ of a single tagRendering. This will be used as header",
|
||||
},
|
||||
{
|
||||
name: "labels",
|
||||
doc: "A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion",
|
||||
},
|
||||
{
|
||||
name: "blacklist",
|
||||
required: false,
|
||||
doc: "A `;`-separated list of either identifiers or label names. Matching tagrenderings will _not_ be included, even if they are in `labels`",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
const [header, labelsStr, blacklistStr] = argument
|
||||
const labels = labelsStr.split(";").map((x) => x.trim())
|
||||
const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? []
|
||||
return new SvelteUIElement(GroupedView, {
|
||||
state,
|
||||
tags,
|
||||
selectedElement,
|
||||
layer,
|
||||
header,
|
||||
labels,
|
||||
blacklist,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "open_in_iD",
|
||||
docs: "Opens the current view in the iD-editor",
|
||||
args: [],
|
||||
group: "tagrendering_manipulation",
|
||||
constr: (state, feature): SvelteUIElement => {
|
||||
return new SvelteUIElement(OpenIdEditor, {
|
||||
mapProperties: state.mapProperties,
|
||||
objectId: feature.data.id,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "open_in_josm",
|
||||
group: "tagrendering_manipulation",
|
||||
docs: "Opens the current view in the JOSM-editor",
|
||||
args: [],
|
||||
needsUrls: ["http://127.0.0.1:8111/load_and_zoom"],
|
||||
|
||||
constr: (state): SvelteUIElement => {
|
||||
return new SvelteUIElement(OpenJosm, { state })
|
||||
},
|
||||
},
|
||||
new Multi(),
|
||||
new Group(),
|
||||
new OpenInId(),
|
||||
new OpenInJosm,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import { Feature, GeoJSON } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Questionbox from "../Popup/TagRendering/Questionbox.svelte"
|
||||
import MinimapViz from "../Popup/MinimapViz.svelte"
|
||||
|
@ -13,11 +13,16 @@ import NothingKnown from "../Popup/NothingKnown.svelte"
|
|||
import { ShareLinkViz } from "../Popup/ShareLinkViz"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import AddNewPoint from "../Popup/AddNewPoint/AddNewPoint.svelte"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { default as FeatureTitle } from "../Popup/Title.svelte"
|
||||
|
||||
/**
|
||||
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
|
||||
*/
|
||||
class QuestionViz implements SpecialVisualizationSvelte {
|
||||
class QuestionViz extends SpecialVisualizationSvelte {
|
||||
funcName = "questions"
|
||||
needsUrls = []
|
||||
docs =
|
||||
|
@ -37,7 +42,7 @@ class QuestionViz implements SpecialVisualizationSvelte {
|
|||
doc: "Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once",
|
||||
},
|
||||
]
|
||||
group: "default"
|
||||
group = "default"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
|
@ -73,179 +78,279 @@ class QuestionViz implements SpecialVisualizationSvelte {
|
|||
}
|
||||
}
|
||||
|
||||
class Minimap extends SpecialVisualizationSvelte {
|
||||
funcName = "minimap"
|
||||
docs = "A small map showing the selected feature."
|
||||
needsUrls = []
|
||||
group = "default"
|
||||
|
||||
args = [
|
||||
{
|
||||
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
|
||||
name: "zoomlevel",
|
||||
defaultValue: "18",
|
||||
},
|
||||
{
|
||||
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)",
|
||||
name: "idKey",
|
||||
defaultValue: "id",
|
||||
},
|
||||
]
|
||||
example = "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource })
|
||||
}
|
||||
}
|
||||
|
||||
class SplitButton extends SpecialVisualizationSvelte {
|
||||
funcName = "split_button"
|
||||
docs = "Adds a button which allows to split a way"
|
||||
args = []
|
||||
group = "default"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(SplitRoadWizard, {
|
||||
id: tagSource.map((pr) => pr.id),
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MoveButton extends SpecialVisualizationSvelte {
|
||||
funcName = "move_button"
|
||||
docs = "Adds a button which allows to move the object to another location. The config will be read from the layer config"
|
||||
args = []
|
||||
group = "default"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
if (feature.geometry.type !== "Point") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new SvelteUIElement(MoveWizard, {
|
||||
state,
|
||||
featureToMove: feature,
|
||||
layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteButton extends SpecialVisualizationSvelte {
|
||||
funcName = "delete_button"
|
||||
docs = "Adds a button which allows to delete the object at this location. The config will be read from the layer config"
|
||||
args = []
|
||||
group = "default"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
if (!layer.deletion) {
|
||||
return undefined
|
||||
}
|
||||
return new SvelteUIElement(DeleteWizard, {
|
||||
tags: tagSource,
|
||||
deleteConfig: layer.deletion,
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class QrCodeVis extends SpecialVisualizationSvelte {
|
||||
funcName = "qr_code"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Extra text on the side of the QR-code",
|
||||
},
|
||||
{
|
||||
name: "textClass",
|
||||
doc: "CSS class of the the side text",
|
||||
},
|
||||
]
|
||||
group = "default"
|
||||
docs = "Generates a QR-code to share the selected object"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
): SvelteUIElement {
|
||||
const sideText = argument[0]
|
||||
const sideTextClass = argument[1] ?? ""
|
||||
return new SvelteUIElement(QrCode, {
|
||||
state,
|
||||
tags,
|
||||
feature,
|
||||
sideText,
|
||||
sideTextClass,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class IfNothingKnown extends SpecialVisualizationSvelte {
|
||||
funcName = "if_nothing_known"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Text to show",
|
||||
required: true,
|
||||
},
|
||||
{ name: "cssClasses", doc: "Classes to apply onto the text" },
|
||||
]
|
||||
group = "default"
|
||||
docs = "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const text = argument[0]
|
||||
const cssClasses = argument[1]
|
||||
return new SvelteUIElement(NothingKnown, {
|
||||
state,
|
||||
tags: tagSource,
|
||||
layer,
|
||||
text,
|
||||
cssClasses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class AddNewPointVis extends SpecialVisualizationSvelte {
|
||||
funcName = "add_new_point"
|
||||
docs = "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`"
|
||||
args = []
|
||||
group = "default"
|
||||
|
||||
constr(state: SpecialVisualizationState, _, __, feature: GeoJSON): SvelteUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
return new SvelteUIElement(AddNewPoint, {
|
||||
state,
|
||||
coordinate: { lon, lat },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Translated extends SpecialVisualization {
|
||||
funcName = "translated"
|
||||
docs = "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes"
|
||||
group = "UI"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The attribute to interpret as json",
|
||||
defaultValue: "value",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource.map((tags) => {
|
||||
const v = tags[argument[0] ?? "value"]
|
||||
try {
|
||||
const tr = typeof v === "string" ? JSON.parse(v) : v
|
||||
return new Translation(tr).SetClass("font-bold")
|
||||
} catch (e) {
|
||||
console.error("Cannot create a translation for", v, "due to", e)
|
||||
return JSON.stringify(v)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TitleVis extends SpecialVisualizationSvelte {
|
||||
group = "UI"
|
||||
funcName = "title"
|
||||
args = []
|
||||
|
||||
docs = "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'"
|
||||
example =
|
||||
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`."
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
_: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
) {
|
||||
return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer })
|
||||
}
|
||||
}
|
||||
|
||||
class BracedVis extends SpecialVisualization {
|
||||
group = "UI"
|
||||
funcName = "braced"
|
||||
docs = "Show a literal text within braces"
|
||||
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
required: true,
|
||||
doc: "The value to show",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new FixedUiElement("{" + args[0] + "}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class UISpecialVisualisations {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
public static initList(): SpecialVisualization[] {
|
||||
return [
|
||||
new QuestionViz(),
|
||||
{
|
||||
funcName: "minimap",
|
||||
docs: "A small map showing the selected feature.",
|
||||
needsUrls: [],
|
||||
group: "default",
|
||||
|
||||
args: [
|
||||
{
|
||||
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
|
||||
name: "zoomlevel",
|
||||
defaultValue: "18",
|
||||
},
|
||||
{
|
||||
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)",
|
||||
name: "idKey",
|
||||
defaultValue: "id",
|
||||
},
|
||||
],
|
||||
example:
|
||||
"`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "split_button",
|
||||
docs: "Adds a button which allows to split a way",
|
||||
args: [],
|
||||
group: "default",
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(SplitRoadWizard, {
|
||||
id: tagSource.map((pr) => pr.id),
|
||||
state,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "move_button",
|
||||
docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config",
|
||||
args: [],
|
||||
group: "default",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
if (feature.geometry.type !== "Point") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new SvelteUIElement(MoveWizard, {
|
||||
state,
|
||||
featureToMove: feature,
|
||||
layer,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "delete_button",
|
||||
docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config",
|
||||
args: [],
|
||||
group: "default",
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
if (!layer.deletion) {
|
||||
return undefined
|
||||
}
|
||||
return new SvelteUIElement(DeleteWizard, {
|
||||
tags: tagSource,
|
||||
deleteConfig: layer.deletion,
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "qr_code",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Extra text on the side of the QR-code",
|
||||
},
|
||||
{
|
||||
name: "textClass",
|
||||
doc: "CSS class of the the side text",
|
||||
},
|
||||
],
|
||||
group: "default",
|
||||
docs: "Generates a QR-code to share the selected object",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): SvelteUIElement {
|
||||
const sideText = argument[0]
|
||||
const sideTextClass = argument[1] ?? ""
|
||||
return new SvelteUIElement(QrCode, {
|
||||
state,
|
||||
tags,
|
||||
feature,
|
||||
sideText,
|
||||
sideTextClass,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "if_nothing_known",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Text to show",
|
||||
required: true,
|
||||
},
|
||||
{ name: "cssClasses", doc: "Classes to apply onto the text" },
|
||||
],
|
||||
group: "default",
|
||||
docs: "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
const text = argument[0]
|
||||
const cssClasses = argument[1]
|
||||
return new SvelteUIElement(NothingKnown, {
|
||||
state,
|
||||
tags: tagSource,
|
||||
layer,
|
||||
text,
|
||||
cssClasses,
|
||||
})
|
||||
},
|
||||
},
|
||||
new Minimap(),
|
||||
new SplitButton(),
|
||||
new MoveButton(),
|
||||
new DeleteButton(),
|
||||
new QrCodeVis(),
|
||||
new IfNothingKnown(),
|
||||
new ShareLinkViz(),
|
||||
{
|
||||
funcName: "add_new_point",
|
||||
docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`",
|
||||
args: [],
|
||||
group: "default",
|
||||
constr(state: SpecialVisualizationState, _, __, feature): SvelteUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
return new SvelteUIElement(AddNewPoint, {
|
||||
state,
|
||||
coordinate: { lon, lat },
|
||||
})
|
||||
},
|
||||
},
|
||||
new AddNewPointVis(),
|
||||
new Translated(),
|
||||
new BracedVis(),
|
||||
new TitleVis(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
|
@ -13,182 +13,194 @@ import { MapillaryLinkVis } from "../Popup/MapillaryLinkVis"
|
|||
import SendEmail from "../Popup/SendEmail.svelte"
|
||||
import DynLink from "../Base/DynLink.svelte"
|
||||
|
||||
class FediverseLinkVis extends SpecialVisualization {
|
||||
funcName = "fediverse_link"
|
||||
group = "web_and_communication"
|
||||
docs = "Converts a fediverse username or link into a clickable link"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
doc: "The attribute-name containing the link",
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
): BaseUIElement {
|
||||
const key = argument[0]
|
||||
return new SvelteUIElement(FediverseLink, { key, tags, state })
|
||||
}
|
||||
}
|
||||
|
||||
class WikipediaVis extends SpecialVisualization {
|
||||
funcName = "wikipedia"
|
||||
group = "web_and_communication"
|
||||
docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag."
|
||||
args = [
|
||||
{
|
||||
name: "keyToShowWikipediaFor",
|
||||
doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used",
|
||||
defaultValue: "wikidata;wikipedia",
|
||||
},
|
||||
]
|
||||
needsUrls = [...Wikidata.neededUrls, ...Wikipedia.neededUrls]
|
||||
|
||||
example =
|
||||
"`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height"
|
||||
|
||||
constr(_, tagsSource, args) {
|
||||
const keys = args[0].split(";").map((k) => k.trim())
|
||||
const wikiIds: Store<string[]> = tagsSource.map((tags) => {
|
||||
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
|
||||
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
|
||||
})
|
||||
return new SvelteUIElement(WikipediaPanel, {
|
||||
wikiIds,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class WikidatalabelVis extends SpecialVisualization {
|
||||
funcName = "wikidata_label"
|
||||
group = "web_and_communication"
|
||||
|
||||
docs = "Shows the label of the corresponding wikidata-item"
|
||||
args = [
|
||||
{
|
||||
name: "keyToShowWikidataFor",
|
||||
doc: "Use the wikidata entry from this key to show the label",
|
||||
defaultValue: "wikidata",
|
||||
},
|
||||
]
|
||||
|
||||
needsUrls = Wikidata.neededUrls
|
||||
example =
|
||||
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself"
|
||||
|
||||
constr(_, tagsSource, args) {
|
||||
const id = tagsSource
|
||||
.map((tags) => tags[args[0]])
|
||||
.map((wikidata) => {
|
||||
const wikidataIds = Utils.NoEmpty(
|
||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
|
||||
)
|
||||
return wikidataIds?.[0]
|
||||
})
|
||||
const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id))
|
||||
|
||||
return new VariableUiElement(
|
||||
entry.map((e) => {
|
||||
if (e === undefined || e["success"] === undefined) {
|
||||
return id.data
|
||||
}
|
||||
const response = <WikidataResponse>e["success"]
|
||||
return Translation.fromMap(response.labels)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SendEmailVis extends SpecialVisualizationSvelte {
|
||||
funcName = "send_email"
|
||||
group = "web_and_communication"
|
||||
docs = "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email"
|
||||
args = [
|
||||
{
|
||||
name: "to",
|
||||
doc: "Who to send the email to?",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
doc: "The subject of the email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
doc: "The text in the email",
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "button_text",
|
||||
doc: "The text shown on the button in the UI",
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
constr(__, tags, args) {
|
||||
return new SvelteUIElement(SendEmail, { args, tags })
|
||||
}
|
||||
}
|
||||
|
||||
class LinkVis extends SpecialVisualizationSvelte {
|
||||
funcName = "link"
|
||||
group = "web_and_communication"
|
||||
docs = "Construct a link. By using the 'special' visualisation notation, translations should be easier"
|
||||
args = [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Text to be shown",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "href",
|
||||
doc: "The URL to link to. Note that this will be URI-encoded before ",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "class",
|
||||
doc: "CSS-classes to add to the element",
|
||||
},
|
||||
{
|
||||
name: "download",
|
||||
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button.",
|
||||
},
|
||||
{
|
||||
name: "arialabel",
|
||||
doc: "If set, this text will be used as aria-label",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`",
|
||||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
): SvelteUIElement {
|
||||
let [text, href, classnames, download, ariaLabel, icon] = args
|
||||
if (download === "") {
|
||||
download = undefined
|
||||
}
|
||||
const newTab = download === undefined && !href.startsWith("#")
|
||||
const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags))
|
||||
const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags))
|
||||
return new SvelteUIElement(DynLink, {
|
||||
text: textStore,
|
||||
href: hrefStore,
|
||||
classnames: new ImmutableStore(classnames),
|
||||
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
|
||||
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
|
||||
newTab: new ImmutableStore(newTab),
|
||||
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
|
||||
}).setSpan()
|
||||
}
|
||||
}
|
||||
export class WebAndCommunicationSpecialVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
return [
|
||||
{
|
||||
funcName: "fediverse_link",
|
||||
group: "web_and_communication",
|
||||
docs: "Converts a fediverse username or link into a clickable link",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
doc: "The attribute-name containing the link",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): BaseUIElement {
|
||||
const key = argument[0]
|
||||
return new SvelteUIElement(FediverseLink, { key, tags, state })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "wikipedia",
|
||||
group: "web_and_communication",
|
||||
docs: "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag.",
|
||||
args: [
|
||||
{
|
||||
name: "keyToShowWikipediaFor",
|
||||
doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used",
|
||||
defaultValue: "wikidata;wikipedia",
|
||||
},
|
||||
],
|
||||
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
|
||||
|
||||
example:
|
||||
"`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height",
|
||||
constr: (_, tagsSource, args) => {
|
||||
const keys = args[0].split(";").map((k) => k.trim())
|
||||
const wikiIds: Store<string[]> = tagsSource.map((tags) => {
|
||||
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
|
||||
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
|
||||
})
|
||||
return new SvelteUIElement(WikipediaPanel, {
|
||||
wikiIds,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "wikidata_label",
|
||||
group: "web_and_communication",
|
||||
|
||||
docs: "Shows the label of the corresponding wikidata-item",
|
||||
args: [
|
||||
{
|
||||
name: "keyToShowWikidataFor",
|
||||
doc: "Use the wikidata entry from this key to show the label",
|
||||
defaultValue: "wikidata",
|
||||
},
|
||||
],
|
||||
needsUrls: Wikidata.neededUrls,
|
||||
example:
|
||||
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself",
|
||||
constr: (_, tagsSource, args) => {
|
||||
const id = tagsSource
|
||||
.map((tags) => tags[args[0]])
|
||||
.map((wikidata) => {
|
||||
const wikidataIds = Utils.NoEmpty(
|
||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
||||
)
|
||||
return wikidataIds?.[0]
|
||||
})
|
||||
const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id))
|
||||
|
||||
return new VariableUiElement(
|
||||
entry.map((e) => {
|
||||
if (e === undefined || e["success"] === undefined) {
|
||||
return id.data
|
||||
}
|
||||
const response = <WikidataResponse>e["success"]
|
||||
return Translation.fromMap(response.labels)
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
new FediverseLinkVis(),
|
||||
new WikipediaVis(),
|
||||
new WikidatalabelVis(),
|
||||
new MapillaryLinkVis(),
|
||||
{
|
||||
funcName: "send_email",
|
||||
group: "web_and_communication",
|
||||
docs: "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email",
|
||||
args: [
|
||||
{
|
||||
name: "to",
|
||||
doc: "Who to send the email to?",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
doc: "The subject of the email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
doc: "The text in the email",
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "button_text",
|
||||
doc: "The text shown on the button in the UI",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
|
||||
constr(__, tags, args) {
|
||||
return new SvelteUIElement(SendEmail, { args, tags })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "link",
|
||||
group: "web_and_communication",
|
||||
docs: "Construct a link. By using the 'special' visualisation notation, translations should be easier",
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
doc: "Text to be shown",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "href",
|
||||
doc: "The URL to link to. Note that this will be URI-encoded before ",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "class",
|
||||
doc: "CSS-classes to add to the element",
|
||||
},
|
||||
{
|
||||
name: "download",
|
||||
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button.",
|
||||
},
|
||||
{
|
||||
name: "arialabel",
|
||||
doc: "If set, this text will be used as aria-label",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`",
|
||||
},
|
||||
],
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
): SvelteUIElement {
|
||||
let [text, href, classnames, download, ariaLabel, icon] = args
|
||||
if (download === "") {
|
||||
download = undefined
|
||||
}
|
||||
const newTab = download === undefined && !href.startsWith("#")
|
||||
const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags))
|
||||
const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags))
|
||||
return new SvelteUIElement(DynLink, {
|
||||
text: textStore,
|
||||
href: hrefStore,
|
||||
classnames: new ImmutableStore(classnames),
|
||||
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
|
||||
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
|
||||
newTab: new ImmutableStore(newTab),
|
||||
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
|
||||
}).setSpan()
|
||||
},
|
||||
},
|
||||
new SendEmailVis(),
|
||||
new LinkVis(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
|
|||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import { Utils } from "../Utils"
|
||||
|
||||
/**
|
||||
* The state needed to render a special Visualisation.
|
||||
|
@ -85,17 +86,24 @@ export interface SpecialVisualizationState {
|
|||
reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void>
|
||||
}
|
||||
|
||||
export interface SpecialVisualization {
|
||||
export abstract class SpecialVisualization {
|
||||
readonly funcName: string
|
||||
readonly docs: string | BaseUIElement
|
||||
/**
|
||||
* The 'group' is merely what association it has in the docs
|
||||
*/
|
||||
readonly group?: string
|
||||
readonly example?: string
|
||||
readonly needsUrls?: string[] | ((args: string[]) => string | string[])
|
||||
readonly definedIn = Utils.runningFromConsole ? Utils.getLocationInCode(2) : undefined;
|
||||
|
||||
/**
|
||||
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
|
||||
*/
|
||||
readonly needsNodeDatabase?: boolean
|
||||
/**
|
||||
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
|
||||
*/
|
||||
readonly args: {
|
||||
name: string
|
||||
defaultValue?: string
|
||||
|
@ -107,7 +115,7 @@ export interface SpecialVisualization {
|
|||
|
||||
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
|
||||
|
||||
constr(
|
||||
abstract constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
|
@ -116,32 +124,8 @@ export interface SpecialVisualization {
|
|||
): BaseUIElement
|
||||
}
|
||||
|
||||
export interface SpecialVisualizationSvelte extends SpecialVisualization {
|
||||
readonly funcName: string
|
||||
readonly docs: string
|
||||
/**
|
||||
* The 'group' is merely what association it has in the docs
|
||||
*/
|
||||
readonly group: string
|
||||
readonly example?: string
|
||||
readonly needsUrls?: string[] | ((args: string[]) => string | string[])
|
||||
|
||||
/**
|
||||
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
|
||||
*/
|
||||
readonly needsNodeDatabase?: boolean
|
||||
readonly args: {
|
||||
name: string
|
||||
defaultValue?: string
|
||||
doc: string
|
||||
required?: false | boolean
|
||||
type?: "key" | string
|
||||
}[]
|
||||
readonly getLayerDependencies?: (argument: string[]) => string[]
|
||||
|
||||
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
|
||||
|
||||
constr(
|
||||
export abstract class SpecialVisualizationSvelte extends SpecialVisualization {
|
||||
abstract constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
|
|
|
@ -1,37 +1,9 @@
|
|||
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||
import BaseUIElement from "./BaseUIElement"
|
||||
import { default as FeatureTitle } from "./Popup/Title.svelte"
|
||||
import {
|
||||
RenderingSpecification,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
} from "./SpecialVisualization"
|
||||
import { HistogramViz } from "./Popup/HistogramViz"
|
||||
import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization"
|
||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import AllTagsPanel from "./Popup/AllTagsPanel/AllTagsPanel.svelte"
|
||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||
import { Translation } from "./i18n/Translation"
|
||||
import Translations from "./i18n/Translations"
|
||||
import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis"
|
||||
import { LanguageElement } from "./Popup/LanguageElement/LanguageElement"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import { GeoOperations } from "../Logic/GeoOperations"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
||||
import ExportFeatureButton from "./Popup/ExportFeatureButton.svelte"
|
||||
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import Constants from "../Models/Constants"
|
||||
import { TagUtils } from "../Logic/Tags/TagUtils"
|
||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||
import { Unit } from "../Models/Unit"
|
||||
import DirectionIndicator from "./Base/DirectionIndicator.svelte"
|
||||
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
||||
import MarkdownUtils from "../Utils/MarkdownUtils"
|
||||
import { And } from "../Logic/Tags/And"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { ImageVisualisations } from "./SpecialVisualisations/ImageVisualisations"
|
||||
import { NoteVisualisations } from "./SpecialVisualisations/NoteVisualisations"
|
||||
import { FavouriteVisualisations } from "./SpecialVisualisations/FavouriteVisualisations"
|
||||
|
@ -39,13 +11,13 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual
|
|||
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
|
||||
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
|
||||
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
|
||||
import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
||||
import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
|
||||
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"
|
||||
import OpeningHoursWithError from "./OpeningHours/Visualisation/OpeningHoursWithError.svelte"
|
||||
import { OH } from "./OpeningHours/OpeningHours"
|
||||
import opening_hours from "opening_hours"
|
||||
import TagrenderingManipulationSpecialVisualisations
|
||||
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
||||
import {
|
||||
WebAndCommunicationSpecialVisualisations,
|
||||
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||
import { DataVisualisations } from "./Popup/DataVisualisations"
|
||||
import { DataExportVisualisations } from "./Popup/DataExportVisualisations"
|
||||
|
||||
export default class SpecialVisualizations {
|
||||
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
|
||||
|
@ -73,6 +45,12 @@ export default class SpecialVisualizations {
|
|||
const example =
|
||||
viz.example ??
|
||||
"`{" + viz.funcName + "(" + viz.args.map((arg) => arg.defaultValue).join(",") + ")}`"
|
||||
|
||||
let definitionPlace = ""
|
||||
if(viz.definedIn){
|
||||
const path = viz.definedIn
|
||||
definitionPlace = `Defined in [${path.markdownLocation}](${path.markdownLocation})`
|
||||
}
|
||||
return [
|
||||
"### " + viz.funcName,
|
||||
viz.docs,
|
||||
|
@ -88,6 +66,7 @@ export default class SpecialVisualizations {
|
|||
})
|
||||
)
|
||||
: undefined,
|
||||
definitionPlace,
|
||||
"#### Example usage of " + viz.funcName,
|
||||
example,
|
||||
].join("\n\n")
|
||||
|
@ -119,6 +98,7 @@ export default class SpecialVisualizations {
|
|||
const groupExplanations: Record<string, string> = {
|
||||
default:
|
||||
"These special visualisations are (mostly) interactive components that most elements get by default. You'll normally won't need them in custom layers. There are also a few miscellaneous elements supporting the map UI.",
|
||||
data: "Visualises data of a POI, sometimes with data updating capabilities",
|
||||
favourites:
|
||||
"Elements relating to marking an object as favourite (giving it a heart). Default element",
|
||||
settings: "Elements part of the usersettings-ui",
|
||||
|
@ -132,18 +112,19 @@ export default class SpecialVisualizations {
|
|||
"Special visualisations which reuse other tagRenderings to show data, but with a twist.",
|
||||
web_and_communication:
|
||||
"Tools to show data from external websites, which link to external websites or which link to external profiles",
|
||||
ui: "Elements to support the user interface, e.g. 'title', 'translated'"
|
||||
}
|
||||
|
||||
const helpTexts: string[] = []
|
||||
let lastGroup: string = null
|
||||
for (const viz of vis) {
|
||||
if (viz.group !== lastGroup) {
|
||||
lastGroup = viz.group
|
||||
if (viz.group?.toLowerCase() !== lastGroup) {
|
||||
lastGroup = viz.group?.toLowerCase()
|
||||
if (viz.group === undefined) {
|
||||
helpTexts.push("## Unclassified elements\n\nVarious elements")
|
||||
} else {
|
||||
helpTexts.push("## " + viz.group)
|
||||
if (!groupExplanations[viz.group]) {
|
||||
if (!groupExplanations[viz.group.toLowerCase()]) {
|
||||
throw (
|
||||
"\n\n >>>> ERROR <<<< Unknown visualisation group type: " +
|
||||
viz.group +
|
||||
|
@ -207,416 +188,10 @@ export default class SpecialVisualizations {
|
|||
...DataImportSpecialVisualisations.initList(),
|
||||
...TagrenderingManipulationSpecialVisualisations.initList(),
|
||||
...WebAndCommunicationSpecialVisualisations.initList(),
|
||||
new HistogramViz(),
|
||||
{
|
||||
funcName: "export_as_gpx",
|
||||
docs: "Exports the selected feature as GPX-file",
|
||||
args: [],
|
||||
needsUrls: [],
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
) {
|
||||
if (feature.geometry.type !== "LineString") {
|
||||
return undefined
|
||||
}
|
||||
const t = Translations.t.general.download
|
||||
|
||||
return new SvelteUIElement(ExportFeatureButton, {
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
mimetype: "{gpx=application/gpx+xml}",
|
||||
extension: "gpx",
|
||||
construct: (feature: Feature<LineString>, title: string) =>
|
||||
GeoOperations.toGpx(feature, title),
|
||||
helpertext: t.downloadGpxHelper,
|
||||
maintext: t.downloadFeatureAsGpx,
|
||||
})
|
||||
},
|
||||
},
|
||||
...DataVisualisations.initList(),
|
||||
...DataExportVisualisations.initList(),
|
||||
new UploadToOsmViz(),
|
||||
new MultiApplyViz(),
|
||||
|
||||
new LanguageElement(),
|
||||
{
|
||||
funcName: "all_tags",
|
||||
docs: "Prints all key-value pairs of the object - used for debugging",
|
||||
args: [],
|
||||
constr: (
|
||||
state,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
_,
|
||||
__,
|
||||
layer: LayerConfig
|
||||
) => new SvelteUIElement(AllTagsPanel, { tags, layer }),
|
||||
},
|
||||
{
|
||||
funcName: "opening_hours_table",
|
||||
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
defaultValue: "opening_hours",
|
||||
type: "key",
|
||||
doc: "The tagkey from which the table is constructed.",
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
|
||||
},
|
||||
{
|
||||
name: "postfix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
|
||||
},
|
||||
],
|
||||
needsUrls: [Constants.countryCoderEndpoint],
|
||||
example:
|
||||
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
|
||||
constr: (state, tagSource: UIEventSource<any>, args) => {
|
||||
const [key, prefix, postfix] = args
|
||||
const openingHoursStore: Store<opening_hours | "error" | undefined> =
|
||||
OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
|
||||
return new SvelteUIElement(OpeningHoursWithError, {
|
||||
tags: tagSource,
|
||||
key,
|
||||
opening_hours_obj: openingHoursStore,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "opening_hours_state",
|
||||
docs: "A small element, showing if the POI is currently open and when the next change is",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
defaultValue: "opening_hours",
|
||||
doc: "The tagkey from which the opening hours are read.",
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
|
||||
},
|
||||
{
|
||||
name: "postfix",
|
||||
defaultValue: "",
|
||||
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
): SvelteUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
const postfix = args[2]
|
||||
return new SvelteUIElement(NextChangeViz, {
|
||||
state,
|
||||
keyToUse,
|
||||
tags,
|
||||
prefix,
|
||||
postfix,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "canonical",
|
||||
|
||||
docs: "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. ",
|
||||
example:
|
||||
"If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The key of the tag to give the canonical text for",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
constr: (state, tagSource, args) => {
|
||||
const key = args[0]
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => tags[key])
|
||||
.map((value) => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const allUnits: Unit[] = [].concat(
|
||||
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? [])
|
||||
)
|
||||
const unit = allUnits.filter((unit) =>
|
||||
unit.isApplicableToKey(key)
|
||||
)[0]
|
||||
if (unit === undefined) {
|
||||
return value
|
||||
}
|
||||
const getCountry = () => tagSource.data._country
|
||||
return unit.asHumanLongValue(value, getCountry)
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "export_as_geojson",
|
||||
docs: "Exports the selected feature as GeoJson-file",
|
||||
args: [],
|
||||
|
||||
constr: (state, tags, args, feature, layer) => {
|
||||
const t = Translations.t.general.download
|
||||
return new SvelteUIElement(ExportFeatureButton, {
|
||||
tags,
|
||||
feature,
|
||||
layer,
|
||||
mimetype: "application/vnd.geo+json",
|
||||
extension: "geojson",
|
||||
construct: (feature: Feature<LineString>) =>
|
||||
JSON.stringify(feature, null, " "),
|
||||
maintext: t.downloadFeatureAsGeojson,
|
||||
helpertext: t.downloadGeoJsonHelper,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "clear_location_history",
|
||||
docs: "A button to remove the travelled track information from the device",
|
||||
args: [],
|
||||
|
||||
constr: (state) => {
|
||||
return new SvelteUIElement(ClearGPSHistory, { state })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "title",
|
||||
args: [],
|
||||
|
||||
docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'",
|
||||
example:
|
||||
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.",
|
||||
constr: (
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
_: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
) => {
|
||||
return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "statistics",
|
||||
docs: "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer",
|
||||
args: [],
|
||||
|
||||
constr: (state) => new SvelteUIElement(AllFeaturesStatistics, { state }),
|
||||
},
|
||||
{
|
||||
funcName: "translated",
|
||||
docs: "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes",
|
||||
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The attribute to interpret as json",
|
||||
defaultValue: "value",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource.map((tags) => {
|
||||
const v = tags[argument[0] ?? "value"]
|
||||
try {
|
||||
const tr = typeof v === "string" ? JSON.parse(v) : v
|
||||
return new Translation(tr).SetClass("font-bold")
|
||||
} catch (e) {
|
||||
console.error("Cannot create a translation for", v, "due to", e)
|
||||
return JSON.stringify(v)
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "braced",
|
||||
docs: "Show a literal text within braces",
|
||||
|
||||
args: [
|
||||
{
|
||||
name: "text",
|
||||
required: true,
|
||||
doc: "The value to show",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new FixedUiElement("{" + args[0] + "}")
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "tags",
|
||||
docs: "Shows a (json of) tags in a human-readable way + links to the wiki",
|
||||
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
defaultValue: "value",
|
||||
doc: "The key to look for the tags",
|
||||
},
|
||||
],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "value"
|
||||
return new VariableUiElement(
|
||||
tagSource.map((tags) => {
|
||||
let value = tags[key]
|
||||
if (!value) {
|
||||
return new FixedUiElement("No tags found").SetClass("font-bold")
|
||||
}
|
||||
if (typeof value === "string" && value.startsWith("{")) {
|
||||
value = JSON.parse(value)
|
||||
}
|
||||
try {
|
||||
const parsed = TagUtils.Tag(value)
|
||||
return parsed.asHumanString(true, false, {})
|
||||
} catch (e) {
|
||||
return new FixedUiElement(
|
||||
"Could not parse this tag: " +
|
||||
JSON.stringify(value) +
|
||||
" due to " +
|
||||
e
|
||||
).SetClass("alert")
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "direction_indicator",
|
||||
args: [],
|
||||
|
||||
docs: "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object",
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "direction_absolute",
|
||||
docs: "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'",
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "key",
|
||||
doc: "The attribute containing the degrees",
|
||||
defaultValue: "_direction:centerpoint",
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
doc: "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction",
|
||||
defaultValue: "0",
|
||||
},
|
||||
],
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
): BaseUIElement {
|
||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||
const offset = args[1] === "" ? 0 : Number(args[1])
|
||||
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => {
|
||||
console.log("Direction value", tags[key], key)
|
||||
return tags[key]
|
||||
})
|
||||
.mapD((value) => {
|
||||
const dir = GeoOperations.bearingToHuman(
|
||||
GeoOperations.parseBearing(value) + offset
|
||||
)
|
||||
console.log("Human dir", dir)
|
||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "preset_description",
|
||||
docs: "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>
|
||||
): BaseUIElement {
|
||||
const translation = tagSource.map((tags) => {
|
||||
const layer = state.theme.getMatchingLayer(tags)
|
||||
return layer?.getMostMatchingPreset(tags)?.description
|
||||
})
|
||||
return new VariableUiElement(translation)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "preset_type_select",
|
||||
docs: "An editable tag rendering which allows to change the type",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
const t = Translations.t.preset_type
|
||||
const question: QuestionableTagRenderingConfigJson = {
|
||||
id: layer.id + "-type",
|
||||
question: t.question.translations,
|
||||
mappings: layer.presets.map((pr) => ({
|
||||
if: new And(pr.tags).asJson(),
|
||||
icon: "auto",
|
||||
then: (pr.description ? t.typeDescription : t.typeTitle).Subs({
|
||||
title: pr.title,
|
||||
description: pr.description,
|
||||
}).translations,
|
||||
})),
|
||||
}
|
||||
const config = new TagRenderingConfig(question)
|
||||
return new SvelteUIElement(TagRenderingEditable, {
|
||||
config,
|
||||
tags,
|
||||
selectedElement,
|
||||
state,
|
||||
layer,
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue