From 8e72b70742cad39802c7957cf15608ae89d1339c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 15 Jun 2021 00:28:59 +0200 Subject: [PATCH] Fix deployment, fix documentation generation, add a small markdown generator --- Docs/CalculatedTags.md | 150 +++++++++++++----- Docs/SpecialRenderings.md | 62 +------- Docs/TagInfo/mapcomplete_fietsstraten.json | 24 +-- Docs/URL_Parameters.md | 172 +++++++++++---------- InitUiElements.ts | 12 +- Logic/Actors/GeoLocationHandler.ts | 1 - Logic/ExtraFunction.ts | 94 ++++++----- Logic/SimpleMetaTagger.ts | 51 +++--- Logic/Web/QueryParameters.ts | 100 +++++++----- State.ts | 21 ++- UI/Base/Combine.ts | 4 + UI/Base/FixedUiElement.ts | 4 + UI/Base/List.ts | 9 ++ UI/Base/Title.ts | 37 +++++ UI/BaseUIElement.ts | 20 ++- UI/BigComponents/PersonalLayersPanel.ts | 1 - UI/SpecialVisualizations.ts | 1 - UI/UIElement.ts | 21 --- Utils.ts | 1 - assets/contributors.json | 2 +- css/imageUploadFlow.css | 23 --- index.html | 1 - index.manifest | 2 - index.ts | 5 - package.json | 1 - scripts/generateDocs.ts | 50 ++++-- scripts/generateLayouts.ts | 8 +- 27 files changed, 478 insertions(+), 399 deletions(-) create mode 100644 UI/Base/Title.ts delete mode 100644 css/imageUploadFlow.css diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index 6347c09a8..f9e247674 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -1,5 +1,8 @@ -Metatags --------- + + Metatags +========== + + Metatags are extra tags available, in order to display more data or to give better questions. @@ -7,85 +10,154 @@ The are calculated automatically on every feature when the data arrives in the w **Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object -### \_lat, \_lon + + Metatags calculated by MapComplete +------------------------------------ + + + +The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme + + +### _lat, _lon + + The latitude and longitude of the point (or centerpoint in the case of a way/area) -### \_surface, \_surface:ha + +### _surface, _surface:ha + + The surface area of the feature, in square meters and in hectare. Not set on points and ways -### \_length, \_length:km -The total length of a feature in meters (and in kilometers, rounded to one decimal for '\_length:km'). For a surface, the length of the perimeter +### _length, _length:km + + + +The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter + + +### _country + -### \_country The country code of the property (with latlon2country) -### \_isOpen, \_isOpen:description -If 'opening\_hours' is present, it will add the current state of the feature (being 'yes' or 'no') +### _isOpen, _isOpen:description + + + +If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no') + + +### _width:needed, _width:needed:no_pedestrians, _width:difference + -### \_width:needed, \_width:needed:no\_pedestrians, \_width:difference Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present -### \_direction:numerical, \_direction:leftright -\_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). \_direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map +### _direction:numerical, _direction:leftright + + + +_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map + + +### _now:date, _now:datetime, _loaded:date, _loaded:_datetime + -### \_now:date, \_now:datetime, \_loaded:date, \_loaded:\_datetime Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely -### \_last\_edit:contributor, \_last\_edit:contributor:uid, \_last\_edit:changeset, \_last\_edit:timestamp, \_version\_number + +### _last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number + + Information about the last edit of this object. -Calculating tags with Javascript --------------------------------- -In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. **lat**, **lon**, **\_country**), as detailed above. + Calculating tags with Javascript +---------------------------------- + + + +In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. `lat`, `lon`, `_country`), as detailed above. It is also possible to calculate your own tags - but this requires some javascript knowledge. + + Before proceeding, some warnings: -* DO NOT DO THIS AS BEGINNER -* **Only do this if all other techniques fail**. This should _not_ be done to create a rendering effect, only to calculate a specific value -* **THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES**. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs. -In the layer object, add a field **calculatedTags**, e.g.: -"calculatedTags": \[ "\_someKey=javascript-expression", "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", "\_distanceCloserThen3Km=feat.distanceTo( some\_lon, some\_lat) < 3 ? 'yes' : 'no'" \] + - DO NOT DO THIS AS BEGINNER + - **Only do this if all other techniques fail** This should _not_ be done to create a rendering effect, only to calculate a specific value + - **THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES** As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs. -The above code will be executed for every feature in the layer. The feature is accessible as **feat** and is an amended geojson object: - **area** contains the surface area (in square meters) of the object - **lat** and **lon** contain the latitude and longitude Some advanced functions are available on **feat** as well: -* distanceTo -* overlapWith -* closest -* memberships +To enable this feature, add a field `calculatedTags` in the layer object, e.g.: -### distanceTo +```` -Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object +"calculatedTags": [ -* longitude -* latitude + "_someKey=javascript-expression", -### overlapWith + "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", -Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point + "_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" -* ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap) + ] -### closest +```` -Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. -* list of features -### memberships +The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object: + + + + - `area` contains the surface area (in square meters) of the object + - `lat` and `lon` contain the latitude and longitude + + +Some advanced functions are available on **feat** as well: + + - distanceTo + - overlapWith + - closest + - memberships + +### distanceTo + + Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object + + 0. longitude + 1. latitude + +### overlapWith + + Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point + + 0. ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap) + +### closest + + Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered. + + 0. list of features + +### memberships + + Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. + +For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')` + -Gives a list of `{role: string, relation: Relation}`\-objects, containing all the relations that this feature is part of. For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')` \ No newline at end of file diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index a0b77e323..6de976f17 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -1,61 +1 @@ -### Special tag renderings - -In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.General usage is **{func\_name()}** or **{func\_name(arg, someotherarg)}**. Note that you _do not_ need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args - -### all\_tags - -Prints all key-value pairs of the object - used for debugging - -**Example usage:** {all\_tags()} - -### image\_carousel - -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) - -1. **image key/prefix**: The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Default: image -2. **smart search**: Also include images given via 'Wikidata', 'wikimedia\_commons' and 'mapillary Default: true - -**Example usage:** {image\_carousel(image,true)} - -### image\_upload - -Creates a button where a user can upload an image to IMGUR - -1. **image-key**: Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added) Default: image - -**Example usage:** {image\_upload(image)} - -### reviews - -Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten - -1. **subjectKey**: The key to use to determine the subject. If specified, the subject will be **tags\[subjectKey\]** Default: name -2. **fallback**: The identifier to use, if _tags\[subjectKey\]_ as specified above is not available. This is effectively a fallback value - -**Example usage:** **{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**** - -### ****opening\_hours\_table**** - -****Creates an opening-hours table. Usage: {opening\_hours\_table(opening\_hours)} to create a table of the tag 'opening\_hours'. - -1. **key**: The tagkey from which the table is constructed. Default: opening\_hours - -**Example usage:** {opening\_hours\_table(opening\_hours)} - -### live - -Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json\[x\]\[y\]\[z\], other: json\[a\]\[b\]\[c\] out of it and will return 'other' or 'json\[a\]\[b\]\[c\]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed\_value)} - -1. **Url**: The URL to load -2. **Shorthands**: A list of shorthands, of the format 'shorthandname:path.path.path'. Seperated by ; -3. **path**: The path (or shorthand) that should be returned - -**Example usage:** {live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour\_cnt;day:data.day\_cnt;year:data.year\_cnt,hour)} - -### share\_link - -Creates a link that (attempts to) open the native 'share'-screen - -1. **url**: The url to share (default: current URL) - -**Example usage:** {share\_link()} to share the current page, {share\_link()} to share the given url**** \ No newline at end of file +

Special tag renderings

In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's. General usage is {func_name()} or {func_name(arg, someotherarg)}. Note that you do not need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args

all_tags

Prints all key-value pairs of the object - used for debugging
Example usage: {all_tags()}

image_carousel

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)
  1. image key/prefix: The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Default: image
  2. smart search: Also include images given via 'Wikidata', 'wikimedia_commons' and 'mapillary Default: true
Example usage: {image_carousel(image,true)}

image_upload

Creates a button where a user can upload an image to IMGUR
  1. image-key: Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added) Default: image
Example usage: {image_upload(image)}

reviews

Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten
  1. subjectKey: The key to use to determine the subject. If specified, the subject will be tags[subjectKey] Default: name
  2. fallback: The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value
Example usage: {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

opening_hours_table

Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.
  1. key: The tagkey from which the table is constructed. Default: opening_hours
Example usage: {opening_hours_table(opening_hours)}

live

Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}
  1. Url: The URL to load
  2. Shorthands: A list of shorthands, of the format 'shorthandname:path.path.path'. Seperated by ;
  3. path: The path (or shorthand) that should be returned
Example usage: {live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}

share_link

Creates a link that (attempts to) open the native 'share'-screen
  1. url: The url to share (default: current URL)
Example usage: {share_link()} to share the current page, {share_link()} to share the given url \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_fietsstraten.json b/Docs/TagInfo/mapcomplete_fietsstraten.json index e8b411615..b5b234baf 100644 --- a/Docs/TagInfo/mapcomplete_fietsstraten.json +++ b/Docs/TagInfo/mapcomplete_fietsstraten.json @@ -33,22 +33,22 @@ }, { "key": "cyclestreet", - "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "yes" }, { "key": "maxspeed", - "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "30" }, { "key": "overtaking:motor_vehicle", - "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "no" }, { "key": "proposed:cyclestreet", - "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", + "description": "Layer 'Cyclestreets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", "value": "" }, { @@ -113,22 +113,22 @@ }, { "key": "cyclestreet", - "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "yes" }, { "key": "maxspeed", - "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "30" }, { "key": "overtaking:motor_vehicle", - "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "no" }, { "key": "proposed:cyclestreet", - "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", + "description": "Layer 'Future cyclestreet' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", "value": "" }, { @@ -203,22 +203,22 @@ }, { "key": "cyclestreet", - "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "yes" }, { "key": "maxspeed", - "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "30" }, { "key": "overtaking:motor_vehicle", - "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", + "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets')", "value": "no" }, { "key": "proposed:cyclestreet", - "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a maxspeeld of 30km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", + "description": "Layer 'All streets' shows cyclestreet=yes&maxspeed=30&overtaking:motor_vehicle=no&proposed:cyclestreet= with a fixed text, namely 'This street is a cyclestreet (and has a speed limit of 30 km/h)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Cyclestreets') Picking this answer will delete the key proposed:cyclestreet.", "value": "" }, { diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index fa0e0aaea..3894dfb14 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -1,3 +1,4 @@ + URL-parameters and URL-hash ============================ @@ -18,125 +19,128 @@ the URL-parameters are stated in the part between the `?` and the `#`. There are Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. -custom-css (broken) ------------- -If specified, the custom css from the given link will be loaded additionaly - -test ------- -If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org -The default value is _false_ - -layout --------- -The layout to load into MapComplete - -userlayout ------------- -If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: - -- The hash of the URL contains a base64-encoded .json-file containing the theme definition -- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator -- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme - The default value is _false_ - -layer-control-toggle + layer-control-toggle ---------------------- -Whether or not the layer control is shown -The default value is _false_ -tab + Whether or not the layer control is shown The default value is _false_ + + + tab ----- -The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) -The default value is _0_ -z + The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ + + + z --- -The initial/current zoom level -The default value is set by the theme -lat + The initial/current zoom level The default value is _0_ + + + lat ----- -The initial/current latitude -The default value is set by the theme -lon + The initial/current latitude The default value is _0_ + + + lon ----- -The initial/current longitude of the app -The default value is set by the theme -fs-userbadge + The initial/current longitude of the app The default value is _0_ + + + fs-userbadge -------------- -Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. -The default value is _true_ -fs-search + Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_ + + + fs-search ----------- -Disables/Enables the search bar -The default value is _true_ -fs-layers + Disables/Enables the search bar The default value is _true_ + + + fs-layers ----------- -Disables/Enables the layer control -The default value is _true_ -fs-add-new + Disables/Enables the layer control The default value is _true_ + + + fs-add-new ------------ -Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) -The default value is _true_ -fs-welcome-message + Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_ + + + fs-welcome-message -------------------- -Disables/enables the help menu or welcome message -The default value is _true_ -fs-iframe + Disables/enables the help menu or welcome message The default value is _true_ + + + fs-iframe ----------- -Disables/Enables the iframe-popup -The default value is _false_ -fs-more-quests + Disables/Enables the iframe-popup The default value is _false_ + + + fs-more-quests ---------------- -Disables/Enables the 'More Quests'-tab in the welcome message -The default value is _true_ -fs-share-screen + Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ + + + fs-share-screen ----------------- -Disables/Enables the 'Share-screen'-tab in the welcome message -The default value is _true_ -fs-geolocation + Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ + + + fs-geolocation ---------------- -Disables/Enables the geolocation button -The default value is _true_ -fs-all-questions + Disables/Enables the geolocation button The default value is _true_ + + + fs-all-questions ------------------ -Always show all questions -The default value is _false_ -debug + Always show all questions The default value is _false_ + + + test +------ + + If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_ + + + debug ------- -If true, shows some extra debugging help such as all the available tags on every object -The default value is _false_ -backend + If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ + + + backend --------- -The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using osm-test -The default value is _osm_ -oauth_token -------------- -Used to complete the login -No default value set + The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ -background + + custom-css ------------ -The id of the background layer to start with -The default value is _OSM_ (overridden by the theme) -layer- --------------- -Wether or not layer with layer-id is shown -The default value is _true_ + If specified, the custom css from the given link will be loaded additionaly The default value is __ + + + background +------------ + + The id of the background layer to start with The default value is _osm_ + + + layer- +------------------ + + Wether or not the layer with id is shown The default value is _true_ \ No newline at end of file diff --git a/InitUiElements.ts b/InitUiElements.ts index 7858dba2a..75b5824a4 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -21,7 +21,6 @@ import * as L from "leaflet"; import Img from "./UI/Base/Img"; import UserDetails from "./Logic/Osm/OsmConnection"; import Attribution from "./UI/BigComponents/Attribution"; -import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import LayerResetter from "./Logic/Actors/LayerResetter"; import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs"; import LayerControlPanel from "./UI/BigComponents/LayerControlPanel"; @@ -40,6 +39,7 @@ import ContributorCount from "./Logic/ContributorCount"; import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import LayerConfig from "./Customizations/JSON/LayerConfig"; +import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; export class InitUiElements { @@ -348,9 +348,8 @@ export class InitUiElements { private static InitBaseMap() { State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.locationControl).availableEditorLayers; - State.state.backgroundLayer = QueryParameters.GetQueryParameter("background", - State.state.layoutToUse.data.defaultBackgroundId ?? AvailableBaseLayers.osmCarto.id, - "The id of the background layer to start with") + + State.state.backgroundLayer = State.state.backgroundLayerId .map((selectedId: string) => { const available = State.state.availableBackgroundLayers.data; for (const layer of available) { @@ -359,9 +358,8 @@ export class InitUiElements { } } return AvailableBaseLayers.osmCarto; - }, [], layer => layer.id); - - + }, [State.state.availableBackgroundLayers], layer => layer.id); + new LayerResetter( State.state.backgroundLayer, State.state.locationControl, State.state.availableBackgroundLayers, State.state.layoutToUse.map((layout: LayoutConfig) => layout.defaultBackgroundId)); diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 64545ee90..740fc77ce 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -76,7 +76,6 @@ export default class GeoLocationHandler extends UIElement { }, [this._hasLocation]) currentPointer.addCallbackAndRun(pointerClass => { self.SetClass(pointerClass); - self.Update() }) this._element = new VariableUiElement( this._hasLocation.map(hasLocation => { diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index 51f4ff9f9..b94f925d9 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -1,46 +1,48 @@ import {GeoOperations} from "./GeoOperations"; -import {UIElement} from "../UI/UIElement"; import Combine from "../UI/Base/Combine"; import {Relation} from "./Osm/ExtractRelations"; import State from "../State"; import {Utils} from "../Utils"; +import BaseUIElement from "../UI/BaseUIElement"; +import List from "../UI/Base/List"; +import Title from "../UI/Base/Title"; export class ExtraFunction { - static readonly intro = `

Calculating tags with Javascript

+ static readonly intro = new Combine([ + new Title("Calculating tags with Javascript", 2), + "In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. `lat`, `lon`, `_country`), as detailed above.", + "It is also possible to calculate your own tags - but this requires some javascript knowledge.", + "", + "Before proceeding, some warnings:", + new List([ + "DO NOT DO THIS AS BEGINNER", + "**Only do this if all other techniques fail** This should _not_ be done to create a rendering effect, only to calculate a specific value", + "**THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES** As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs." + ]), + "To enable this feature, add a field `calculatedTags` in the layer object, e.g.:", + "````", + "\"calculatedTags\": [", + " \"_someKey=javascript-expression\",", + " \"name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator\",", + " \"_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'\" ", + " ]", + "````", + "", + "The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object:", -

In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. lat, lon, _country), as detailed above.

+ new List([ + "`area` contains the surface area (in square meters) of the object", + "`lat` and `lon` contain the latitude and longitude" + ]), + "Some advanced functions are available on **feat** as well:" + ]).SetClass("flex-col").AsMarkdown(); -

It is also possible to calculate your own tags - but this requires some javascript knowledge.

-Before proceeding, some warnings: - -
    -
  • DO NOT DO THIS AS BEGINNER
  • -
  • Only do this if all other techniques fail. This should not be done to create a rendering effect, only to calculate a specific value
  • -
  • THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES. As unofficial themes might be loaded from the internet, this is the equivalent of injecting arbitrary code into the client. It'll be disabled if abuse occurs.
  • -
-In the layer object, add a field calculatedTags, e.g.: - -
- "calculatedTags": [ - "_someKey=javascript-expression", - "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", - "_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'" - ] -
- -The above code will be executed for every feature in the layer. The feature is accessible as feat and is an amended geojson object: -- area contains the surface area (in square meters) of the object -- lat and lon contain the latitude and longitude - -Some advanced functions are available on feat as well: - -` private static readonly OverlapFunc = new ExtraFunction( "overlapWith", - "Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is { feat: GeoJSONFeature, overlap: number}[] where overlap is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or undefined if the current feature is a point", + "Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point", ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"], (params, feat) => { return (...layerIds: string[]) => { @@ -72,7 +74,7 @@ Some advanced functions are available on feat as well: if (typeof arg0 === "string") { // This is an identifier const feature = State.state.allElements.ContainingFeatures.get(arg0); - if(feature === undefined){ + if (feature === undefined) { return undefined; } arg0 = feature; @@ -138,9 +140,9 @@ Some advanced functions are available on feat as well: private static readonly Memberships = new ExtraFunction( "memberships", - "Gives a list of {role: string, relation: Relation}-objects, containing all the relations that this feature is part of. " + + "Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " + "\n\n" + - "For example: _part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')", + "For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`", [], (params, _) => { return () => params.relations ?? []; @@ -167,25 +169,19 @@ Some advanced functions are available on feat as well: } } - public static HelpText(): UIElement { + public static HelpText(): BaseUIElement { + + const elems = [] + for (const func of ExtraFunction.allFuncs) { + elems.push(new Title(func._name, 3), + func._doc, + new List(func._args, true)) + } + return new Combine([ ExtraFunction.intro, - "
    ", - ...ExtraFunction.allFuncs.map(func => - new Combine([ - "
  • ", func._name, "
  • " - ]) - ), - "
", - ...ExtraFunction.allFuncs.map(func => - new Combine([ - "

" + func._name + "

", - func._doc, - "
    ", - ...func._args.map(arg => "
  • " + arg + "
  • "), - "
" - ]) - ) + new List(ExtraFunction.allFuncs.map(func => func._name)), + ...elems ]); } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index df40a9195..4d13af724 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -5,13 +5,15 @@ import {Tag} from "./Tags/Tag"; import {Or} from "./Tags/Or"; import {Utils} from "../Utils"; import opening_hours from "opening_hours"; -import {UIElement} from "../UI/UIElement"; import Combine from "../UI/Base/Combine"; +import BaseUIElement from "../UI/BaseUIElement"; +import Title from "../UI/Base/Title"; +import {FixedUiElement} from "../UI/Base/FixedUiElement"; const cardinalDirections = { - N: 0, NNE: 22.5, NE: 45, ENE: 67.5, - E: 90, ESE: 112.5, SE: 135, SSE: 157.5, + N: 0, NNE: 22.5, NE: 45, ENE: 67.5, + E: 90, ESE: 112.5, SE: 135, SSE: 157.5, S: 180, SSW: 202.5, SW: 225, WSW: 247.5, W: 270, WNW: 292.5, NW: 315, NNW: 337.5 } @@ -31,20 +33,20 @@ export default class SimpleMetaTagger { (feature) => {/*Note: also handled by 'UpdateTagsFromOsmAPI'*/ const tgs = feature.properties; - - function move(src: string, target: string){ - if(tgs[src] === undefined){ + + function move(src: string, target: string) { + if (tgs[src] === undefined) { return; } tgs[target] = tgs[src] delete tgs[src] } - - move("user","_last_edit:contributor") - move("uid","_last_edit:contributor:uid") - move("changeset","_last_edit:changeset") - move("timestamp","_last_edit:timestamp") - move("version","_version_number") + + move("user", "_last_edit:contributor") + move("uid", "_last_edit:contributor:uid") + move("changeset", "_last_edit:changeset") + move("timestamp", "_last_edit:timestamp") + move("version", "_version_number") } ) private static latlon = new SimpleMetaTagger({ @@ -375,28 +377,27 @@ export default class SimpleMetaTagger { SimpleMetaTagger.coder?.GetCountryCodeFor(lon, lat, callback) } - static HelpText(): UIElement { - const subElements: UIElement[] = [ + static HelpText(): BaseUIElement { + const subElements: (string | BaseUIElement)[] = [ new Combine([ - "

Metatags

", - "

Metatags are extra tags available, in order to display more data or to give better questions.

", - "

The are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.

", - "

Hint: when using metatags, add the query parameter debug=true to the URL. This will include a box in the popup for features which shows all the properties of the object

" - ]) - + new Title("Metatags", 1), + "Metatags are extra tags available, in order to display more data or to give better questions.", + "The are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.", + "**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object" + ]).SetClass("flex-col") ]; + subElements.push(new Title("Metatags calculated by MapComplete", 2)) + subElements.push(new FixedUiElement("The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme")) for (const metatag of SimpleMetaTagger.metatags) { subElements.push( - new Combine([ - "

", metatag.keys.join(", "), "

", - metatag.doc] - ) + new Title(metatag.keys.join(", "), 3), + metatag.doc ) } - return new Combine(subElements) + return new Combine(subElements).SetClass("flex-col") } addMetaTags(features: { feature: any, freshness: Date }[]) { diff --git a/Logic/Web/QueryParameters.ts b/Logic/Web/QueryParameters.ts index d08df0fdf..a7bca6e8d 100644 --- a/Logic/Web/QueryParameters.ts +++ b/Logic/Web/QueryParameters.ts @@ -3,6 +3,9 @@ */ import {UIEventSource} from "../UIEventSource"; import Hash from "./Hash"; +import {Utils} from "../../Utils"; +import Title from "../../UI/Base/Title"; +import Combine from "../../UI/Base/Combine"; export class QueryParameters { @@ -12,6 +15,58 @@ export class QueryParameters { private static defaults = {} private static documentation = {} + private static QueryParamDocsIntro = "\n" + + "URL-parameters and URL-hash\n" + + "============================\n" + + "\n" + + "This document gives an overview of which URL-parameters can be used to influence MapComplete.\n" + + "\n" + + "What is a URL parameter?\n" + + "------------------------\n" + + "\n" + + "URL-parameters are extra parts of the URL used to set the state.\n" + + "\n" + + "For example, if the url is `https://mapcomplete.osm.be/cyclofix?lat=51.0&lon=4.3&z=5&test=true#node/1234`,\n" + + "the URL-parameters are stated in the part between the `?` and the `#`. There are multiple, all seperated by `&`, namely:\n" + + "\n" + + "- The url-parameter `lat` is `51.0` in this instance\n" + + "- The url-parameter `lon` is `4.3` in this instance\n" + + "- The url-parameter `z` is `5` in this instance\n" + + "- The url-parameter `test` is `true` in this instance\n" + + "\n" + + "Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case." + + public static GetQueryParameter(key: string, deflt: string, documentation?: string): UIEventSource { + if (!this.initialized) { + this.init(); + } + QueryParameters.documentation[key] = documentation; + if (deflt !== undefined) { + QueryParameters.defaults[key] = deflt; + } + if (QueryParameters.knownSources[key] !== undefined) { + return QueryParameters.knownSources[key]; + } + QueryParameters.addOrder(key); + const source = new UIEventSource(deflt, "&" + key); + QueryParameters.knownSources[key] = source; + source.addCallback(() => QueryParameters.Serialize()) + return source; + } + + public static GenerateQueryParameterDocs(): string { + const docs = [QueryParameters.QueryParamDocsIntro]; + for (const key in QueryParameters.documentation) { + const c = new Combine([ + new Title(key, 2), + QueryParameters.documentation[key], + QueryParameters.defaults[key] === undefined ? "No default value set" : `The default value is _${QueryParameters.defaults[key]}_` + + ]) + docs.push(c.AsMarkdown()) + } + return docs.join("\n\n"); + } private static addOrder(key) { if (this.order.indexOf(key) < 0) { @@ -25,7 +80,11 @@ export class QueryParameters { return; } this.initialized = true; - + + if (Utils.runningFromConsole) { + return; + } + if (window?.location?.search) { const params = window.location.search.substr(1).split("&"); for (const param of params) { @@ -38,7 +97,7 @@ export class QueryParameters { QueryParameters.knownSources[key] = source; } } - + window["mapcomplete_query_parameter_overview"] = () => { console.log(QueryParameters.GenerateQueryParameterDocs()) } @@ -50,7 +109,7 @@ export class QueryParameters { if (QueryParameters.knownSources[key]?.data === undefined) { continue; } - + if (QueryParameters.knownSources[key].data === "undefined") { continue; } @@ -62,41 +121,8 @@ export class QueryParameters { parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data)) } // Don't pollute the history every time a parameter changes - + history.replaceState(null, "", "?" + parts.join("&") + Hash.Current()); } - - public static GetQueryParameter(key: string, deflt: string, documentation?: string): UIEventSource { - if(!this.initialized){ - this.init(); - } - QueryParameters.documentation[key] = documentation; - if (deflt !== undefined) { - QueryParameters.defaults[key] = deflt; - } - if (QueryParameters.knownSources[key] !== undefined) { - return QueryParameters.knownSources[key]; - } - QueryParameters.addOrder(key); - const source = new UIEventSource(deflt, "&"+key); - QueryParameters.knownSources[key] = source; - source.addCallback(() => QueryParameters.Serialize()) - return source; - } - - public static GenerateQueryParameterDocs(): string { - const docs = []; - for (const key in QueryParameters.documentation) { - docs.push([ - " "+key+" ", - "-".repeat(key.length + 2), - QueryParameters.documentation[key], - QueryParameters.defaults[key] === undefined ? "No default value set" : `The default value is _${QueryParameters.defaults[key]}_` - - ].join("\n")) - } - return docs.join("\n\n"); - } - } \ No newline at end of file diff --git a/State.ts b/State.ts index 786c51c28..18fc3f972 100644 --- a/State.ts +++ b/State.ts @@ -102,6 +102,8 @@ export default class State { */ public readonly locationControl = new UIEventSource(undefined); public backgroundLayer; + public readonly backgroundLayerId: UIEventSource; + /* Last location where a click was registered */ public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) @@ -123,7 +125,7 @@ export default class State { public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map( str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n ); - + constructor(layoutToUse: LayoutConfig) { const self = this; @@ -210,8 +212,25 @@ export default class State { "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'") } + { + // Some other feature switches + const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly"); + if (customCssQP.data !== undefined && customCssQP.data !== "") { + Utils.LoadCustomCss(customCssQP.data); + } + this.backgroundLayerId = QueryParameters.GetQueryParameter("background", + layoutToUse.defaultBackgroundId ?? "osm", + "The id of the background layer to start with") + + } + + + if(Utils.runningFromConsole){ + return; + } + this.osmConnection = new OsmConnection( this.featureSwitchIsTesting.data, QueryParameters.GetQueryParameter("oauth_token", undefined, diff --git a/UI/Base/Combine.ts b/UI/Base/Combine.ts index 9bf1cf950..79d4a8f2f 100644 --- a/UI/Base/Combine.ts +++ b/UI/Base/Combine.ts @@ -32,4 +32,8 @@ export default class Combine extends BaseUIElement { return el; } + AsMarkdown(): string { + return this.uiElements.map(el => el.AsMarkdown()).join(this.HasClass("flex-col") ? "\n\n" : " "); + } + } \ No newline at end of file diff --git a/UI/Base/FixedUiElement.ts b/UI/Base/FixedUiElement.ts index a65b80e6f..b0552caac 100644 --- a/UI/Base/FixedUiElement.ts +++ b/UI/Base/FixedUiElement.ts @@ -17,5 +17,9 @@ export class FixedUiElement extends BaseUIElement { e.innerHTML = this._html return e; } + + AsMarkdown(): string { + return this._html; + } } \ No newline at end of file diff --git a/UI/Base/List.ts b/UI/Base/List.ts index d7b45a399..9a9bd8776 100644 --- a/UI/Base/List.ts +++ b/UI/Base/List.ts @@ -30,5 +30,14 @@ export default class List extends BaseUIElement { return el; } + + AsMarkdown(): string { + if(this._ordered){ + return "\n\n"+this.uiElements.map((el, i) => " "+i+". "+el.AsMarkdown().replace(/\n/g, ' \n') ).join("\n") + "\n" + }else{ + return "\n\n"+this.uiElements.map(el => " - "+el.AsMarkdown().replace(/\n/g, ' \n') ).join("\n")+"\n" + + } + } } \ No newline at end of file diff --git a/UI/Base/Title.ts b/UI/Base/Title.ts new file mode 100644 index 000000000..2fd3b069d --- /dev/null +++ b/UI/Base/Title.ts @@ -0,0 +1,37 @@ +import {UIElement} from "../UIElement"; +import BaseUIElement from "../BaseUIElement"; +import Translations from "../i18n/Translations"; + +export default class Title extends BaseUIElement{ + private readonly _embedded: BaseUIElement; + private readonly _level: number; + constructor(embedded: string | BaseUIElement, level: number =3 ) { + super() + this._embedded = Translations.W(embedded); + this._level = level; + } + + protected InnerConstructElement(): HTMLElement { + const el = this._embedded.ConstructElement() + if(el === undefined){ + return undefined; + } + const h = document.createElement("h"+this._level) + h.appendChild(el) + return h; + } + + AsMarkdown(): string { + const embedded = " " +this._embedded.AsMarkdown()+" "; + + if(this._level == 1){ + return "\n"+embedded+"\n"+"=".repeat(embedded.length)+"\n\n" + } + + if(this._level == 2){ + return "\n"+embedded+"\n"+"-".repeat(embedded.length)+"\n\n" + } + + return "\n"+"#".repeat( this._level)+embedded +"\n\n"; + } +} \ No newline at end of file diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 840814530..8bafff884 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -26,17 +26,7 @@ export default abstract class BaseUIElement { } return this; } - - public IsHovered(): UIEventSource { - if (this._onHover !== undefined) { - return this._onHover; - } - // Note: we just save it. 'Update' will register that an eventsource exist and install the necessary hooks - this._onHover = new UIEventSource(false); - return this._onHover; - } - - + AttachTo(divId: string) { let element = document.getElementById(divId); if (element === null) { @@ -84,6 +74,10 @@ export default abstract class BaseUIElement { } return this; } + + public HasClass(clss: string): boolean{ + return this.clss.has(clss) + } public SetStyle(style: string): BaseUIElement { this.style = style; @@ -156,4 +150,8 @@ export default abstract class BaseUIElement { return el } + + public AsMarkdown(): string{ + throw "AsMarkdown is not implemented by "+this.constructor.name + } } \ No newline at end of file diff --git a/UI/BigComponents/PersonalLayersPanel.ts b/UI/BigComponents/PersonalLayersPanel.ts index 2bf2f800e..51438c759 100644 --- a/UI/BigComponents/PersonalLayersPanel.ts +++ b/UI/BigComponents/PersonalLayersPanel.ts @@ -23,7 +23,6 @@ export default class PersonalLayersPanel extends UIElement { const self = this; State.state.installedThemes.addCallback(extraThemes => { self.UpdateView(extraThemes.map(layout => layout.layout)); - self.Update(); }) } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 2bcae1027..5ee4591c5 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -216,7 +216,6 @@ export default class SpecialVisualizations { "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.", "General usage is {func_name()} or {func_name(arg, someotherarg)}. Note that you do not need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args", ...helpTexts - ] ); } diff --git a/UI/UIElement.ts b/UI/UIElement.ts index 83b5f6752..565af7830 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -33,27 +33,6 @@ export abstract class UIElement extends BaseUIElement{ return this; } - - - Update(): void { - - } - - Render(): string { - return this.InnerRenderAsString() - } - - - public InnerRenderAsString(): string { - let rendered = this.InnerRender(); - if (typeof rendered !== "string") { - let html = rendered.ConstructElement() - return html.innerHTML - } - return rendered - } - - /** * Should be overridden for specific HTML functionality */ diff --git a/Utils.ts b/Utils.ts index b8ae81e7e..dc1d416a4 100644 --- a/Utils.ts +++ b/Utils.ts @@ -159,7 +159,6 @@ export class Utils { return txt; } - // Date will be undefined on failure public static LoadCustomCss(location: string) { const head = document.getElementsByTagName('head')[0]; const link = document.createElement('link'); diff --git a/assets/contributors.json b/assets/contributors.json index eda3444b7..02c30c2ce 100644 --- a/assets/contributors.json +++ b/assets/contributors.json @@ -1 +1 @@ -{"contributors":[{"contributor":"Pieter Vander Vennet", "commits":714},{"contributor":"pietervdvn", "commits":650},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"Weblate", "commits":14},{"contributor":"Marco", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Joost", "commits":11},{"contributor":"Midgard", "commits":8},{"contributor":"Jacque Fresco", "commits":8},{"contributor":"Artem", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"Mateusz Konieczny", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"Hosted Weblate", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"Wiktor Przybylski", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jan Zabel", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Vinicius", "commits":1},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file +{"contributors":[{"contributor":"Pieter Vander Vennet", "commits":738},{"contributor":"pietervdvn", "commits":718},{"contributor":"Weblate", "commits":35},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":16},{"contributor":"Marco", "commits":16},{"contributor":"Joost", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Artem", "commits":12},{"contributor":"Supaplex", "commits":9},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"Allan Nordhøy", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Hosted Weblate", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Wiktor Przybylski", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file diff --git a/css/imageUploadFlow.css b/css/imageUploadFlow.css deleted file mode 100644 index c36b33b64..000000000 --- a/css/imageUploadFlow.css +++ /dev/null @@ -1,23 +0,0 @@ -.image-upload-flow-button span { - width: max-content; - font-size: 28px; - font-weight: bold; - margin-top: 4px; - padding-top: 4px; - padding-bottom: 4px; - padding-left: 13px; -} - -.image-upload-flow-button { - display: flex; - cursor: pointer; - padding: 0.5em; - border-radius: 1em; - border: 3px solid var(--foreground-color); - box-sizing: border-box; -} - -.image-upload-flow svg { - fill: var(--foreground-color); - stroke: var(--foreground-color); -} diff --git a/index.html b/index.html index 9330ff148..1f9df50a6 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,6 @@ - diff --git a/index.manifest b/index.manifest index 1eb1aec3c..9b311e900 100644 --- a/index.manifest +++ b/index.manifest @@ -1,10 +1,8 @@ { "name": "index", - "short_name": "MapComplete", "start_url": "index.html", "display": "standalone", "background_color": "#fff", - "description": "A thematic map viewer and editor based on OpenStreetMap", "orientation": "portrait-primary, landscape-primary", "icons": [ { diff --git a/index.ts b/index.ts index e787b7649..79a81b2ea 100644 --- a/index.ts +++ b/index.ts @@ -33,10 +33,6 @@ if (location.href.indexOf("buurtnatuur.be") >= 0) { defaultLayout = "buurtnatuur" } -const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly"); -if (customCssQP.data !== undefined && customCssQP.data !== "") { - Utils.LoadCustomCss(customCssQP.data); -} let testing: UIEventSource; @@ -87,7 +83,6 @@ if (layoutToUse?.id === "cyclofix") { const layoutFromBase64 = decodeURIComponent(userLayoutParam.data); -document.getElementById('centermessage').innerText = 'Initilai'; new Combine(["Initializing...
", new FixedUiElement("If this message persist, something went wrong - click here to try again") diff --git a/package.json b/package.json index 80b07d36e..b62ffd445 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "ts-node": "^9.0.0", "ts-node-dev": "^1.0.0-pre.63", "tslint-no-circular-imports": "^0.7.0", - "turndown": "^7.0.0", "typescript": "^3.9.7", "write-file": "^1.0.0" } diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index b9b2553b2..c76cb1583 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -1,25 +1,53 @@ import {Utils} from "../Utils"; + Utils.runningFromConsole = true; import SpecialVisualizations from "../UI/SpecialVisualizations"; -import {writeFileSync} from "fs"; -import {UIElement} from "../UI/UIElement"; import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; import Combine from "../UI/Base/Combine"; import {ExtraFunction} from "../Logic/ExtraFunction"; import ValidatedTextField from "../UI/Input/ValidatedTextField"; +import BaseUIElement from "../UI/BaseUIElement"; +import Translations from "../UI/i18n/Translations"; +import {writeFileSync} from "fs"; +import LayoutConfig from "../Customizations/JSON/LayoutConfig"; +import State from "../State"; +import {QueryParameters} from "../Logic/Web/QueryParameters"; - - -const TurndownService = require('turndown') - -function WriteFile(filename, html: UIElement) : void { - const md = new TurndownService().turndown(html.InnerRenderAsString()); - writeFileSync(filename, md); +function WriteFile(filename, html: string | BaseUIElement): void { + writeFileSync(filename, Translations.W(html).AsMarkdown()); } WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage) -WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()])) -writeFileSync("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText()); +WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()]).SetClass("flex-col")) +WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText()); + + +new State(new LayoutConfig({ + language: ["en"], + id: "", + maintainer: "pietervdvn", + version: "0", + title: "", + description: "A theme to generate docs with", + startLat: 0, + startLon: 0, + startZoom: 0, + icon: undefined, + layers: [ + { + name: "", + id: "", + source: { + osmTags: "id~*" + } + } + ] + +})) +QueryParameters.GetQueryParameter("layer-", "true", "Wether or not the layer with id is shown") + +WriteFile("./Docs/URL_Parameters.md", QueryParameters.GenerateQueryParameterDocs()) + console.log("Generated docs") diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts index dcfadc3da..8c1a365d5 100644 --- a/scripts/generateLayouts.ts +++ b/scripts/generateLayouts.ts @@ -88,8 +88,8 @@ async function createManifest(layout: LayoutConfig) { console.log(icon) throw "Icon is not an svg for " + layout.id } - const ogTitle = Translations.W(layout.title).InnerRenderAsString(); - const ogDescr = Translations.W(layout.description ?? "").InnerRenderAsString(); + const ogTitle = Translations.W(layout.title).ConstructElement()?.innerText; + const ogDescr = Translations.W(layout.description ?? "").ConstructElement()?.innerText; return { name: name, @@ -109,8 +109,8 @@ async function createLandingPage(layout: LayoutConfig, manifest) { Locale.language.setData(layout.language[0]); - const ogTitle = Translations.W(layout.title)?.InnerRenderAsString(); - const ogDescr = Translations.W(layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap")?.InnerRenderAsString(); + const ogTitle = Translations.W(layout.title)?.ConstructElement()?.innerText; + const ogDescr = Translations.W(layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap")?.ConstructElement()?.innerText; const ogImage = layout.socialImage; let customCss = "";