Fix deployment, fix documentation generation, add a small markdown generator

This commit is contained in:
Pieter Vander Vennet 2021-06-15 00:28:59 +02:00
parent e480c97676
commit 8e72b70742
27 changed files with 478 additions and 399 deletions

View file

@ -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 => {

View file

@ -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 = `<h2>Calculating tags with Javascript</h2>
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:",
<p>In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. <b>lat</b>, <b>lon</b>, <b>_country</b>), as detailed above.</p>
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();
<p>It is also possible to calculate your own tags - but this requires some javascript knowledge. </p>
Before proceeding, some warnings:
<ul>
<li> DO NOT DO THIS AS BEGINNER</li>
<li> <b>Only do this if all other techniques fail</b>. This should <i>not</i> be done to create a rendering effect, only to calculate a specific value</li>
<li> <b>THIS MIGHT BE DISABLED WITHOUT ANY NOTICE ON UNOFFICIAL THEMES</b>. 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.</li>
</ul>
In the layer object, add a field <b>calculatedTags</b>, e.g.:
<div class="code">
"calculatedTags": [
"_someKey=javascript-expression",
"name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",
"_distanceCloserThen3Km=feat.distanceTo( some_lon, some_lat) < 3 ? 'yes' : 'no'"
]
</div>
The above code will be executed for every feature in the layer. The feature is accessible as <b>feat</b> and is an amended geojson object:
- <b>area</b> contains the surface area (in square meters) of the object
- <b>lat</b> and <b>lon</b> contain the latitude and longitude
Some advanced functions are available on <b>feat</b> 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 <code>{ feat: GeoJSONFeature, overlap: number}[]</code> where <code>overlap</code> is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or <code>undefined</code> 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 <b>feat</b> 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 <b>feat</b> as well:
private static readonly Memberships = new ExtraFunction(
"memberships",
"Gives a list of <code>{role: string, relation: Relation}</code>-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: <code>_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')</code>",
"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 <b>feat</b> 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,
"<ul>",
...ExtraFunction.allFuncs.map(func =>
new Combine([
"<li>", func._name, "</li>"
])
),
"</ul>",
...ExtraFunction.allFuncs.map(func =>
new Combine([
"<h3>" + func._name + "</h3>",
func._doc,
"<ul>",
...func._args.map(arg => "<li>" + arg + "</li>"),
"</ul>"
])
)
new List(ExtraFunction.allFuncs.map(func => func._name)),
...elems
]);
}

View file

@ -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([
"<h2>Metatags</h2>",
"<p>Metatags are extra tags available, in order to display more data or to give better questions.</p>",
"<p>The are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.</p>",
"<p><b>Hint:</b> when using metatags, add the <a href='URL_Parameters.md'>query parameter</a> <code>debug=true</code> to the URL. This will include a box in the popup for features which shows all the properties of the object</p>"
])
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([
"<h3>", metatag.keys.join(", "), "</h3>",
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 }[]) {

View file

@ -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<string> {
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<string>(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<string> {
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<string>(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");
}
}