Huge refactorings of JSON-parsing and Tagsfilter, other cleanups, warning cleanups and lots of small subtle bugfixes
This commit is contained in:
parent
9a5b35b9f3
commit
a57b7d93fa
113 changed files with 1565 additions and 2594 deletions
|
@ -1,306 +0,0 @@
|
|||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
import {LayerDefinition, Preset} from "../LayerDefinition";
|
||||
import {Layout} from "../Layout";
|
||||
import Translation from "../../UI/i18n/Translation";
|
||||
import Combine from "../../UI/Base/Combine";
|
||||
import {And, Tag} from "../../Logic/TagsFilter";
|
||||
import FixedText from "../Questions/FixedText";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {TagDependantUIElementConstructor} from "../UIElementConstructor";
|
||||
import {Map} from "../Layers/Map";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
|
||||
|
||||
export interface TagRenderingConfigJson {
|
||||
// If this key is present, then...
|
||||
key?: string,
|
||||
// Use this string to render
|
||||
render?: string | any,
|
||||
// One of string, int, nat, float, pfloat, email, phone. Default: string
|
||||
type?: string,
|
||||
// If it is not known (and no mapping below matches), this question is asked; a textfield is inserted in the rendering above
|
||||
question?: string | any,
|
||||
// If a value is added with the textfield, this extra tag is addded. Optional field
|
||||
addExtraTags?: string | { k: string, v: string }[];
|
||||
// Extra tags: rendering is only shown/asked if these tags are present
|
||||
condition?: string;
|
||||
// Alternatively, these tags are shown if they match - even if the key above is not there
|
||||
// If unknown, these become a radio button
|
||||
mappings?:
|
||||
{
|
||||
if: string,
|
||||
then: string | any
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface LayerConfigJson {
|
||||
name: string;
|
||||
title: string | any | TagRenderingConfigJson;
|
||||
description: string | any;
|
||||
minzoom: number | string,
|
||||
icon?: TagRenderingConfigJson;
|
||||
color?: TagRenderingConfigJson;
|
||||
width?: TagRenderingConfigJson;
|
||||
overpassTags: string | { k: string, v: string }[];
|
||||
wayHandling?: number,
|
||||
widenFactor?: number,
|
||||
presets: {
|
||||
tags: string,
|
||||
title: string | any,
|
||||
description?: string | any,
|
||||
icon?: string
|
||||
}[],
|
||||
tagRenderings: TagRenderingConfigJson []
|
||||
}
|
||||
|
||||
export interface LayoutConfigJson {
|
||||
widenFactor?: number;
|
||||
name: string;
|
||||
title: string | any;
|
||||
description: string | any;
|
||||
maintainer: string;
|
||||
language: string | string[];
|
||||
layers: LayerConfigJson[],
|
||||
startZoom: string | number;
|
||||
startLat: string | number;
|
||||
startLon: string | number;
|
||||
/**
|
||||
* Either a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64,'
|
||||
*/
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export class CustomLayoutFromJSON {
|
||||
|
||||
|
||||
public static FromQueryParam(layoutFromBase64: string): Layout {
|
||||
return CustomLayoutFromJSON.LayoutFromJSON(JSON.parse(atob(layoutFromBase64)));
|
||||
}
|
||||
|
||||
public static TagRenderingFromJson(json: TagRenderingConfigJson): TagDependantUIElementConstructor {
|
||||
|
||||
if(json === undefined){
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof (json) === "string") {
|
||||
return new FixedText(json);
|
||||
}
|
||||
|
||||
let freeform = undefined;
|
||||
if (json.render !== undefined) {
|
||||
const type = json.type ?? "text";
|
||||
let renderTemplate = CustomLayoutFromJSON.MaybeTranslation(json.render);;
|
||||
const template = renderTemplate.replace("{" + json.key + "}", "$" + type + "$");
|
||||
if(type === "url"){
|
||||
renderTemplate = json.render.replace("{" + json.key + "}",
|
||||
`<a href='{${json.key}}' target='_blank'>{${json.key}}</a>`
|
||||
);
|
||||
}
|
||||
|
||||
freeform = {
|
||||
key: json.key,
|
||||
template: template,
|
||||
renderTemplate: renderTemplate,
|
||||
extraTags: CustomLayoutFromJSON.TagsFromJson(json.addExtraTags),
|
||||
}
|
||||
if (freeform.key === "*") {
|
||||
freeform.key = "id"; // Id is always there -> always take the rendering. Used for 'icon' and 'stroke'
|
||||
}
|
||||
}
|
||||
|
||||
let mappings = undefined;
|
||||
if (json.mappings !== undefined) {
|
||||
mappings = [];
|
||||
for (const mapping of json.mappings) {
|
||||
mappings.push({
|
||||
k: new And(CustomLayoutFromJSON.TagsFromJson(mapping.if)),
|
||||
txt: CustomLayoutFromJSON.MaybeTranslation(mapping.then)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const rendering = new TagRenderingOptions({
|
||||
question: CustomLayoutFromJSON.MaybeTranslation(json.question),
|
||||
freeform: freeform,
|
||||
mappings: mappings
|
||||
});
|
||||
|
||||
if (json.condition) {
|
||||
const conditionTags: Tag[] = CustomLayoutFromJSON.TagsFromJson(json.condition);
|
||||
return rendering.OnlyShowIf(new And(conditionTags));
|
||||
}
|
||||
return rendering;
|
||||
}
|
||||
|
||||
private static PresetFromJson(layout: any, preset: any): Preset {
|
||||
const t = CustomLayoutFromJSON.MaybeTranslation;
|
||||
const tags = CustomLayoutFromJSON.TagsFromJson;
|
||||
return {
|
||||
icon: preset.icon ?? CustomLayoutFromJSON.TagRenderingFromJson(layout.icon),
|
||||
tags: tags(preset.tags) ?? tags(layout.overpassTags),
|
||||
title: t(preset.title) ?? t(layout.title),
|
||||
description: t(preset.description) ?? t(layout.description)
|
||||
}
|
||||
}
|
||||
|
||||
private static StyleFromJson(layout: LayerConfigJson): ((tags: any) => {
|
||||
color: string,
|
||||
weight?: number,
|
||||
icon: {
|
||||
iconUrl: string,
|
||||
iconSize: number[],
|
||||
},
|
||||
}) {
|
||||
const iconRendering: TagDependantUIElementConstructor = CustomLayoutFromJSON.TagRenderingFromJson(layout.icon);
|
||||
const colourRendering = CustomLayoutFromJSON.TagRenderingFromJson(layout.color);
|
||||
let thickness = CustomLayoutFromJSON.TagRenderingFromJson(layout.width);
|
||||
|
||||
|
||||
return (tags) => {
|
||||
const iconUrl = iconRendering.GetContent(tags);
|
||||
const stroke = colourRendering.GetContent(tags) ?? "#00f";
|
||||
let weight = parseInt(thickness?.GetContent(tags)) ?? 10;
|
||||
if(isNaN(weight)){
|
||||
weight = 10;
|
||||
}
|
||||
return {
|
||||
color: stroke,
|
||||
weight: weight,
|
||||
icon: {
|
||||
iconUrl: iconUrl,
|
||||
iconSize: [40, 40],
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static TagFromJson(json: string | { k: string, v: string }): Tag {
|
||||
if (json === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof (json) !== "string") {
|
||||
return new Tag(json.k.trim(), json.v.trim())
|
||||
}
|
||||
|
||||
let kv: string[] = undefined;
|
||||
let invert = false;
|
||||
let regex = false;
|
||||
if (json.indexOf("!=") >= 0) {
|
||||
kv = json.split("!=");
|
||||
invert = true;
|
||||
} else if (json.indexOf("~=") >= 0) {
|
||||
kv = json.split("~=");
|
||||
regex = true;
|
||||
} else {
|
||||
kv = json.split("=");
|
||||
}
|
||||
|
||||
if (kv.length !== 2) {
|
||||
return undefined;
|
||||
}
|
||||
if (kv[0].trim() === "") {
|
||||
return undefined;
|
||||
}
|
||||
let v = kv[1].trim();
|
||||
if(v.startsWith("/") && v.endsWith("/")){
|
||||
v = v.substr(1, v.length - 2);
|
||||
regex = true;
|
||||
}
|
||||
return new Tag(kv[0].trim(), regex ? new RegExp(v): v, invert);
|
||||
}
|
||||
|
||||
public static TagsFromJson(json: string | { k: string, v: string }[]): Tag[] {
|
||||
if (json === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (json === "") {
|
||||
return [];
|
||||
}
|
||||
let tags = [];
|
||||
if (typeof (json) === "string") {
|
||||
tags = json.split("&").map(CustomLayoutFromJSON.TagFromJson);
|
||||
} else {
|
||||
tags = json.map(x => {CustomLayoutFromJSON.TagFromJson(x)});
|
||||
}
|
||||
for (const tag of tags) {
|
||||
if (tag === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static LayerFromJson(json: LayerConfigJson): LayerDefinition {
|
||||
const t = CustomLayoutFromJSON.MaybeTranslation;
|
||||
const tr = CustomLayoutFromJSON.TagRenderingFromJson;
|
||||
const tags = CustomLayoutFromJSON.TagsFromJson(json.overpassTags);
|
||||
// We run the icon rendering with the bare minimum of tags (the overpass tags) to get the actual icon
|
||||
const icon = CustomLayoutFromJSON.TagRenderingFromJson(json.icon).GetContent({id:"node/-1"});
|
||||
|
||||
// @ts-ignore
|
||||
const id = json.name?.replace(/[^a-zA-Z0-9_-]/g,'') ?? json.id;
|
||||
return new LayerDefinition(
|
||||
id,
|
||||
{
|
||||
description: t(json.description),
|
||||
name: Translations.WT(t(json.name)),
|
||||
icon: icon,
|
||||
minzoom: parseInt(""+json.minzoom),
|
||||
title: tr(json.title),
|
||||
presets: json.presets.map((preset) => {
|
||||
return CustomLayoutFromJSON.PresetFromJson(json, preset)
|
||||
}),
|
||||
elementsToShow:
|
||||
[new ImageCarouselWithUploadConstructor()].concat(json.tagRenderings.map(tr)),
|
||||
overpassFilter: new And(tags),
|
||||
wayHandling: parseInt(""+json.wayHandling) ?? LayerDefinition.WAYHANDLING_CENTER_AND_WAY,
|
||||
maxAllowedOverlapPercentage: 0,
|
||||
style: CustomLayoutFromJSON.StyleFromJson(json)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private static MaybeTranslation(json: any): Translation | string {
|
||||
if (json === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof (json) === "string") {
|
||||
return json;
|
||||
}
|
||||
return new Translation(json);
|
||||
}
|
||||
|
||||
public static LayoutFromJSON(json: LayoutConfigJson) {
|
||||
const t = CustomLayoutFromJSON.MaybeTranslation;
|
||||
let languages : string[] ;
|
||||
if(typeof (json.language) === "string"){
|
||||
languages = [json.language];
|
||||
}else{
|
||||
languages = json.language
|
||||
}
|
||||
const layout = new Layout(json.name,
|
||||
languages,
|
||||
t(json.title),
|
||||
json.layers.map(CustomLayoutFromJSON.LayerFromJson),
|
||||
parseInt(""+json.startZoom),
|
||||
parseFloat(""+json.startLat),
|
||||
parseFloat(""+json.startLon),
|
||||
new Combine(['<h3>', t(json.title), '</h3><br/>', t(json.description)])
|
||||
);
|
||||
layout.icon = json.icon;
|
||||
layout.maintainer = json.maintainer;
|
||||
layout.widenFactor = parseFloat(""+json.widenFactor) ?? 0.03;
|
||||
if(isNaN(layout.widenFactor)){
|
||||
layout.widenFactor = 0.03;
|
||||
}
|
||||
if (layout.widenFactor > 0.1) {
|
||||
layout.widenFactor = 0.1;
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
}
|
259
Customizations/JSON/FromJSON.ts
Normal file
259
Customizations/JSON/FromJSON.ts
Normal file
|
@ -0,0 +1,259 @@
|
|||
import {Layout} from "../Layout";
|
||||
import {LayoutConfigJson} from "./LayoutConfigJson";
|
||||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||
import {And, RegexTag, Tag, TagsFilter} from "../../Logic/Tags";
|
||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
import Translation from "../../UI/i18n/Translation";
|
||||
import {LayerConfigJson} from "./LayerConfigJson";
|
||||
import {LayerDefinition, Preset} from "../LayerDefinition";
|
||||
import {TagDependantUIElementConstructor} from "../UIElementConstructor";
|
||||
import FixedText from "../Questions/FixedText";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
import Combine from "../../UI/Base/Combine";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {ImageCarouselConstructor} from "../../UI/Image/ImageCarousel";
|
||||
|
||||
|
||||
export class FromJSON {
|
||||
|
||||
|
||||
public static FromBase64(layoutFromBase64: string): Layout {
|
||||
return FromJSON.LayoutFromJSON(JSON.parse(atob(layoutFromBase64)));
|
||||
}
|
||||
|
||||
public static LayoutFromJSON(json: LayoutConfigJson): Layout {
|
||||
console.log("Parsing ", json.id)
|
||||
const tr = FromJSON.Translation;
|
||||
|
||||
const layers = json.layers.map(FromJSON.Layer);
|
||||
const roaming: TagDependantUIElementConstructor[] = json.roamingRenderings?.map(FromJSON.TagRendering) ?? [];
|
||||
for (const layer of layers) {
|
||||
layer.elementsToShow.push(...roaming);
|
||||
}
|
||||
|
||||
const layout = new Layout(
|
||||
json.id,
|
||||
typeof (json.language) === "string" ? [json.language] : json.language,
|
||||
tr(json.title),
|
||||
layers,
|
||||
json.startZoom,
|
||||
json.startLat,
|
||||
json.startLon,
|
||||
new Combine(["<h3>", tr(json.title), "</h3>", tr(json.description)]),
|
||||
);
|
||||
|
||||
layout.widenFactor = json.widenFactor ?? 0.07;
|
||||
layout.icon = json.icon;
|
||||
layout.maintainer = json.maintainer;
|
||||
layout.version = json.version;
|
||||
layout.socialImage = json.socialImage;
|
||||
layout.changesetMessage = json.changesetmessage;
|
||||
return layout;
|
||||
}
|
||||
|
||||
public static Translation(json: string | any): string | Translation {
|
||||
if (json === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof (json) === "string") {
|
||||
return json;
|
||||
}
|
||||
const tr = {};
|
||||
for (let key in json) {
|
||||
tr[key] = json[key]; // I'm doing this wrong, I know
|
||||
}
|
||||
return new Translation(tr);
|
||||
}
|
||||
|
||||
public static TagRendering(json: TagRenderingConfigJson | string): TagDependantUIElementConstructor {
|
||||
return FromJSON.TagRenderingWithDefault(json, "", undefined);
|
||||
}
|
||||
|
||||
public static TagRenderingWithDefault(json: TagRenderingConfigJson | string, propertyName, defaultValue: string): TagDependantUIElementConstructor {
|
||||
if (json === undefined) {
|
||||
if(defaultValue !== undefined){
|
||||
console.warn(`Using default value ${defaultValue} for ${propertyName}`)
|
||||
return FromJSON.TagRendering(defaultValue);
|
||||
}
|
||||
throw `Tagrendering ${propertyName} is undefined...`
|
||||
}
|
||||
|
||||
if (typeof json === "string") {
|
||||
|
||||
switch (json) {
|
||||
case "picture": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "pictures": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "image": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "images": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "picturesNoUpload": {
|
||||
return new ImageCarouselConstructor()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new TagRenderingOptions({
|
||||
freeform: {
|
||||
key: "id",
|
||||
renderTemplate: json,
|
||||
template: "$$$"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let template = FromJSON.Translation(json.render);
|
||||
|
||||
let freeform = undefined;
|
||||
if (json.freeform) {
|
||||
|
||||
if(json.render === undefined){
|
||||
console.error("Freeform is defined, but render is not. This is not allowed.", json)
|
||||
throw "Freeform is defined, but render is not. This is not allowed."
|
||||
}
|
||||
|
||||
freeform = {
|
||||
template: `$${json.freeform.type ?? "string"}$`,
|
||||
renderTemplate: template,
|
||||
key: json.freeform.key
|
||||
};
|
||||
if (json.freeform.addExtraTags) {
|
||||
freeform["extraTags"] = FromJSON.Tag(json.freeform.addExtraTags);
|
||||
}
|
||||
} else if (json.render) {
|
||||
freeform = {
|
||||
template: `$string$`,
|
||||
renderTemplate: template,
|
||||
key: "id"
|
||||
}
|
||||
}
|
||||
|
||||
const mappings = json.mappings?.map(mapping => (
|
||||
{
|
||||
k: FromJSON.Tag(mapping.if),
|
||||
txt: FromJSON.Translation(mapping.then),
|
||||
hideInAnswer: mapping.hideInAnswer
|
||||
})
|
||||
);
|
||||
|
||||
return new TagRenderingOptions({
|
||||
question: FromJSON.Translation(json.question),
|
||||
freeform: freeform,
|
||||
mappings: mappings
|
||||
});
|
||||
}
|
||||
|
||||
public static SimpleTag(json: string): Tag {
|
||||
const tag = json.split("=");
|
||||
return new Tag(tag[0], tag[1]);
|
||||
}
|
||||
|
||||
public static Tag(json: AndOrTagConfigJson | string): TagsFilter {
|
||||
if (typeof (json) == "string") {
|
||||
const tag = json as string;
|
||||
if (tag.indexOf("!~") >= 0) {
|
||||
const split = tag.split("!~");
|
||||
if(split[1] == "*"){
|
||||
split[1] = ".*"
|
||||
}
|
||||
return new RegexTag(
|
||||
new RegExp(split[0]),
|
||||
new RegExp(split[1]),
|
||||
true
|
||||
);
|
||||
}
|
||||
if (tag.indexOf("!=") >= 0) {
|
||||
const split = tag.split("!=");
|
||||
return new RegexTag(
|
||||
new RegExp(split[0]),
|
||||
new RegExp(split[1]),
|
||||
true
|
||||
);
|
||||
}
|
||||
if (tag.indexOf("~") >= 0) {
|
||||
const split = tag.split("~");
|
||||
if(split[1] == "*"){
|
||||
split[1] = ".*"
|
||||
}
|
||||
return new RegexTag(
|
||||
new RegExp("^"+split[0]+"$"),
|
||||
new RegExp("^"+split[1]+"$")
|
||||
);
|
||||
}
|
||||
const split = tag.split("=");
|
||||
return new Tag(split[0], split[1])
|
||||
}
|
||||
if (json.and !== undefined) {
|
||||
return new And(json.and.map(FromJSON.Tag));
|
||||
}
|
||||
if (json.or !== undefined) {
|
||||
return new And(json.or.map(FromJSON.Tag));
|
||||
}
|
||||
}
|
||||
|
||||
private static Title(json: string | Map<string, string> | TagRenderingConfigJson): TagDependantUIElementConstructor {
|
||||
if ((json as TagRenderingConfigJson).render !== undefined) {
|
||||
return FromJSON.TagRendering((json as TagRenderingConfigJson));
|
||||
} else if (typeof (json) === "string") {
|
||||
return new FixedText(Translations.WT(json));
|
||||
} else {
|
||||
return new FixedText(FromJSON.Translation(json as Map<string, string>));
|
||||
}
|
||||
}
|
||||
|
||||
public static Layer(json: LayerConfigJson): LayerDefinition {
|
||||
console.log("Parsing ",json.name);
|
||||
const tr = FromJSON.Translation;
|
||||
const overpassTags = FromJSON.Tag(json.overpassTags);
|
||||
const icon = FromJSON.TagRenderingWithDefault(json.icon, "layericon", "./assets/bug.svg");
|
||||
const color = FromJSON.TagRenderingWithDefault(json.color, "layercolor", "#0000ff");
|
||||
const width = FromJSON.TagRenderingWithDefault(json.width, "layerwidth", "10");
|
||||
const renderTags = {"id": "node/-1"}
|
||||
const presets: Preset[] = json?.presets?.map(preset => {
|
||||
return ({
|
||||
title: tr(preset.title),
|
||||
description: tr(preset.description),
|
||||
tags: preset.tags.map(FromJSON.SimpleTag)
|
||||
});
|
||||
}) ?? [];
|
||||
|
||||
function style(tags) {
|
||||
return {
|
||||
color: color.GetContent(tags).txt,
|
||||
weight: width.GetContent(tags).txt,
|
||||
icon: {
|
||||
iconUrl: icon.GetContent(tags).txt
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const layer = new LayerDefinition(
|
||||
json.id,
|
||||
{
|
||||
name: tr(json.name),
|
||||
description: tr(json.description),
|
||||
icon: icon.GetContent(renderTags).txt,
|
||||
overpassFilter: overpassTags,
|
||||
|
||||
title: FromJSON.Title(json.title),
|
||||
minzoom: json.minzoom,
|
||||
presets: presets,
|
||||
elementsToShow: json.tagRenderings?.map(FromJSON.TagRendering) ?? [],
|
||||
style: style,
|
||||
wayHandling: json.wayHandling
|
||||
|
||||
}
|
||||
);
|
||||
return layer;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
79
Customizations/JSON/LayerConfigJson.ts
Normal file
79
Customizations/JSON/LayerConfigJson.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||
|
||||
/**
|
||||
* Configuration for a single layer
|
||||
*/
|
||||
export interface LayerConfigJson {
|
||||
/**
|
||||
* The id of this layer.
|
||||
* This should be a simple, lowercase, human readable string that is used to identify the layer.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The name of this layer
|
||||
* Used in the layer control panel and the 'Personal theme'
|
||||
*/
|
||||
name: string | any
|
||||
|
||||
/**
|
||||
* A description for this layer.
|
||||
* Shown in the layer selections and in the personal theme
|
||||
*/
|
||||
description?: string | any;
|
||||
|
||||
|
||||
/**
|
||||
* The tags to load from overpass. Either a simple 'key=value'-string, otherwise an advanced configuration
|
||||
*/
|
||||
overpassTags: AndOrTagConfigJson | string;
|
||||
|
||||
/**
|
||||
* The zoomlevel at which point the data is shown and loaded.
|
||||
*/
|
||||
minzoom: number;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The title shown in a popup for elements of this layer
|
||||
*/
|
||||
title: string | any | TagRenderingConfigJson;
|
||||
|
||||
/**
|
||||
* The icon for an element.
|
||||
* Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.
|
||||
*/
|
||||
icon?: string | TagRenderingConfigJson;
|
||||
/**
|
||||
* The color for way-elements
|
||||
*/
|
||||
color?: string | TagRenderingConfigJson;
|
||||
/**
|
||||
* The stroke-width for way-elements
|
||||
*/
|
||||
width?: string | TagRenderingConfigJson;
|
||||
|
||||
/**
|
||||
* Wayhandling: should a way/area be displayed as:
|
||||
* 0) The way itself
|
||||
* 1) The centerpoint and the way
|
||||
* 2) Only the centerpoint?
|
||||
*/
|
||||
wayHandling?: number;
|
||||
|
||||
/**
|
||||
* Presets for this layer
|
||||
*/
|
||||
presets?: {
|
||||
tags: string[],
|
||||
title: string | any,
|
||||
description?: string | any,
|
||||
}[],
|
||||
|
||||
/**
|
||||
* All the tag renderings.
|
||||
*/
|
||||
tagRenderings?: (string | TagRenderingConfigJson) []
|
||||
}
|
98
Customizations/JSON/LayoutConfigJson.ts
Normal file
98
Customizations/JSON/LayoutConfigJson.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import {LayerConfigJson} from "./LayerConfigJson";
|
||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
|
||||
/**
|
||||
* Defines what a JSON-segment defining a layout should look like.
|
||||
*
|
||||
* General remark: a type (string | any) indicates either a fixed or a translatable string
|
||||
*/
|
||||
export interface LayoutConfigJson {
|
||||
/**
|
||||
* The id of this layout.
|
||||
* This should be a simple, lowercase string which is used to create the html-page, e.g.
|
||||
* 'cyclestreets' which become 'cyclestreets.html'
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Who does maintian this preset?
|
||||
*/
|
||||
maintainer: string;
|
||||
/**
|
||||
* Extra piece of text that can be added to the changeset
|
||||
*/
|
||||
changesetmessage?: string;
|
||||
/**
|
||||
* A version number, either semantically or by date.
|
||||
* Should be sortable, where the higher value is the later version
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* The supported language(s).
|
||||
* This should be a two-letter, lowercase code which identifies the language, e.g. "en", "nl", ...
|
||||
* If the theme supports multiple languages, use a list: `["en","nl","fr"]` to allow the user to pick any of them
|
||||
*/
|
||||
language: string | string[];
|
||||
|
||||
/**
|
||||
* The title, as shown in the welcome message and the more-screen
|
||||
*/
|
||||
title: string | any;
|
||||
/**
|
||||
* The description, as shown in the welcome message and the more-screen
|
||||
*/
|
||||
description: string | any;
|
||||
|
||||
|
||||
/**
|
||||
* The icon representing this theme.
|
||||
* Used as logo in the more-screen and (for official themes) as favicon, webmanifest logo, ...
|
||||
* Either a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64)
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
/**
|
||||
* Link to a 'social image' which is included as og:image-tag on official themes.
|
||||
* Usefull to share the theme on social media
|
||||
*/
|
||||
socialImage?: string;
|
||||
|
||||
/**
|
||||
* Default location and zoom to start.
|
||||
* Note that this is barely used. Once the user has visited mapcomplete at least once, the previous location of the user will be used
|
||||
*/
|
||||
startZoom: number;
|
||||
startLat: number;
|
||||
startLon: number;
|
||||
|
||||
/**
|
||||
* When a query is run, the data within bounds of the visible map is loaded.
|
||||
* However, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.
|
||||
* For this, the bounds are widened in order to make a small pan still within bounds of the loaded data.
|
||||
*
|
||||
* IF widenfactor is 0, this feature is disabled. A recommended value is between 0.5 and 0.01 (the latter for very dense queries)
|
||||
*/
|
||||
widenFactor?: number;
|
||||
|
||||
/**
|
||||
* A tagrendering depicts how to show some tags or how to show a question for it.
|
||||
*
|
||||
* These tagrenderings are applied to _all_ the loaded layers and are a way to reuse tagrenderings.
|
||||
* Note that if multiple themes are loaded (e.g. via the personal theme)
|
||||
* that these roamingRenderings are applied to the layers of the OTHER themes too!
|
||||
*
|
||||
* In order to prevent them to do too much damage, all the overpass-tags of the layers are taken and combined as OR.
|
||||
* These tag renderings will only show up if the object matches this filter.
|
||||
*/
|
||||
roamingRenderings?: TagRenderingConfigJson[],
|
||||
|
||||
/**
|
||||
* The layers to display
|
||||
*/
|
||||
layers: LayerConfigJson[],
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
14
Customizations/JSON/TagConfig.ts
Normal file
14
Customizations/JSON/TagConfig.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Read a tagconfig and converts it into a TagsFilter value
|
||||
*/
|
||||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||
|
||||
export default class TagConfig {
|
||||
|
||||
public static fromJson(json: any): TagConfig {
|
||||
const config: AndOrTagConfigJson = json;
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
8
Customizations/JSON/TagConfigJson.ts
Normal file
8
Customizations/JSON/TagConfigJson.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
export interface AndOrTagConfigJson {
|
||||
|
||||
and?: (string | AndOrTagConfigJson)[]
|
||||
or?: (string | AndOrTagConfigJson)[]
|
||||
|
||||
|
||||
}
|
51
Customizations/JSON/TagRenderingConfigJson.ts
Normal file
51
Customizations/JSON/TagRenderingConfigJson.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||
|
||||
export interface TagRenderingConfigJson {
|
||||
/**
|
||||
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
|
||||
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.
|
||||
*/
|
||||
render?: string | any,
|
||||
|
||||
/**
|
||||
* If it turns out that this tagRendering doesn't match _any_ value, then we show this question.
|
||||
* If undefined, the question is never asked and this tagrendering is read-only
|
||||
*/
|
||||
question?: string | any,
|
||||
|
||||
/**
|
||||
* Only show this question if the object also matches the following tags.
|
||||
*
|
||||
* This is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...
|
||||
* */
|
||||
condition?: AndOrTagConfigJson | string;
|
||||
|
||||
/**
|
||||
* Allow freeform text input from the user
|
||||
*/
|
||||
freeform?: {
|
||||
/**
|
||||
* If this key is present, then 'render' is used to display the value.
|
||||
* If this is undefined, the rendering is _always_ shown
|
||||
*/
|
||||
key: string,
|
||||
/**
|
||||
* The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...
|
||||
*/
|
||||
type?: string,
|
||||
/**
|
||||
* If a value is added with the textfield, these extra tag is addded.
|
||||
* Usefull to add a 'fixme=freeform textfield used - to be checked'
|
||||
**/
|
||||
addExtraTags?: AndOrTagConfigJson | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes
|
||||
*/
|
||||
mappings?: {
|
||||
if: AndOrTagConfigJson | string,
|
||||
then: string | any
|
||||
hideInAnswer?: boolean
|
||||
}[]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue