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,6 +1,5 @@
|
||||||
import {LayerDefinition} from "./LayerDefinition";
|
import {LayerDefinition} from "./LayerDefinition";
|
||||||
import {Layout} from "./Layout";
|
import {Layout} from "./Layout";
|
||||||
import {All} from "./Layouts/All";
|
|
||||||
import {Groen} from "./Layouts/Groen";
|
import {Groen} from "./Layouts/Groen";
|
||||||
import Cyclofix from "./Layouts/Cyclofix";
|
import Cyclofix from "./Layouts/Cyclofix";
|
||||||
import {StreetWidth} from "./Layouts/StreetWidth";
|
import {StreetWidth} from "./Layouts/StreetWidth";
|
||||||
|
@ -10,12 +9,14 @@ import {Smoothness} from "./Layouts/Smoothness";
|
||||||
import {MetaMap} from "./Layouts/MetaMap";
|
import {MetaMap} from "./Layouts/MetaMap";
|
||||||
import {Natuurpunt} from "./Layouts/Natuurpunt";
|
import {Natuurpunt} from "./Layouts/Natuurpunt";
|
||||||
import {GhostBikes} from "./Layouts/GhostBikes";
|
import {GhostBikes} from "./Layouts/GhostBikes";
|
||||||
import {CustomLayoutFromJSON} from "./JSON/CustomLayoutFromJSON";
|
import {FromJSON} from "./JSON/FromJSON";
|
||||||
import * as bookcases from "../assets/themes/bookcases/Bookcases.json";
|
import * as bookcases from "../assets/themes/bookcases/Bookcases.json";
|
||||||
import * as aed from "../assets/themes/aed/aed.json";
|
import * as aed from "../assets/themes/aed/aed.json";
|
||||||
import * as toilets from "../assets/themes/toilets/toilets.json";
|
import * as toilets from "../assets/themes/toilets/toilets.json";
|
||||||
import * as artworks from "../assets/themes/artwork/artwork.json";
|
import * as artworks from "../assets/themes/artwork/artwork.json";
|
||||||
import * as cyclestreets from "../assets/themes/cyclestreets/cyclestreets.json";
|
import * as cyclestreets from "../assets/themes/cyclestreets/cyclestreets.json";
|
||||||
|
|
||||||
|
|
||||||
import {PersonalLayout} from "../Logic/PersonalLayout";
|
import {PersonalLayout} from "../Logic/PersonalLayout";
|
||||||
|
|
||||||
export class AllKnownLayouts {
|
export class AllKnownLayouts {
|
||||||
|
@ -28,11 +29,11 @@ export class AllKnownLayouts {
|
||||||
new GRB(),
|
new GRB(),
|
||||||
new Cyclofix(),
|
new Cyclofix(),
|
||||||
new GhostBikes(),
|
new GhostBikes(),
|
||||||
CustomLayoutFromJSON.LayoutFromJSON(bookcases),
|
FromJSON.LayoutFromJSON(bookcases),
|
||||||
CustomLayoutFromJSON.LayoutFromJSON(aed),
|
// FromJSON.LayoutFromJSON(aed),
|
||||||
CustomLayoutFromJSON.LayoutFromJSON(toilets),
|
// FromJSON.LayoutFromJSON(toilets),
|
||||||
CustomLayoutFromJSON.LayoutFromJSON(artworks),
|
// FromJSON.LayoutFromJSON(artworks),
|
||||||
CustomLayoutFromJSON.LayoutFromJSON(cyclestreets),
|
// FromJSON.LayoutFromJSON(cyclestreets),
|
||||||
|
|
||||||
new MetaMap(),
|
new MetaMap(),
|
||||||
new StreetWidth(),
|
new StreetWidth(),
|
||||||
|
@ -48,26 +49,22 @@ export class AllKnownLayouts {
|
||||||
private static AllLayouts(): Map<string, Layout> {
|
private static AllLayouts(): Map<string, Layout> {
|
||||||
|
|
||||||
|
|
||||||
const all = new All();
|
|
||||||
this.allLayers = new Map<string, LayerDefinition>();
|
this.allLayers = new Map<string, LayerDefinition>();
|
||||||
for (const layout of this.layoutsList) {
|
for (const layout of this.layoutsList) {
|
||||||
for (const layer of layout.layers) {
|
for (const layer of layout.layers) {
|
||||||
const key = layer.id;
|
|
||||||
if (this.allLayers[layer.id] !== undefined) {
|
if (this.allLayers[layer.id] !== undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.allLayers[layer.id] = layer;
|
this.allLayers[layer.id] = layer;
|
||||||
this.allLayers[layer.id.toLowerCase()] = layer;
|
this.allLayers[layer.id.toLowerCase()] = layer;
|
||||||
all.layers.push(layer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSets: Map<string, Layout> = new Map();
|
const allSets: Map<string, Layout> = new Map();
|
||||||
for (const layout of this.layoutsList) {
|
for (const layout of this.layoutsList) {
|
||||||
allSets[layout.name] = layout;
|
allSets[layout.id] = layout;
|
||||||
allSets[layout.name.toLowerCase()] = layout;
|
allSets[layout.id.toLowerCase()] = layout;
|
||||||
}
|
}
|
||||||
allSets[all.name] = all;
|
|
||||||
return allSets;
|
return allSets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}[]
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import {Tag, TagsFilter} from "../Logic/TagsFilter";
|
import {Tag, TagsFilter} from "../Logic/Tags";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import {TagDependantUIElementConstructor} from "./UIElementConstructor";
|
import {TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||||
import {TagRenderingOptions} from "./TagRenderingOptions";
|
import {TagRenderingOptions} from "./TagRenderingOptions";
|
||||||
import Translation from "../UI/i18n/Translation";
|
import Translation from "../UI/i18n/Translation";
|
||||||
import {LayerConfigJson, TagRenderingConfigJson} from "./JSON/CustomLayoutFromJSON";
|
|
||||||
|
|
||||||
export interface Preset {
|
export interface Preset {
|
||||||
tags: Tag[],
|
tags: Tag[],
|
||||||
|
@ -76,8 +75,7 @@ export class LayerDefinition {
|
||||||
color: string,
|
color: string,
|
||||||
weight?: number,
|
weight?: number,
|
||||||
icon: {
|
icon: {
|
||||||
iconUrl: string,
|
iconUrl: string, iconSize?: number[], popupAnchor?: number[], iconAnchor?: number[]
|
||||||
iconSize: number[],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import FixedText from "../Questions/FixedText";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import CafeName from "../Questions/bike/CafeName";
|
import CafeName from "../Questions/bike/CafeName";
|
||||||
import { Or, And, Tag, anyValueExcept, Regex } from "../../Logic/TagsFilter";
|
import {And, Or, RegexTag, Tag} from "../../Logic/Tags";
|
||||||
import { PhoneNumberQuestion } from "../Questions/PhoneNumberQuestion";
|
import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion";
|
||||||
import Website from "../Questions/Website";
|
import Website from "../Questions/Website";
|
||||||
import CafeRepair from "../Questions/bike/CafeRepair";
|
import CafeRepair from "../Questions/bike/CafeRepair";
|
||||||
import CafeDiy from "../Questions/bike/CafeDiy";
|
import CafeDiy from "../Questions/bike/CafeDiy";
|
||||||
|
@ -20,10 +20,11 @@ export default class BikeCafes extends LayerDefinition {
|
||||||
this.name = this.to.name
|
this.name = this.to.name
|
||||||
this.icon = "./assets/bike/cafe.svg"
|
this.icon = "./assets/bike/cafe.svg"
|
||||||
this.overpassFilter = new And([
|
this.overpassFilter = new And([
|
||||||
new Tag("amenity", /^pub|bar|cafe$/),
|
new RegexTag(/^amenity$/, /^pub|bar|cafe$/),
|
||||||
new Or([
|
new Or([
|
||||||
new Tag(/^service:bicycle:/, "*"),
|
new RegexTag(/^service:bicycle:/, /.*/),
|
||||||
new Tag("pub", "cycling")
|
new RegexTag(/^pub$/, /^cycling|bicycle$/),
|
||||||
|
new RegexTag(/^theme$/, /^cycling|bicycle$/),
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -40,7 +41,14 @@ export default class BikeCafes extends LayerDefinition {
|
||||||
this.maxAllowedOverlapPercentage = 10;
|
this.maxAllowedOverlapPercentage = 10;
|
||||||
|
|
||||||
this.minzoom = 13
|
this.minzoom = 13
|
||||||
this.style = this.generateStyleFunction()
|
this.style = () => ({
|
||||||
|
color: "#00bb00",
|
||||||
|
icon: {
|
||||||
|
iconUrl: "./assets/bike/cafe.svg",
|
||||||
|
iconSize: [50, 50],
|
||||||
|
iconAnchor: [25, 50]
|
||||||
|
}
|
||||||
|
});
|
||||||
this.title = new FixedText(this.to.title)
|
this.title = new FixedText(this.to.title)
|
||||||
this.elementsToShow = [
|
this.elementsToShow = [
|
||||||
new ImageCarouselWithUploadConstructor(),
|
new ImageCarouselWithUploadConstructor(),
|
||||||
|
@ -54,18 +62,4 @@ export default class BikeCafes extends LayerDefinition {
|
||||||
]
|
]
|
||||||
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
|
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateStyleFunction() {
|
|
||||||
const self = this
|
|
||||||
return function (properties: any) {
|
|
||||||
return {
|
|
||||||
color: "#00bb00",
|
|
||||||
icon: {
|
|
||||||
iconUrl: "./assets/bike/cafe.svg",
|
|
||||||
iconSize: [50, 50],
|
|
||||||
iconAnchor: [25,50]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { LayerDefinition } from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import {And, Tag, Or, anyValueExcept} from "../../Logic/TagsFilter";
|
import {And, RegexTag, Tag} from "../../Logic/Tags";
|
||||||
import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import ShopRetail from "../Questions/bike/ShopRetail";
|
import ShopRetail from "../Questions/bike/ShopRetail";
|
||||||
import ShopPump from "../Questions/bike/ShopPump";
|
import ShopPump from "../Questions/bike/ShopPump";
|
||||||
import ShopRental from "../Questions/bike/ShopRental";
|
import ShopRental from "../Questions/bike/ShopRental";
|
||||||
|
@ -9,7 +9,7 @@ import ShopRepair from "../Questions/bike/ShopRepair";
|
||||||
import ShopDiy from "../Questions/bike/ShopDiy";
|
import ShopDiy from "../Questions/bike/ShopDiy";
|
||||||
import ShopName from "../Questions/bike/ShopName";
|
import ShopName from "../Questions/bike/ShopName";
|
||||||
import ShopSecondHand from "../Questions/bike/ShopSecondHand";
|
import ShopSecondHand from "../Questions/bike/ShopSecondHand";
|
||||||
import { PhoneNumberQuestion } from "../Questions/PhoneNumberQuestion";
|
import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion";
|
||||||
import Website from "../Questions/Website";
|
import Website from "../Questions/Website";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ export default class BikeOtherShops extends LayerDefinition {
|
||||||
this.name = this.to.name
|
this.name = this.to.name
|
||||||
this.icon = "./assets/bike/non_bike_repair_shop.svg"
|
this.icon = "./assets/bike/non_bike_repair_shop.svg"
|
||||||
this.overpassFilter = new And([
|
this.overpassFilter = new And([
|
||||||
anyValueExcept("shop", "bicycle"),
|
new RegexTag(/^shop$/, /^bicycle$/, true),
|
||||||
new Tag(/^service:bicycle:/, "*"),
|
new RegexTag(/^service:bicycle:/, /.*/),
|
||||||
])
|
])
|
||||||
this.presets = []
|
this.presets = []
|
||||||
this.maxAllowedOverlapPercentage = 10
|
this.maxAllowedOverlapPercentage = 10
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Or, Tag, TagsFilter} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {OperatorTag} from "../Questions/OperatorTag";
|
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import ParkingType from "../Questions/bike/ParkingType";
|
import ParkingType from "../Questions/bike/ParkingType";
|
||||||
import ParkingCapacity from "../Questions/bike/ParkingCapacity";
|
import ParkingCapacity from "../Questions/bike/ParkingCapacity";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import ParkingOperator from "../Questions/bike/ParkingOperator";
|
|
||||||
import ParkingAccessCargo from "../Questions/bike/ParkingAccessCargo";
|
import ParkingAccessCargo from "../Questions/bike/ParkingAccessCargo";
|
||||||
import ParkingCapacityCargo from "../Questions/bike/ParkingCapacityCargo";
|
import ParkingCapacityCargo from "../Questions/bike/ParkingCapacityCargo";
|
||||||
|
|
||||||
|
@ -28,8 +26,17 @@ export default class BikeParkings extends LayerDefinition {
|
||||||
|
|
||||||
this.maxAllowedOverlapPercentage = 10;
|
this.maxAllowedOverlapPercentage = 10;
|
||||||
|
|
||||||
this.minzoom = 13;
|
this.minzoom = 17;
|
||||||
this.style = this.generateStyleFunction();
|
this.style = function () {
|
||||||
|
return {
|
||||||
|
color: "#00bb00",
|
||||||
|
icon: {
|
||||||
|
iconUrl: "./assets/bike/parking.svg",
|
||||||
|
iconSize: [50, 50],
|
||||||
|
iconAnchor: [25, 50]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
this.title = new FixedText(Translations.t.cyclofix.parking.title)
|
this.title = new FixedText(Translations.t.cyclofix.parking.title)
|
||||||
this.elementsToShow = [
|
this.elementsToShow = [
|
||||||
new ImageCarouselWithUploadConstructor(),
|
new ImageCarouselWithUploadConstructor(),
|
||||||
|
@ -42,18 +49,4 @@ export default class BikeParkings extends LayerDefinition {
|
||||||
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY;
|
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateStyleFunction() {
|
|
||||||
const self = this;
|
|
||||||
return function (properties: any) {
|
|
||||||
return {
|
|
||||||
color: "#00bb00",
|
|
||||||
icon: {
|
|
||||||
iconUrl: "./assets/bike/parking.svg",
|
|
||||||
iconSize: [50, 50],
|
|
||||||
iconAnchor: [25,50]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { LayerDefinition } from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import {And, Tag, Or} from "../../Logic/TagsFilter";
|
import {And, Tag} from "../../Logic/Tags";
|
||||||
import FixedText from "../Questions/FixedText";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload";
|
|
||||||
import ShopRetail from "../Questions/bike/ShopRetail";
|
import ShopRetail from "../Questions/bike/ShopRetail";
|
||||||
import ShopPump from "../Questions/bike/ShopPump";
|
import ShopPump from "../Questions/bike/ShopPump";
|
||||||
import ShopRental from "../Questions/bike/ShopRental";
|
import ShopRental from "../Questions/bike/ShopRental";
|
||||||
|
@ -18,7 +17,6 @@ import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export default class BikeShops extends LayerDefinition {
|
export default class BikeShops extends LayerDefinition {
|
||||||
private readonly sellsBikes = new Tag("service:bicycle:retail", "yes")
|
private readonly sellsBikes = new Tag("service:bicycle:retail", "yes")
|
||||||
private readonly repairsBikes = new Tag("service:bicycle:repair", "yes")
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("bikeshop");
|
super("bikeshop");
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Tag, TagsFilter, Or, Not} from "../../Logic/TagsFilter";
|
import {And, Or, Tag} from "../../Logic/Tags";
|
||||||
import BikeStationChain from "../Questions/bike/StationChain";
|
import BikeStationChain from "../Questions/bike/StationChain";
|
||||||
import BikeStationPumpTools from "../Questions/bike/StationPumpTools";
|
import BikeStationPumpTools from "../Questions/bike/StationPumpTools";
|
||||||
import BikeStationStand from "../Questions/bike/StationStand";
|
import BikeStationStand from "../Questions/bike/StationStand";
|
||||||
import PumpManual from "../Questions/bike/PumpManual";
|
import PumpManual from "../Questions/bike/PumpManual";
|
||||||
import BikeStationOperator from "../Questions/bike/StationOperator";
|
|
||||||
import BikeStationBrand from "../Questions/bike/StationBrand";
|
|
||||||
import FixedText from "../Questions/FixedText";
|
|
||||||
import PumpManometer from "../Questions/bike/PumpManometer";
|
import PumpManometer from "../Questions/bike/PumpManometer";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import PumpOperational from "../Questions/bike/PumpOperational";
|
import PumpOperational from "../Questions/bike/PumpOperational";
|
||||||
|
@ -19,7 +16,6 @@ export default class BikeStations extends LayerDefinition {
|
||||||
private readonly repairStation = new Tag("amenity", "bicycle_repair_station");
|
private readonly repairStation = new Tag("amenity", "bicycle_repair_station");
|
||||||
private readonly pump = new Tag("service:bicycle:pump", "yes");
|
private readonly pump = new Tag("service:bicycle:pump", "yes");
|
||||||
private readonly nopump = new Tag("service:bicycle:pump", "no");
|
private readonly nopump = new Tag("service:bicycle:pump", "no");
|
||||||
private readonly pumpOperationalAny = new Tag("service:bicycle:pump:operational_status", "yes");
|
|
||||||
private readonly pumpOperationalOk = new Or([new Tag("service:bicycle:pump:operational_status", "yes"), new Tag("service:bicycle:pump:operational_status", "operational"), new Tag("service:bicycle:pump:operational_status", "ok"), new Tag("service:bicycle:pump:operational_status", "")]);
|
private readonly pumpOperationalOk = new Or([new Tag("service:bicycle:pump:operational_status", "yes"), new Tag("service:bicycle:pump:operational_status", "operational"), new Tag("service:bicycle:pump:operational_status", "ok"), new Tag("service:bicycle:pump:operational_status", "")]);
|
||||||
private readonly tools = new Tag("service:bicycle:tools", "yes");
|
private readonly tools = new Tag("service:bicycle:tools", "yes");
|
||||||
private readonly notools = new Tag("service:bicycle:tools", "no");
|
private readonly notools = new Tag("service:bicycle:tools", "no");
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Or, Tag} from "../../Logic/TagsFilter";
|
import {And, Or, Tag} from "../../Logic/Tags";
|
||||||
import FixedText from "../Questions/FixedText";
|
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ export class Birdhide extends LayerDefinition {
|
||||||
tags: [Birdhide.birdhide]
|
tags: [Birdhide.birdhide]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
style(tags: any): { color: string; icon: any } {
|
style(): { color: string; icon: any } {
|
||||||
return {color: "", icon: undefined};
|
return {color: "", icon: undefined};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {Or, Tag} from "../../Logic/TagsFilter";
|
import {Or, Tag} from "../../Logic/Tags";
|
||||||
import {AccessTag} from "../Questions/AccessTag";
|
import {AccessTag} from "../Questions/AccessTag";
|
||||||
import {OperatorTag} from "../Questions/OperatorTag";
|
import {OperatorTag} from "../Questions/OperatorTag";
|
||||||
import {NameQuestion} from "../Questions/NameQuestion";
|
import {NameQuestion} from "../Questions/NameQuestion";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
import {And, Tag} from "../../Logic/Tags";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
|
|
||||||
export class ClimbingTree extends LayerDefinition {
|
export class ClimbingTree extends LayerDefinition {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Or, Tag} from "../../Logic/TagsFilter";
|
import {And, Or, Tag} from "../../Logic/Tags";
|
||||||
import {OperatorTag} from "../Questions/OperatorTag";
|
import {OperatorTag} from "../Questions/OperatorTag";
|
||||||
import * as L from "leaflet";
|
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
|
@ -29,7 +28,7 @@ export class DrinkingWater extends LayerDefinition {
|
||||||
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
|
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
|
||||||
|
|
||||||
this.minzoom = 13;
|
this.minzoom = 13;
|
||||||
this.style = this.generateStyleFunction();
|
this.style = DrinkingWater.generateStyleFunction();
|
||||||
this.title = new FixedText("Drinking water");
|
this.title = new FixedText("Drinking water");
|
||||||
this.elementsToShow = [
|
this.elementsToShow = [
|
||||||
new OperatorTag(),
|
new OperatorTag(),
|
||||||
|
@ -47,10 +46,8 @@ export class DrinkingWater extends LayerDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private generateStyleFunction() {
|
private static generateStyleFunction() {
|
||||||
const self = this;
|
return function () {
|
||||||
return function (properties: any) {
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
color: "#00bb00",
|
color: "#00bb00",
|
||||||
icon: {
|
icon: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Regex, Tag} from "../../Logic/TagsFilter";
|
import {And, RegexTag, Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class GrbToFix extends LayerDefinition {
|
export class GrbToFix extends LayerDefinition {
|
||||||
|
@ -10,12 +10,12 @@ export class GrbToFix extends LayerDefinition {
|
||||||
this.name = "grb";
|
this.name = "grb";
|
||||||
this.presets = [];
|
this.presets = [];
|
||||||
this.icon = "./assets/star.svg";
|
this.icon = "./assets/star.svg";
|
||||||
this.overpassFilter = new Regex("fixme", "GRB");
|
this.overpassFilter = new RegexTag(/fixme/, /.*GRB.*/);
|
||||||
this.minzoom = 13;
|
this.minzoom = 13;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.style = function (tags) {
|
this.style = function () {
|
||||||
return {
|
return {
|
||||||
icon: {
|
icon: {
|
||||||
iconUrl: "assets/star.svg",
|
iconUrl: "assets/star.svg",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
import {And, Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class InformationBoard extends LayerDefinition {
|
export class InformationBoard extends LayerDefinition {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class Map extends LayerDefinition {
|
export class Map extends LayerDefinition {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {Or, Tag} from "../../Logic/TagsFilter";
|
import {Or, Tag} from "../../Logic/Tags";
|
||||||
import {AccessTag} from "../Questions/AccessTag";
|
import {AccessTag} from "../Questions/AccessTag";
|
||||||
import {OperatorTag} from "../Questions/OperatorTag";
|
import {OperatorTag} from "../Questions/OperatorTag";
|
||||||
import {NameQuestion} from "../Questions/NameQuestion";
|
import {NameQuestion} from "../Questions/NameQuestion";
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Or, Tag} from "../../Logic/TagsFilter";
|
import {Or, Tag} from "../../Logic/Tags";
|
||||||
import {AccessTag} from "../Questions/AccessTag";
|
|
||||||
import {OperatorTag} from "../Questions/OperatorTag";
|
|
||||||
import {NameQuestion} from "../Questions/NameQuestion";
|
import {NameQuestion} from "../Questions/NameQuestion";
|
||||||
import {NameInline} from "../Questions/NameInline";
|
import {NameInline} from "../Questions/NameInline";
|
||||||
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
|
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
|
||||||
import FixedText from "../Questions/FixedText";
|
import FixedText from "../Questions/FixedText";
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ export class Viewpoint extends LayerDefinition {
|
||||||
}],
|
}],
|
||||||
icon: "assets/viewpoint.svg",
|
icon: "assets/viewpoint.svg",
|
||||||
wayHandling: LayerDefinition.WAYHANDLING_CENTER_ONLY,
|
wayHandling: LayerDefinition.WAYHANDLING_CENTER_ONLY,
|
||||||
style: tags => {
|
style: _ => {
|
||||||
return {
|
return {
|
||||||
color: undefined, icon: {
|
color: undefined, icon: {
|
||||||
iconUrl: "assets/viewpoint.svg",
|
iconUrl: "assets/viewpoint.svg",
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {And, Not, Or, Tag} from "../../Logic/TagsFilter";
|
import {And, Or, Tag} from "../../Logic/Tags";
|
||||||
import {Park} from "./Park";
|
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class Widths extends LayerDefinition {
|
export class Widths extends LayerDefinition {
|
||||||
|
|
||||||
private cyclistWidth: number;
|
private readonly cyclistWidth: number;
|
||||||
private carWidth: number;
|
private readonly carWidth: number;
|
||||||
private pedestrianWidth: number;
|
private readonly pedestrianWidth: number;
|
||||||
|
|
||||||
private readonly _bothSideParking = new Tag("parking:lane:both", "parallel");
|
private readonly _bothSideParking = new Tag("parking:lane:both", "parallel");
|
||||||
private readonly _noSideParking = new Tag("parking:lane:both", "no_parking");
|
private readonly _noSideParking = new Tag("parking:lane:both", "no_parking");
|
||||||
|
@ -36,10 +35,9 @@ export class Widths extends LayerDefinition {
|
||||||
|
|
||||||
private readonly _oneSideParking = new Or([this._leftSideParking, this._rightSideParking]);
|
private readonly _oneSideParking = new Or([this._leftSideParking, this._rightSideParking]);
|
||||||
|
|
||||||
private readonly _carfree = new Or(
|
private readonly _carfree = new And(
|
||||||
[new Tag("highway", "pedestrian"), new Tag("highway", "living_street"),
|
[new Tag("highway", "pedestrian"), new Tag("highway", "living_street"),
|
||||||
new Tag("access","destination"), new Tag("motor_vehicle", "destination")])
|
new Tag("access","destination"), new Tag("motor_vehicle", "destination")])
|
||||||
private readonly _notCarFree = new Not(this._carfree);
|
|
||||||
|
|
||||||
private calcProps(properties) {
|
private calcProps(properties) {
|
||||||
let parkingStateKnown = true;
|
let parkingStateKnown = true;
|
||||||
|
@ -59,8 +57,7 @@ export class Widths extends LayerDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let pedestrianFlowNeeded = 0;
|
let pedestrianFlowNeeded;
|
||||||
|
|
||||||
if (this._sidewalkBoth.matchesProperties(properties)) {
|
if (this._sidewalkBoth.matchesProperties(properties)) {
|
||||||
pedestrianFlowNeeded = 0;
|
pedestrianFlowNeeded = 0;
|
||||||
} else if (this._sidewalkNone.matchesProperties(properties)) {
|
} else if (this._sidewalkNone.matchesProperties(properties)) {
|
||||||
|
@ -198,7 +195,7 @@ export class Widths extends LayerDefinition {
|
||||||
renderTemplate: "{note:width:carriageway}",
|
renderTemplate: "{note:width:carriageway}",
|
||||||
template: "$$$",
|
template: "$$$",
|
||||||
}
|
}
|
||||||
}).OnlyShowIf(this._notCarFree),
|
}).OnlyShowIf(this._carfree, true),
|
||||||
|
|
||||||
|
|
||||||
new TagRenderingOptions({
|
new TagRenderingOptions({
|
||||||
|
@ -218,7 +215,7 @@ export class Widths extends LayerDefinition {
|
||||||
renderTemplate: "{note:width:carriageway}",
|
renderTemplate: "{note:width:carriageway}",
|
||||||
template: "$$$",
|
template: "$$$",
|
||||||
}
|
}
|
||||||
}).OnlyShowIf(this._notCarFree),
|
}).OnlyShowIf(this._carfree, true),
|
||||||
|
|
||||||
|
|
||||||
new TagRenderingOptions({
|
new TagRenderingOptions({
|
||||||
|
@ -248,7 +245,7 @@ export class Widths extends LayerDefinition {
|
||||||
txt: "Tweerichtingsverkeer voor iedereen. Dit gebruikt <b>" + r(2 * this.carWidth + 2 * this.cyclistWidth) + "m</b>"
|
txt: "Tweerichtingsverkeer voor iedereen. Dit gebruikt <b>" + r(2 * this.carWidth + 2 * this.cyclistWidth) + "m</b>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).OnlyShowIf(this._notCarFree),
|
}).OnlyShowIf(this._carfree, true),
|
||||||
|
|
||||||
new TagRenderingOptions(
|
new TagRenderingOptions(
|
||||||
{
|
{
|
||||||
|
@ -266,7 +263,7 @@ export class Widths extends LayerDefinition {
|
||||||
{k: new Tag("short",""), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <span class='thanks'>{targetWidth}m</span>"}
|
{k: new Tag("short",""), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <span class='thanks'>{targetWidth}m</span>"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
).OnlyShowIf(this._notCarFree),
|
).OnlyShowIf(this._carfree, true),
|
||||||
|
|
||||||
|
|
||||||
new TagRenderingOptions({
|
new TagRenderingOptions({
|
||||||
|
|
|
@ -2,7 +2,6 @@ import {LayerDefinition} from "./LayerDefinition";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import Translations from "../UI/i18n/Translations";
|
import Translations from "../UI/i18n/Translations";
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +9,7 @@ import {State} from "../State";
|
||||||
*/
|
*/
|
||||||
export class Layout {
|
export class Layout {
|
||||||
|
|
||||||
public name: string;
|
public id: string;
|
||||||
public icon: string = "./assets/logo.svg";
|
public icon: string = "./assets/logo.svg";
|
||||||
public title: UIElement;
|
public title: UIElement;
|
||||||
public maintainer: string;
|
public maintainer: string;
|
||||||
|
@ -25,20 +24,19 @@ export class Layout {
|
||||||
public welcomeBackMessage: UIElement;
|
public welcomeBackMessage: UIElement;
|
||||||
public welcomeTail: UIElement;
|
public welcomeTail: UIElement;
|
||||||
|
|
||||||
public startzoom: number;
|
|
||||||
public supportedLanguages: string[];
|
public supportedLanguages: string[];
|
||||||
|
|
||||||
|
public startzoom: number;
|
||||||
public startLon: number;
|
public startLon: number;
|
||||||
public startLat: number;
|
public startLat: number;
|
||||||
|
|
||||||
public locationContains: string[];
|
|
||||||
|
|
||||||
public enableAdd: boolean = true;
|
public enableAdd: boolean = true;
|
||||||
public enableUserBadge: boolean = true;
|
public enableUserBadge: boolean = true;
|
||||||
public enableSearch: boolean = true;
|
public enableSearch: boolean = true;
|
||||||
public enableLayers: boolean = true;
|
public enableLayers: boolean = true;
|
||||||
public enableMoreQuests: boolean = true;
|
public enableMoreQuests: boolean = true;
|
||||||
public enableShareScreen: boolean = true;
|
public enableShareScreen: boolean = true;
|
||||||
|
public enableGeolocation: boolean = true;
|
||||||
public hideFromOverview: boolean = false;
|
public hideFromOverview: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,11 +45,10 @@ export class Layout {
|
||||||
*/
|
*/
|
||||||
public widenFactor: number = 0.07;
|
public widenFactor: number = 0.07;
|
||||||
public defaultBackground: string = "osm";
|
public defaultBackground: string = "osm";
|
||||||
public enableGeolocation: boolean = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param name: The name used in the query string. If in the query "quests=<name>" is defined, it will select this layout
|
* @param id: The name used in the query string. If in the query "quests=<name>" is defined, it will select this layout
|
||||||
* @param title: Will be used in the <title> of the page
|
* @param title: Will be used in the <title> of the page
|
||||||
* @param layers: The layers to show, a list of LayerDefinitions
|
* @param layers: The layers to show, a list of LayerDefinitions
|
||||||
* @param startzoom: The initial starting zoom of the map
|
* @param startzoom: The initial starting zoom of the map
|
||||||
|
@ -63,7 +60,7 @@ export class Layout {
|
||||||
* @param welcomeTail: This text is shown below the login message. It is ideal for extra help
|
* @param welcomeTail: This text is shown below the login message. It is ideal for extra help
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
name: string,
|
id: string,
|
||||||
supportedLanguages: string[],
|
supportedLanguages: string[],
|
||||||
title: UIElement | string,
|
title: UIElement | string,
|
||||||
layers: LayerDefinition[],
|
layers: LayerDefinition[],
|
||||||
|
@ -85,7 +82,7 @@ export class Layout {
|
||||||
this.startLon = startLon;
|
this.startLon = startLon;
|
||||||
this.startLat = startLat;
|
this.startLat = startLat;
|
||||||
this.startzoom = startzoom;
|
this.startzoom = startzoom;
|
||||||
this.name = name;
|
this.id = id;
|
||||||
this.layers = layers;
|
this.layers = layers;
|
||||||
this.welcomeMessage = Translations.W(welcomeMessage)
|
this.welcomeMessage = Translations.W(welcomeMessage)
|
||||||
this.gettingStartedPlzLogin = Translations.W(gettingStartedPlzLogin);
|
this.gettingStartedPlzLogin = Translations.W(gettingStartedPlzLogin);
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import {Layout} from "../Layout";
|
|
||||||
|
|
||||||
export class All extends Layout{
|
|
||||||
constructor() {
|
|
||||||
super(
|
|
||||||
"all",
|
|
||||||
["en"],
|
|
||||||
"All quest layers",
|
|
||||||
[],
|
|
||||||
15,
|
|
||||||
51.2,
|
|
||||||
3.2,
|
|
||||||
"<h3>All quests of MapComplete</h3>" +
|
|
||||||
"This is a mixed bag. Some quests might be hard or for experts to answer only",
|
|
||||||
"Please log in",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
this.hideFromOverview = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import {Layout} from "../Layout";
|
import {Layout} from "../Layout";
|
||||||
import {ClimbingTree} from "../Layers/ClimbingTree";
|
import {ClimbingTree} from "../Layers/ClimbingTree";
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default class Cyclofix extends Layout {
|
||||||
);
|
);
|
||||||
this.icon = "./assets/bike/logo.svg"
|
this.icon = "./assets/bike/logo.svg"
|
||||||
this.description = "Easily search and contribute bicycle data nearby";
|
this.description = "Easily search and contribute bicycle data nearby";
|
||||||
this.socialImage = "./assets/bike/cyclofix.jpeg"
|
this.socialImage = "./assets/bike/cyclofix.jpeg";
|
||||||
|
this.widenFactor = 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {Layout} from "../Layout";
|
import {Layout} from "../Layout";
|
||||||
import {GhostBike} from "../Layers/GhostBike";
|
import {GhostBike} from "../Layers/GhostBike";
|
||||||
import Combine from "../../UI/Base/Combine";
|
import Combine from "../../UI/Base/Combine";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
|
||||||
|
|
||||||
export class GhostBikes extends Layout {
|
export class GhostBikes extends Layout {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Layout} from "../Layout";
|
import {Layout} from "../Layout";
|
||||||
import {LayerDefinition} from "../LayerDefinition";
|
import {LayerDefinition} from "../LayerDefinition";
|
||||||
import {Or, Tag} from "../../Logic/TagsFilter";
|
import {Or, Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
|
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||||
|
import {TagsFilter, TagUtils} from "../Logic/Tags";
|
||||||
|
import {UIElement} from "../UI/UIElement";
|
||||||
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
|
import Translation from "../UI/i18n/Translation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around another TagDependandElement, which only shows if the filters match
|
* Wrapper around another TagDependandElement, which only shows if the filters match
|
||||||
*/
|
*/
|
||||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
|
||||||
import {TagsFilter, TagUtils} from "../Logic/TagsFilter";
|
|
||||||
import {UIElement} from "../UI/UIElement";
|
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
|
||||||
import {Changes} from "../Logic/Osm/Changes";
|
|
||||||
|
|
||||||
|
|
||||||
export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{
|
export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{
|
||||||
private _tagsFilter: TagsFilter;
|
private readonly _tagsFilter: TagsFilter;
|
||||||
private _embedded: TagDependantUIElementConstructor;
|
private readonly _embedded: TagDependantUIElementConstructor;
|
||||||
|
private readonly _invert: boolean;
|
||||||
|
|
||||||
constructor(tagsFilter : TagsFilter, embedded: TagDependantUIElementConstructor) {
|
constructor(tagsFilter: TagsFilter, embedded: TagDependantUIElementConstructor, invert: boolean = false) {
|
||||||
this._tagsFilter = tagsFilter;
|
this._tagsFilter = tagsFilter;
|
||||||
this._embedded = embedded;
|
this._embedded = embedded;
|
||||||
|
this._invert = invert;
|
||||||
}
|
}
|
||||||
|
|
||||||
construct(dependencies): TagDependantUIElement {
|
construct(dependencies): TagDependantUIElement {
|
||||||
return new OnlyShowIf(dependencies.tags,
|
return new OnlyShowIf(dependencies.tags,
|
||||||
this._embedded.construct(dependencies),
|
this._embedded.construct(dependencies),
|
||||||
this._tagsFilter);
|
this._tagsFilter,
|
||||||
|
this._invert);
|
||||||
}
|
}
|
||||||
|
|
||||||
IsKnown(properties: any): boolean {
|
IsKnown(properties: any): boolean {
|
||||||
|
@ -41,34 +43,38 @@ export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{
|
||||||
return this._embedded.Priority();
|
return this._embedded.Priority();
|
||||||
}
|
}
|
||||||
|
|
||||||
GetContent(tags: any): string {
|
GetContent(tags: any): Translation {
|
||||||
if(!this.IsKnown(tags)){
|
if(this.IsKnown(tags)){
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this._embedded.GetContent(tags);
|
return this._embedded.GetContent(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Matches(properties: any) : boolean{
|
private Matches(properties: any) : boolean{
|
||||||
return this._tagsFilter.matches(TagUtils.proprtiesToKV(properties));
|
return this._tagsFilter.matches(TagUtils.proprtiesToKV(properties)) != this._invert;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class OnlyShowIf extends UIElement implements TagDependantUIElement {
|
class OnlyShowIf extends UIElement implements TagDependantUIElement {
|
||||||
private _embedded: TagDependantUIElement;
|
private readonly _embedded: TagDependantUIElement;
|
||||||
private _filter: TagsFilter;
|
private readonly _filter: TagsFilter;
|
||||||
|
private readonly _invert: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
tags: UIEventSource<any>,
|
tags: UIEventSource<any>,
|
||||||
embedded: TagDependantUIElement, filter: TagsFilter) {
|
embedded: TagDependantUIElement,
|
||||||
|
filter: TagsFilter,
|
||||||
|
invert: boolean) {
|
||||||
super(tags);
|
super(tags);
|
||||||
this._filter = filter;
|
this._filter = filter;
|
||||||
this._embedded = embedded;
|
this._embedded = embedded;
|
||||||
|
this._invert = invert;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Matches() : boolean{
|
private Matches() : boolean{
|
||||||
return this._filter.matches(TagUtils.proprtiesToKV(this._source.data));
|
return this._filter.matches(TagUtils.proprtiesToKV(this._source.data)) != this._invert;
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Changes} from "../../Logic/Osm/Changes";
|
import {And, Tag} from "../../Logic/Tags";
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class AccessTag extends TagRenderingOptions {
|
export class AccessTag extends TagRenderingOptions {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {UIElement} from "../../UI/UIElement";
|
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
import Translation from "../../UI/i18n/Translation";
|
import Translation from "../../UI/i18n/Translation";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
import Translation from "../../UI/i18n/Translation";
|
import Translation from "../../UI/i18n/Translation";
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* One is a big 'name-question', the other is the 'edit name' in the title.
|
* One is a big 'name-question', the other is the 'edit name' in the title.
|
||||||
* THis one is the big question
|
* THis one is the big question
|
||||||
*/
|
*/
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
export class NameQuestion extends TagRenderingOptions{
|
export class NameQuestion extends TagRenderingOptions{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Changes} from "../../Logic/Osm/Changes";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Img} from "../../UI/Img";
|
import {Img} from "../../UI/Img";
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
import {Tag} from "../../Logic/Tags";
|
||||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Tag } from "../../../Logic/TagsFilter";
|
import { Tag } from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import Combine from "../../../UI/Base/Combine";
|
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Tag } from "../../../Logic/TagsFilter";
|
import { Tag } from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag, And} from "../../../Logic/TagsFilter";
|
import {Tag, And} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import Combine from "../../../UI/Base/Combine";
|
import Combine from "../../../UI/Base/Combine";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently not used in Cyclofix because it's a little vague
|
|
||||||
*
|
|
||||||
* TODO: Translations
|
|
||||||
*/
|
|
||||||
export default class BikeStationBrand extends TagRenderingOptions {
|
|
||||||
private static options = {
|
|
||||||
priority: 15,
|
|
||||||
question: "What is the brand of this bike station (name of university, shop, city...)?",
|
|
||||||
freeform: {
|
|
||||||
key: "brand",
|
|
||||||
template: "The brand of this bike station is $$$",
|
|
||||||
renderTemplate: "The brand of this bike station is {operator}",
|
|
||||||
placeholder: "brand"
|
|
||||||
},
|
|
||||||
mappings: [
|
|
||||||
{k: new Tag("brand", "Velo Fix Station"), txt: "Velo Fix Station"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw Error('BikeStationBrand disabled')
|
|
||||||
super(BikeStationBrand.options);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag, And} from "../../../Logic/TagsFilter";
|
import {Tag, And} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Tag} from "../../../Logic/TagsFilter";
|
import {Tag} from "../../../Logic/Tags";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
import {TagRenderingOptions} from "../../TagRenderingOptions";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {And, Tag, TagsFilter, TagUtils} from "../Logic/TagsFilter";
|
import {And, Tag, TagsFilter, TagUtils} from "../Logic/Tags";
|
||||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||||
import {SaveButton} from "../UI/SaveButton";
|
import {SaveButton} from "../UI/SaveButton";
|
||||||
import {VariableUiElement} from "../UI/Base/VariableUIElement";
|
import {VariableUiElement} from "../UI/Base/VariableUIElement";
|
||||||
|
@ -15,23 +15,21 @@ import Locale from "../UI/i18n/Locale";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {TagRenderingOptions} from "./TagRenderingOptions";
|
import {TagRenderingOptions} from "./TagRenderingOptions";
|
||||||
import Translation from "../UI/i18n/Translation";
|
import Translation from "../UI/i18n/Translation";
|
||||||
import {SubtleButton} from "../UI/Base/SubtleButton";
|
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
|
|
||||||
|
|
||||||
export class TagRendering extends UIElement implements TagDependantUIElement {
|
export class
|
||||||
|
TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
|
||||||
|
|
||||||
private _priority: number;
|
private readonly _priority: number;
|
||||||
|
private readonly _question: string | Translation;
|
||||||
|
private readonly _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[];
|
||||||
private _question: string | Translation;
|
|
||||||
private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[];
|
|
||||||
|
|
||||||
private currentTags : UIEventSource<any> ;
|
private currentTags : UIEventSource<any> ;
|
||||||
|
|
||||||
|
|
||||||
private _freeform: {
|
private readonly _freeform: {
|
||||||
key: string,
|
key: string,
|
||||||
template: string | UIElement,
|
template: string | UIElement,
|
||||||
renderTemplate: string | Translation,
|
renderTemplate: string | Translation,
|
||||||
|
@ -54,9 +52,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
|
||||||
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
private static injected = TagRendering.injectFunction();
|
|
||||||
|
|
||||||
static injectFunction() {
|
static injectFunction() {
|
||||||
// This is a workaround as not to import tagrendering into TagREnderingOptions
|
// This is a workaround as not to import tagrendering into TagREnderingOptions
|
||||||
TagRenderingOptions.tagRendering = (tags, options) => new TagRendering(tags, options);
|
TagRenderingOptions.tagRendering = (tags, options) => new TagRendering(tags, options);
|
||||||
|
@ -76,7 +71,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
extraTags?: TagsFilter,
|
extraTags?: TagsFilter,
|
||||||
},
|
},
|
||||||
tagsPreprocessor?: ((tags: any) => any),
|
tagsPreprocessor?: ((tags: any) => any),
|
||||||
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean }[]
|
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[]
|
||||||
}) {
|
}) {
|
||||||
super(tags);
|
super(tags);
|
||||||
this.ListenTo(Locale.language);
|
this.ListenTo(Locale.language);
|
||||||
|
@ -204,7 +199,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
placeholder?: string | Translation,
|
placeholder?: string | Translation,
|
||||||
extraTags?: TagsFilter,
|
extraTags?: TagsFilter,
|
||||||
},
|
},
|
||||||
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean }[]
|
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[]
|
||||||
}):
|
}):
|
||||||
InputElement<TagsFilter> {
|
InputElement<TagsFilter> {
|
||||||
|
|
||||||
|
@ -217,7 +212,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
if(mapping.k === null){
|
if(mapping.k === null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(previousTexts.indexOf(mapping.txt) >= 0){
|
if(mapping.hideInAnswer){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
previousTexts.push(this.ApplyTemplate(mapping.txt));
|
previousTexts.push(this.ApplyTemplate(mapping.txt));
|
||||||
|
@ -262,7 +257,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
|
||||||
let isValid = ValidatedTextField.inputValidation[type];
|
let isValid = ValidatedTextField.inputValidation[type];
|
||||||
if (isValid === undefined) {
|
if (isValid === undefined) {
|
||||||
isValid = (str) => true;
|
isValid = () => true;
|
||||||
}
|
}
|
||||||
let formatter = ValidatedTextField.formatting[type] ?? ((str) => str);
|
let formatter = ValidatedTextField.formatting[type] ?? ((str) => str);
|
||||||
|
|
||||||
|
@ -297,7 +292,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let inputElement: InputElement<TagsFilter>;
|
|
||||||
const textField = new TextField({
|
const textField = new TextField({
|
||||||
placeholder: this._freeform.placeholder,
|
placeholder: this._freeform.placeholder,
|
||||||
fromString: pickString,
|
fromString: pickString,
|
||||||
|
@ -455,9 +449,9 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
|
||||||
private ApplyTemplate(template: string | Translation): UIElement {
|
private ApplyTemplate(template: string | Translation): UIElement {
|
||||||
if (template === undefined || template === null) {
|
if (template === undefined || template === null) {
|
||||||
throw "Trying to apply a template, but the template is null/undefined"
|
console.warn("Applying template which is undefined by ",this); // TODO THis error msg can probably be removed
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
const self = this;
|
|
||||||
return new VariableUiElement(this.currentTags.map(tags => {
|
return new VariableUiElement(this.currentTags.map(tags => {
|
||||||
const tr = Translations.WT(template);
|
const tr = Translations.WT(template);
|
||||||
if (tr.Subs === undefined) {
|
if (tr.Subs === undefined) {
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||||
import * as EmailValidator from "email-validator";
|
import {TagsFilter, TagUtils} from "../Logic/Tags";
|
||||||
import {parsePhoneNumberFromString} from "libphonenumber-js";
|
|
||||||
import {UIElement} from "../UI/UIElement";
|
|
||||||
import {TagsFilter, TagUtils} from "../Logic/TagsFilter";
|
|
||||||
import {OnlyShowIfConstructor} from "./OnlyShowIf";
|
import {OnlyShowIfConstructor} from "./OnlyShowIf";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import Translation from "../UI/i18n/Translation";
|
import Translation from "../UI/i18n/Translation";
|
||||||
|
import Translations from "../UI/i18n/Translations";
|
||||||
|
|
||||||
export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notes: by not giving a 'question', one disables the question form alltogether
|
* Notes: by not giving a 'question', one disables the question form alltogether
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public options: {
|
public options: {
|
||||||
priority?: number;
|
priority?: number;
|
||||||
question?: string | Translation;
|
question?: string | Translation;
|
||||||
|
@ -27,10 +21,9 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||||
placeholder?: string | Translation;
|
placeholder?: string | Translation;
|
||||||
extraTags?: TagsFilter
|
extraTags?: TagsFilter
|
||||||
};
|
};
|
||||||
mappings?: { k: TagsFilter; txt: string | Translation; priority?: number, substitute?: boolean }[]
|
mappings?: { k: TagsFilter; txt: string | Translation; priority?: number, substitute?: boolean, hideInAnwser?: boolean }[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,7 +54,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
mappings?: { k: TagsFilter, txt: Translation | string, priority?: number, substitute?: boolean }[],
|
mappings?: { k: TagsFilter, txt: Translation | string, priority?: number, substitute?: boolean , hideInAnswer?:boolean}[],
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,12 +78,11 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||||
*/
|
*/
|
||||||
tagsPreprocessor?: ((tags: any) => void)
|
tagsPreprocessor?: ((tags: any) => void)
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnlyShowIf(tagsFilter: TagsFilter): TagDependantUIElementConstructor {
|
OnlyShowIf(tagsFilter: TagsFilter, invert: boolean = false): TagDependantUIElementConstructor {
|
||||||
return new OnlyShowIfConstructor(tagsFilter, this);
|
return new OnlyShowIfConstructor(tagsFilter, this, invert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,39 +97,28 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||||
if (this.options.freeform !== undefined && tags[this.options.freeform.key] !== undefined) {
|
if (this.options.freeform !== undefined && tags[this.options.freeform.key] !== undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.options.question === undefined) {
|
return this.options.question !== undefined;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetContent(tags: any): string {
|
GetContent(tags: any): Translation {
|
||||||
const tagsKV = TagUtils.proprtiesToKV(tags);
|
const tagsKV = TagUtils.proprtiesToKV(tags);
|
||||||
|
|
||||||
for (const oneOnOneElement of this.options.mappings ?? []) {
|
for (const oneOnOneElement of this.options.mappings ?? []) {
|
||||||
if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tagsKV)) {
|
if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tagsKV)) {
|
||||||
const mapping = oneOnOneElement.txt;
|
return Translations.WT(oneOnOneElement.txt);
|
||||||
if (typeof (mapping) === "string") {
|
|
||||||
return mapping;
|
|
||||||
} else {
|
|
||||||
return mapping.InnerRender();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.options.freeform !== undefined) {
|
if (this.options.freeform !== undefined) {
|
||||||
let template = this.options.freeform.renderTemplate;
|
let template = Translations.WT(this.options.freeform.renderTemplate);
|
||||||
if (typeof (template) !== "string") {
|
return template.Subs(tags);
|
||||||
template = template.InnerRender();
|
|
||||||
}
|
|
||||||
return TagUtils.ApplyTemplate(template, tags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn("No content defined for",tags," with mapping",this);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static tagRendering: (tags: UIEventSource<any>, options: { priority?: number; question?: string | Translation; freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string | Translation; renderTemplate: string | Translation; placeholder?: string | Translation; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string | Translation; priority?: number; substitute?: boolean }[] }) => TagDependantUIElement;
|
public static tagRendering: (tags: UIEventSource<any>, options: { priority?: number; question?: string | Translation; freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string | Translation; renderTemplate: string | Translation; placeholder?: string | Translation; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string | Translation; priority?: number; substitute?: boolean, hideInAnswer?: boolean }[] }) => TagDependantUIElement;
|
||||||
|
|
||||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||||
return TagRenderingOptions.tagRendering(dependencies.tags, this.options);
|
return TagRenderingOptions.tagRendering(dependencies.tags, this.options);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
|
import Translation from "../UI/i18n/Translation";
|
||||||
|
|
||||||
|
|
||||||
export interface Dependencies {
|
export interface Dependencies {
|
||||||
|
@ -12,7 +13,7 @@ export interface TagDependantUIElementConstructor {
|
||||||
IsKnown(properties: any): boolean;
|
IsKnown(properties: any): boolean;
|
||||||
IsQuestioning(properties: any): boolean;
|
IsQuestioning(properties: any): boolean;
|
||||||
Priority(): number;
|
Priority(): number;
|
||||||
GetContent(tags: any): string;
|
GetContent(tags: any): Translation;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {Layout} from "./Customizations/Layout";
|
|
||||||
import Locale from "./UI/i18n/Locale";
|
|
||||||
import Translations from "./UI/i18n/Translations";
|
import Translations from "./UI/i18n/Translations";
|
||||||
import {TabbedComponent} from "./UI/Base/TabbedComponent";
|
import {TabbedComponent} from "./UI/Base/TabbedComponent";
|
||||||
import {ShareScreen} from "./UI/ShareScreen";
|
import {ShareScreen} from "./UI/ShareScreen";
|
||||||
|
@ -8,12 +6,8 @@ import {CheckBox} from "./UI/Input/CheckBox";
|
||||||
import Combine from "./UI/Base/Combine";
|
import Combine from "./UI/Base/Combine";
|
||||||
import {UIElement} from "./UI/UIElement";
|
import {UIElement} from "./UI/UIElement";
|
||||||
import {MoreScreen} from "./UI/MoreScreen";
|
import {MoreScreen} from "./UI/MoreScreen";
|
||||||
import {Tag} from "./Logic/TagsFilter";
|
|
||||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||||
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
|
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
|
||||||
import {ElementStorage} from "./Logic/ElementStorage";
|
|
||||||
import {Changes} from "./Logic/Osm/Changes";
|
|
||||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
|
||||||
import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
|
import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
|
||||||
import {State} from "./State";
|
import {State} from "./State";
|
||||||
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
||||||
|
@ -50,30 +44,30 @@ export class InitUiElements {
|
||||||
|
|
||||||
const layoutToUse = State.state.layoutToUse.data;
|
const layoutToUse = State.state.layoutToUse.data;
|
||||||
let welcome: UIElement = new WelcomeMessage();
|
let welcome: UIElement = new WelcomeMessage();
|
||||||
if (layoutToUse.name === PersonalLayout.NAME) {
|
if (layoutToUse.id === PersonalLayout.NAME) {
|
||||||
welcome = new PersonalLayersPanel();
|
welcome = new PersonalLayersPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{header: Img.AsImageElement(layoutToUse.icon), content: welcome},
|
{header: Img.AsImageElement(layoutToUse.icon), content: welcome},
|
||||||
{header: `<img src='${'./assets/osm-logo.svg'}'>`, content: Translations.t.general.openStreetMapIntro},
|
{header: `<img src='./assets/osm-logo.svg'>`, content: Translations.t.general.openStreetMapIntro},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if (State.state.featureSwitchShareScreen.data) {
|
if (State.state.featureSwitchShareScreen.data) {
|
||||||
tabs.push({header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen()});
|
tabs.push({header: `<img src='./assets/share.svg'>`, content: new ShareScreen()});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (State.state.featureSwitchMoreQuests.data){
|
if (State.state.featureSwitchMoreQuests.data){
|
||||||
|
|
||||||
tabs.push({
|
tabs.push({
|
||||||
header: `<img src='${'./assets/add.svg'}'>`
|
header: `<img src='./assets/add.svg'>`
|
||||||
, content: new MoreScreen()
|
, content: new MoreScreen()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const fullOptions = new TabbedComponent(tabs);
|
const fullOptions = new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab);
|
||||||
|
|
||||||
return fullOptions;
|
return fullOptions;
|
||||||
|
|
||||||
|
@ -94,7 +88,6 @@ export class InitUiElements {
|
||||||
new Combine(["<span class='open-button'>", help, "</span>"])
|
new Combine(["<span class='open-button'>", help, "</span>"])
|
||||||
, true
|
, true
|
||||||
).AttachTo("messagesbox");
|
).AttachTo("messagesbox");
|
||||||
let dontCloseYet = true;
|
|
||||||
const openedTime = new Date().getTime();
|
const openedTime = new Date().getTime();
|
||||||
State.state.locationControl.addCallback(() => {
|
State.state.locationControl.addCallback(() => {
|
||||||
if (new Date().getTime() - openedTime < 15 * 1000) {
|
if (new Date().getTime() - openedTime < 15 * 1000) {
|
||||||
|
@ -107,7 +100,7 @@ export class InitUiElements {
|
||||||
|
|
||||||
const fullOptions2 = this.CreateWelcomePane();
|
const fullOptions2 = this.CreateWelcomePane();
|
||||||
State.state.fullScreenMessage.setData(fullOptions2)
|
State.state.fullScreenMessage.setData(fullOptions2)
|
||||||
new FixedUiElement(`<div class='collapse-button-img' class="shadow"><img src='assets/help.svg' alt='help'></div>`).onClick(() => {
|
new FixedUiElement(`<div class='collapse-button-img shadow'><img src='assets/help.svg' alt='help'></div>`).onClick(() => {
|
||||||
State.state.fullScreenMessage.setData(fullOptions2)
|
State.state.fullScreenMessage.setData(fullOptions2)
|
||||||
}).AttachTo("help-button-mobile");
|
}).AttachTo("help-button-mobile");
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,4 @@ export class ElementStorage {
|
||||||
}
|
}
|
||||||
console.log("Can not find eventsource with id ", elementId);
|
console.log("Can not find eventsource with id ", elementId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
removeId(oldId: string) {
|
|
||||||
delete this._elements[oldId];
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {TagsFilter, TagUtils} from "./TagsFilter";
|
import {TagsFilter, TagUtils} from "./Tags";
|
||||||
import {UIEventSource} from "./UIEventSource";
|
import {UIEventSource} from "./UIEventSource";
|
||||||
import L from "leaflet"
|
import L from "leaflet"
|
||||||
import {GeoOperations} from "./GeoOperations";
|
import {GeoOperations} from "./GeoOperations";
|
||||||
|
@ -21,6 +21,7 @@ export class FilteredLayer {
|
||||||
public readonly name: string | UIElement;
|
public readonly name: string | UIElement;
|
||||||
public readonly filters: TagsFilter;
|
public readonly filters: TagsFilter;
|
||||||
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
|
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
|
||||||
|
private readonly combinedIsDisplayed : UIEventSource<boolean>;
|
||||||
public readonly layerDef: LayerDefinition;
|
public readonly layerDef: LayerDefinition;
|
||||||
private readonly _maxAllowedOverlap: number;
|
private readonly _maxAllowedOverlap: number;
|
||||||
|
|
||||||
|
@ -29,8 +30,8 @@ export class FilteredLayer {
|
||||||
|
|
||||||
/** The featurecollection from overpass
|
/** The featurecollection from overpass
|
||||||
*/
|
*/
|
||||||
private _dataFromOverpass : any[];
|
private _dataFromOverpass: any[];
|
||||||
private _wayHandling: number;
|
private readonly _wayHandling: number;
|
||||||
/** List of new elements, geojson features
|
/** List of new elements, geojson features
|
||||||
*/
|
*/
|
||||||
private _newElements = [];
|
private _newElements = [];
|
||||||
|
@ -60,7 +61,12 @@ export class FilteredLayer {
|
||||||
this.filters = layerDef.overpassFilter;
|
this.filters = layerDef.overpassFilter;
|
||||||
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
|
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
|
||||||
const self = this;
|
const self = this;
|
||||||
this.isDisplayed.addCallback(function (isDisplayed) {
|
this.combinedIsDisplayed = this.isDisplayed.map<boolean>(isDisplayed => {
|
||||||
|
return isDisplayed && State.state.locationControl.data.zoom >= self.layerDef.minzoom
|
||||||
|
},
|
||||||
|
[State.state.locationControl]
|
||||||
|
);
|
||||||
|
this.combinedIsDisplayed.addCallback(function (isDisplayed) {
|
||||||
const map = State.state.bm.map;
|
const map = State.state.bm.map;
|
||||||
if (self._geolayer !== undefined && self._geolayer !== null) {
|
if (self._geolayer !== undefined && self._geolayer !== null) {
|
||||||
if (isDisplayed) {
|
if (isDisplayed) {
|
||||||
|
@ -91,7 +97,8 @@ export class FilteredLayer {
|
||||||
const selfFeatures = [];
|
const selfFeatures = [];
|
||||||
for (let feature of geojson.features) {
|
for (let feature of geojson.features) {
|
||||||
// feature.properties contains all the properties
|
// feature.properties contains all the properties
|
||||||
var tags = TagUtils.proprtiesToKV(feature.properties);
|
const tags = TagUtils.proprtiesToKV(feature.properties);
|
||||||
|
|
||||||
if (this.filters.matches(tags)) {
|
if (this.filters.matches(tags)) {
|
||||||
const centerPoint = GeoOperations.centerpoint(feature);
|
const centerPoint = GeoOperations.centerpoint(feature);
|
||||||
feature.properties["_surface"] = ""+GeoOperations.surfaceAreaInSqMeters(feature);
|
feature.properties["_surface"] = ""+GeoOperations.surfaceAreaInSqMeters(feature);
|
||||||
|
@ -204,7 +211,6 @@ export class FilteredLayer {
|
||||||
style: function (feature) {
|
style: function (feature) {
|
||||||
return self._style(feature.properties);
|
return self._style(feature.properties);
|
||||||
},
|
},
|
||||||
|
|
||||||
pointToLayer: function (feature, latLng) {
|
pointToLayer: function (feature, latLng) {
|
||||||
const style = self._style(feature.properties);
|
const style = self._style(feature.properties);
|
||||||
let marker;
|
let marker;
|
||||||
|
@ -231,7 +237,7 @@ export class FilteredLayer {
|
||||||
const uiElement = self._showOnPopup(eventSource, feature);
|
const uiElement = self._showOnPopup(eventSource, feature);
|
||||||
const popup = L.popup({}, marker).setContent(uiElement.Render());
|
const popup = L.popup({}, marker).setContent(uiElement.Render());
|
||||||
marker.bindPopup(popup)
|
marker.bindPopup(popup)
|
||||||
.on("popupopen", (popup) => {
|
.on("popupopen", () => {
|
||||||
uiElement.Activate();
|
uiElement.Activate();
|
||||||
uiElement.Update();
|
uiElement.Update();
|
||||||
});
|
});
|
||||||
|
@ -264,7 +270,7 @@ export class FilteredLayer {
|
||||||
eventSource.addCallback(feature.updateStyle);
|
eventSource.addCallback(feature.updateStyle);
|
||||||
|
|
||||||
layer.on("click", function (e) {
|
layer.on("click", function (e) {
|
||||||
const prevSelectedElement = State.state.selectedElement.data?.feature.updateStyle();
|
State.state.selectedElement.data?.feature.updateStyle();
|
||||||
State.state.selectedElement.setData({feature: feature});
|
State.state.selectedElement.setData({feature: feature});
|
||||||
feature.updateStyle()
|
feature.updateStyle()
|
||||||
if (feature.geometry.type === "Point") {
|
if (feature.geometry.type === "Point") {
|
||||||
|
@ -273,12 +279,12 @@ export class FilteredLayer {
|
||||||
|
|
||||||
const uiElement = self._showOnPopup(eventSource, feature);
|
const uiElement = self._showOnPopup(eventSource, feature);
|
||||||
|
|
||||||
const popup = L.popup({
|
L.popup({
|
||||||
autoPan: true,
|
autoPan: true,
|
||||||
})
|
}).setContent(uiElement.Render())
|
||||||
.setContent(uiElement.Render())
|
|
||||||
.setLatLng(e.latlng)
|
.setLatLng(e.latlng)
|
||||||
.openOn(State.state.bm.map);
|
.openOn(State.state.bm.map);
|
||||||
|
|
||||||
uiElement.Update();
|
uiElement.Update();
|
||||||
uiElement.Activate();
|
uiElement.Activate();
|
||||||
L.DomEvent.stop(e); // Marks the event as consumed
|
L.DomEvent.stop(e); // Marks the event as consumed
|
||||||
|
@ -286,7 +292,7 @@ export class FilteredLayer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isDisplayed.data) {
|
if (this.combinedIsDisplayed.data) {
|
||||||
this._geolayer.addTo(State.state.bm.map);
|
this._geolayer.addTo(State.state.bm.map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,37 +77,6 @@ export class GeoOperations {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple check: that every point of the polygon is inside the container
|
|
||||||
* @param polygon
|
|
||||||
* @param container
|
|
||||||
*/
|
|
||||||
private static isPolygonInside(polygon, container) {
|
|
||||||
for (const coor of polygon.geometry.coordinates[0]) {
|
|
||||||
if (!GeoOperations.inside(coor, container)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple check: one point of the polygon is inside the container
|
|
||||||
* @param polygon
|
|
||||||
* @param container
|
|
||||||
*/
|
|
||||||
private static isPolygonTouching(polygon, container) {
|
|
||||||
for (const coor of polygon.geometry.coordinates[0]) {
|
|
||||||
if (GeoOperations.inside(coor, container)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static inside(pointCoordinate, feature): boolean {
|
private static inside(pointCoordinate, feature): boolean {
|
||||||
// ray-casting algorithm based on
|
// ray-casting algorithm based on
|
||||||
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||||
|
@ -124,7 +93,7 @@ export class GeoOperations {
|
||||||
let poly = feature.geometry.coordinates[0];
|
let poly = feature.geometry.coordinates[0];
|
||||||
|
|
||||||
var inside = false;
|
var inside = false;
|
||||||
for (var i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
||||||
const coori = poly[i];
|
const coori = poly[i];
|
||||||
const coorj = poly[j];
|
const coorj = poly[j];
|
||||||
|
|
||||||
|
@ -133,7 +102,7 @@ export class GeoOperations {
|
||||||
const xj = coorj[0];
|
const xj = coorj[0];
|
||||||
const yj = coorj[1];
|
const yj = coorj[1];
|
||||||
|
|
||||||
var intersect = ((yi > y) != (yj > y))
|
const intersect = ((yi > y) != (yj > y))
|
||||||
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
||||||
if (intersect) {
|
if (intersect) {
|
||||||
inside = !inside;
|
inside = !inside;
|
||||||
|
@ -146,7 +115,7 @@ export class GeoOperations {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BBox {
|
class BBox{
|
||||||
|
|
||||||
readonly maxLat: number;
|
readonly maxLat: number;
|
||||||
readonly maxLon: number;
|
readonly maxLon: number;
|
||||||
|
@ -188,10 +157,8 @@ class BBox {
|
||||||
if (this.minLon > other.maxLon) {
|
if (this.minLon > other.maxLon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.minLat > other.maxLat) {
|
return this.minLat <= other.maxLat;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get(feature) {
|
static get(feature) {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import {WikimediaImage} from "../UI/Image/WikimediaImage";
|
import {WikimediaImage} from "../UI/Image/WikimediaImage";
|
||||||
import {SimpleImageElement} from "../UI/Image/SimpleImageElement";
|
import {SimpleImageElement} from "../UI/Image/SimpleImageElement";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import {Changes} from "./Osm/Changes";
|
|
||||||
import {ImgurImage} from "../UI/Image/ImgurImage";
|
import {ImgurImage} from "../UI/Image/ImgurImage";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {ImagesInCategory, Wikidata, Wikimedia} from "./Web/Wikimedia";
|
import {ImagesInCategory, Wikidata, Wikimedia} from "./Web/Wikimedia";
|
||||||
import {UIEventSource} from "./UIEventSource";
|
import {UIEventSource} from "./UIEventSource";
|
||||||
import {Tag} from "./TagsFilter";
|
import {Tag} from "./Tags";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are multiple way to fetch images for an object
|
* There are multiple way to fetch images for an object
|
||||||
|
@ -150,7 +149,6 @@ export class ImageSearcher extends UIEventSource<string[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in this._tags.data) {
|
for (const key in this._tags.data) {
|
||||||
// @ts-ignore
|
|
||||||
if (key.startsWith("image:")) {
|
if (key.startsWith("image:")) {
|
||||||
const url = this._tags.data[key]
|
const url = this._tags.data[key]
|
||||||
this.AddImage(url);
|
this.AddImage(url);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import {Or, TagsFilter} from "./TagsFilter";
|
import {Or, TagsFilter} from "./Tags";
|
||||||
import {UIEventSource} from "./UIEventSource";
|
import {UIEventSource} from "./UIEventSource";
|
||||||
import {FilteredLayer} from "./FilteredLayer";
|
import {FilteredLayer} from "./FilteredLayer";
|
||||||
import {Bounds} from "./Bounds";
|
import {Bounds} from "./Bounds";
|
||||||
import {Overpass} from "./Osm/Overpass";
|
import {Overpass} from "./Osm/Overpass";
|
||||||
import {Basemap} from "./Leaflet/Basemap";
|
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
|
|
||||||
export class LayerUpdater {
|
export class LayerUpdater {
|
||||||
|
@ -18,20 +17,20 @@ export class LayerUpdater {
|
||||||
* If the map location changes, we check for each layer if it is loaded:
|
* If the map location changes, we check for each layer if it is loaded:
|
||||||
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
|
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
|
||||||
*/
|
*/
|
||||||
private previousBounds: Map<number, Bounds[]> = new Map<number, Bounds[]>();
|
private readonly previousBounds: Map<number, Bounds[]> = new Map<number, Bounds[]>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The most important layer should go first, as that one gets first pick for the questions
|
* The most important layer should go first, as that one gets first pick for the questions
|
||||||
* @param map
|
|
||||||
* @param minzoom
|
|
||||||
* @param layers
|
|
||||||
*/
|
*/
|
||||||
constructor(state: State) {
|
constructor(state: State) {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom));
|
this.sufficentlyZoomed = State.state.locationControl.map(location => {
|
||||||
this.sufficentlyZoomed = State.state.locationControl.map(location => location.zoom >= minzoom);
|
let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom ?? 18));
|
||||||
|
return location.zoom >= minzoom;
|
||||||
|
}, [state.layoutToUse]
|
||||||
|
);
|
||||||
for (let i = 0; i < 25; i++) {
|
for (let i = 0; i < 25; i++) {
|
||||||
// This update removes all data on all layers -> erase the map on lower levels too
|
// This update removes all data on all layers -> erase the map on lower levels too
|
||||||
this.previousBounds.set(i, []);
|
this.previousBounds.set(i, []);
|
||||||
|
@ -47,7 +46,7 @@ export class LayerUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
private GetFilter(state: State) {
|
private GetFilter(state: State) {
|
||||||
var filters: TagsFilter[] = [];
|
const filters: TagsFilter[] = [];
|
||||||
state = state ?? State.state;
|
state = state ?? State.state;
|
||||||
for (const layer of state.layoutToUse.data.layers) {
|
for (const layer of state.layoutToUse.data.layers) {
|
||||||
if (state.locationControl.data.zoom < layer.minzoom) {
|
if (state.locationControl.data.zoom < layer.minzoom) {
|
||||||
|
@ -142,15 +141,14 @@ export class LayerUpdater {
|
||||||
const w = Math.max(-180, bounds.getWest() - diff);
|
const w = Math.max(-180, bounds.getWest() - diff);
|
||||||
const queryBounds = {north: n, east: e, south: s, west: w};
|
const queryBounds = {north: n, east: e, south: s, west: w};
|
||||||
|
|
||||||
const z = state.locationControl.data.zoom;
|
const z = Math.floor(state.locationControl.data.zoom);
|
||||||
|
|
||||||
this.previousBounds.get(z).push(queryBounds);
|
|
||||||
|
|
||||||
this.runningQuery.setData(true);
|
this.runningQuery.setData(true);
|
||||||
const self = this;
|
const self = this;
|
||||||
const overpass = new Overpass(filter);
|
const overpass = new Overpass(filter);
|
||||||
overpass.queryGeoJson(queryBounds,
|
overpass.queryGeoJson(queryBounds,
|
||||||
function (data) {
|
function (data) {
|
||||||
|
self.previousBounds.get(z).push(queryBounds);
|
||||||
self.handleData(data)
|
self.handleData(data)
|
||||||
},
|
},
|
||||||
function (reason) {
|
function (reason) {
|
||||||
|
@ -162,33 +160,21 @@ export class LayerUpdater {
|
||||||
|
|
||||||
|
|
||||||
private IsInBounds(state: State, bounds: Bounds): boolean {
|
private IsInBounds(state: State, bounds: Bounds): boolean {
|
||||||
|
|
||||||
if (this.previousBounds === undefined) {
|
if (this.previousBounds === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const b = state.bm.map.getBounds();
|
const b = state.bm.map.getBounds();
|
||||||
if (b.getSouth() < bounds.south) {
|
return b.getSouth() >= bounds.south &&
|
||||||
return false;
|
b.getNorth() <= bounds.north &&
|
||||||
}
|
b.getEast() <= bounds.east &&
|
||||||
|
b.getWest() >= bounds.west;
|
||||||
if (b.getNorth() > bounds.north) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.getEast() > bounds.east) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (b.getWest() < bounds.west) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ForceRefresh(){
|
public ForceRefresh() {
|
||||||
this.previousBounds = undefined;
|
for (let i = 0; i < 25; i++) {
|
||||||
|
this.previousBounds.set(i, []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -60,12 +60,12 @@ export class Basemap {
|
||||||
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public map: Map;
|
public readonly map: Map;
|
||||||
|
|
||||||
public Location: UIEventSource<{ zoom: number, lat: number, lon: number }>;
|
public readonly Location: UIEventSource<{ zoom: number, lat: number, lon: number }>;
|
||||||
public LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
|
public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
|
||||||
private _previousLayer: L.tileLayer = undefined;
|
private _previousLayer: L.tileLayer = undefined;
|
||||||
public CurrentLayer: UIEventSource<{
|
public readonly CurrentLayer: UIEventSource<{
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
layer: L.tileLayer
|
layer: L.tileLayer
|
||||||
|
|
|
@ -7,10 +7,10 @@ import {Basemap} from "./Basemap";
|
||||||
|
|
||||||
export class GeoLocationHandler extends UIElement {
|
export class GeoLocationHandler extends UIElement {
|
||||||
|
|
||||||
private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
private _permission: UIEventSource<string> = new UIEventSource<string>("");
|
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
|
||||||
private _marker: any;
|
private _marker: any;
|
||||||
private _hasLocation: UIEventSource<boolean>;
|
private readonly _hasLocation: UIEventSource<boolean>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(undefined);
|
super(undefined);
|
||||||
|
@ -84,13 +84,13 @@ export class GeoLocationHandler extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._hasLocation.data) {
|
if (this._hasLocation.data) {
|
||||||
return "<img src='assets/crosshair-blue.svg' alt='locate me'>";
|
return "<img src='./assets/crosshair-blue.svg' alt='locate me'>";
|
||||||
}
|
}
|
||||||
if (this._isActive.data) {
|
if (this._isActive.data) {
|
||||||
return "<img src='assets/crosshair-blue-center.svg' alt='locate me'>";
|
return "<img src='./assets/crosshair-blue-center.svg' alt='locate me'>";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "<img src='assets/crosshair.svg' alt='locate me'>";
|
return "<img src='./assets/crosshair.svg' alt='locate me'>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import {Basemap} from "./Basemap";
|
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
|
||||||
import {UIElement} from "../../UI/UIElement";
|
import {UIElement} from "../../UI/UIElement";
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
* Handles all changes made to OSM.
|
* Handles all changes made to OSM.
|
||||||
* Needs an authenticator via OsmConnection
|
* Needs an authenticator via OsmConnection
|
||||||
*/
|
*/
|
||||||
import {UIEventSource} from "../UIEventSource";
|
|
||||||
import {OsmConnection} from "./OsmConnection";
|
|
||||||
import {OsmNode, OsmObject} from "./OsmObject";
|
import {OsmNode, OsmObject} from "./OsmObject";
|
||||||
import {And, Tag, TagsFilter} from "../TagsFilter";
|
import {And, Tag, TagsFilter} from "../Tags";
|
||||||
import {ElementStorage} from "../ElementStorage";
|
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
|
||||||
|
@ -163,6 +160,8 @@ export class Changes {
|
||||||
console.log("Beginning upload...");
|
console.log("Beginning upload...");
|
||||||
// At last, we build the changeset and upload
|
// At last, we build the changeset and upload
|
||||||
State.state.osmConnection.UploadChangeset(
|
State.state.osmConnection.UploadChangeset(
|
||||||
|
State.state.layoutToUse.data,
|
||||||
|
State.state.allElements,
|
||||||
function (csId) {
|
function (csId) {
|
||||||
|
|
||||||
let modifications = "";
|
let modifications = "";
|
||||||
|
@ -190,11 +189,10 @@ export class Changes {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifications.length > 0) {
|
if (modifications.length > 0) {
|
||||||
|
|
||||||
changes +=
|
changes +=
|
||||||
"<modify>" +
|
"<modify>\n" +
|
||||||
modifications +
|
modifications +
|
||||||
"</modify>";
|
"\n</modify>";
|
||||||
}
|
}
|
||||||
|
|
||||||
changes += "</osmChange>";
|
changes += "</osmChange>";
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {State} from "../../State";
|
|
||||||
import {OsmConnection, UserDetails} from "./OsmConnection";
|
import {OsmConnection, UserDetails} from "./OsmConnection";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import {ElementStorage} from "../ElementStorage";
|
||||||
|
import {Layout} from "../../Customizations/Layout";
|
||||||
|
import {State} from "../../State";
|
||||||
|
|
||||||
export class ChangesetHandler {
|
export class ChangesetHandler {
|
||||||
|
|
||||||
|
@ -22,11 +24,14 @@ export class ChangesetHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public UploadChangeset(generateChangeXML: (csid: string) => string,
|
public UploadChangeset(
|
||||||
continuation: () => void) {
|
layout: Layout,
|
||||||
|
allElements: ElementStorage,
|
||||||
|
generateChangeXML: (csid: string) => string,
|
||||||
|
continuation: () => void) {
|
||||||
|
|
||||||
if (this._dryRun) {
|
if (this._dryRun) {
|
||||||
var changesetXML = generateChangeXML("123456");
|
const changesetXML = generateChangeXML("123456");
|
||||||
console.log(changesetXML);
|
console.log(changesetXML);
|
||||||
continuation();
|
continuation();
|
||||||
return;
|
return;
|
||||||
|
@ -34,14 +39,14 @@ export class ChangesetHandler {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
|
||||||
if (this.currentChangeset.data === undefined || this.currentChangeset.data === "") {
|
if (this.currentChangeset.data === undefined || this.currentChangeset.data === "") {
|
||||||
// We have to open a new changeset
|
// We have to open a new changeset
|
||||||
this.OpenChangeset((csId) => {
|
this.OpenChangeset(layout,(csId) => {
|
||||||
this.currentChangeset.setData(csId);
|
this.currentChangeset.setData(csId);
|
||||||
const changeset = generateChangeXML(csId);
|
const changeset = generateChangeXML(csId);
|
||||||
console.log(changeset);
|
console.log(changeset);
|
||||||
self.AddChange(csId, changeset,
|
self.AddChange(csId, changeset,
|
||||||
|
allElements,
|
||||||
() => {
|
() => {
|
||||||
},
|
},
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -55,6 +60,7 @@ export class ChangesetHandler {
|
||||||
self.AddChange(
|
self.AddChange(
|
||||||
csId,
|
csId,
|
||||||
generateChangeXML(csId),
|
generateChangeXML(csId),
|
||||||
|
allElements,
|
||||||
() => {
|
() => {
|
||||||
},
|
},
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -62,7 +68,7 @@ export class ChangesetHandler {
|
||||||
// Mark the CS as closed...
|
// Mark the CS as closed...
|
||||||
this.currentChangeset.setData("");
|
this.currentChangeset.setData("");
|
||||||
// ... and try again. As the cs is closed, no recursive loop can exist
|
// ... and try again. As the cs is closed, no recursive loop can exist
|
||||||
self.UploadChangeset(generateChangeXML, continuation);
|
self.UploadChangeset(layout, allElements, generateChangeXML, continuation);
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -71,9 +77,10 @@ export class ChangesetHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private OpenChangeset(continuation: (changesetId: string) => void) {
|
private OpenChangeset(
|
||||||
|
layout : Layout,
|
||||||
|
continuation: (changesetId: string) => void) {
|
||||||
|
|
||||||
const layout = State.state.layoutToUse.data;
|
|
||||||
const commentExtra = layout.changesetMessage !== undefined? " - "+layout.changesetMessage : "";
|
const commentExtra = layout.changesetMessage !== undefined? " - "+layout.changesetMessage : "";
|
||||||
this.auth.xhr({
|
this.auth.xhr({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
@ -81,8 +88,8 @@ export class ChangesetHandler {
|
||||||
options: {header: {'Content-Type': 'text/xml'}},
|
options: {header: {'Content-Type': 'text/xml'}},
|
||||||
content: [`<osm><changeset>`,
|
content: [`<osm><changeset>`,
|
||||||
`<tag k="created_by" v="MapComplete ${State.vNumber}" />`,
|
`<tag k="created_by" v="MapComplete ${State.vNumber}" />`,
|
||||||
`<tag k="comment" v="Adding data with #MapComplete for theme #${layout.name}${commentExtra}"/>`,
|
`<tag k="comment" v="Adding data with #MapComplete for theme #${layout.id}${commentExtra}"/>`,
|
||||||
`<tag k="theme" v="${layout.name}"/>`,
|
`<tag k="theme" v="${layout.id}"/>`,
|
||||||
layout.maintainer !== undefined ? `<tag k="theme-creator" v="${layout.maintainer}"/>` : "",
|
layout.maintainer !== undefined ? `<tag k="theme-creator" v="${layout.maintainer}"/>` : "",
|
||||||
`</changeset></osm>`].join("")
|
`</changeset></osm>`].join("")
|
||||||
}, function (err, response) {
|
}, function (err, response) {
|
||||||
|
@ -98,6 +105,7 @@ export class ChangesetHandler {
|
||||||
|
|
||||||
private AddChange(changesetId: string,
|
private AddChange(changesetId: string,
|
||||||
changesetXML: string,
|
changesetXML: string,
|
||||||
|
allElements: ElementStorage,
|
||||||
continuation: ((changesetId: string, idMapping: any) => void),
|
continuation: ((changesetId: string, idMapping: any) => void),
|
||||||
onFail: ((changesetId: string) => void) = undefined) {
|
onFail: ((changesetId: string) => void) = undefined) {
|
||||||
this.auth.xhr({
|
this.auth.xhr({
|
||||||
|
@ -113,7 +121,7 @@ export class ChangesetHandler {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mapping = ChangesetHandler.parseUploadChangesetResponse(response);
|
const mapping = ChangesetHandler.parseUploadChangesetResponse(response, allElements);
|
||||||
console.log("Uploaded changeset ", changesetId);
|
console.log("Uploaded changeset ", changesetId);
|
||||||
continuation(changesetId, mapping);
|
continuation(changesetId, mapping);
|
||||||
});
|
});
|
||||||
|
@ -145,7 +153,7 @@ export class ChangesetHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseUploadChangesetResponse(response: XMLDocument) {
|
private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) {
|
||||||
const nodes = response.getElementsByTagName("node");
|
const nodes = response.getElementsByTagName("node");
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
|
@ -157,9 +165,9 @@ export class ChangesetHandler {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log("Rewriting id: ", oldId, "-->", newId);
|
console.log("Rewriting id: ", oldId, "-->", newId);
|
||||||
const element = State.state.allElements.getElement("node/" + oldId);
|
const element = allElements.getElement("node/" + oldId);
|
||||||
element.data.id = "node/" + newId;
|
element.data.id = "node/" + newId;
|
||||||
State.state.allElements.addElementById("node/" + newId, element);
|
allElements.addElementById("node/" + newId, element);
|
||||||
element.ping();
|
element.ping();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import osmAuth from "osm-auth";
|
import osmAuth from "osm-auth";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import {State} from "../../State";
|
|
||||||
import {All} from "../../Customizations/Layouts/All";
|
|
||||||
import {OsmPreferences} from "./OsmPreferences";
|
import {OsmPreferences} from "./OsmPreferences";
|
||||||
import {ChangesetHandler} from "./ChangesetHandler";
|
import {ChangesetHandler} from "./ChangesetHandler";
|
||||||
|
import {Layout} from "../../Customizations/Layout";
|
||||||
|
import {ElementStorage} from "../ElementStorage";
|
||||||
|
|
||||||
export class UserDetails {
|
export class UserDetails {
|
||||||
|
|
||||||
|
@ -32,8 +32,7 @@ export class OsmConnection {
|
||||||
constructor(dryRun: boolean, oauth_token: UIEventSource<string>,
|
constructor(dryRun: boolean, oauth_token: UIEventSource<string>,
|
||||||
// Used to keep multiple changesets open and to write to the correct changeset
|
// Used to keep multiple changesets open and to write to the correct changeset
|
||||||
layoutName: string,
|
layoutName: string,
|
||||||
singlePage: boolean = true,
|
singlePage: boolean = true) {
|
||||||
useDevServer:boolean = false) {
|
|
||||||
|
|
||||||
let pwaStandAloneMode = false;
|
let pwaStandAloneMode = false;
|
||||||
try {
|
try {
|
||||||
|
@ -55,7 +54,6 @@ export class OsmConnection {
|
||||||
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
|
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
|
||||||
singlepage: false,
|
singlepage: false,
|
||||||
auto: true,
|
auto: true,
|
||||||
url: useDevServer ? "https://master.apis.dev.openstreetmap.org" : undefined
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -65,7 +63,6 @@ export class OsmConnection {
|
||||||
singlepage: true,
|
singlepage: true,
|
||||||
landing: window.location.href,
|
landing: window.location.href,
|
||||||
auto: true,
|
auto: true,
|
||||||
url: useDevServer ? "https://master.apis.dev.openstreetmap.org" : undefined
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +94,12 @@ export class OsmConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public UploadChangeset(generateChangeXML: (csid: string) => string,
|
public UploadChangeset(
|
||||||
|
layout: Layout,
|
||||||
|
allElements: ElementStorage,
|
||||||
|
generateChangeXML: (csid: string) => string,
|
||||||
continuation: () => void = () => {}) {
|
continuation: () => void = () => {}) {
|
||||||
this.changesetHandler.UploadChangeset(generateChangeXML, continuation);
|
this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, continuation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
|
||||||
|
@ -168,11 +168,12 @@ export class OsmConnection {
|
||||||
data.unreadMessages = parseInt(messages.getAttribute("unread"));
|
data.unreadMessages = parseInt(messages.getAttribute("unread"));
|
||||||
data.totalMessages = parseInt(messages.getAttribute("count"));
|
data.totalMessages = parseInt(messages.getAttribute("count"));
|
||||||
|
|
||||||
|
self.userDetails.ping();
|
||||||
for (const action of self._onLoggedIn) {
|
for (const action of self._onLoggedIn) {
|
||||||
action(self.userDetails.data);
|
action(self.userDetails.data);
|
||||||
}
|
}
|
||||||
|
self._onLoggedIn = [];
|
||||||
|
|
||||||
self.userDetails.ping();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
/**
|
/**
|
||||||
* Helps in uplaoding, by generating the rigth title, decription and by adding the tag to the changeset
|
* Helps in uplaoding, by generating the rigth title, decription and by adding the tag to the changeset
|
||||||
*/
|
*/
|
||||||
import {Changes} from "./Changes";
|
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
||||||
import {UserDetails} from "./OsmConnection";
|
|
||||||
import {SlideShow} from "../../UI/SlideShow";
|
import {SlideShow} from "../../UI/SlideShow";
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
import {Tag} from "../TagsFilter";
|
import {Tag} from "../Tags";
|
||||||
|
|
||||||
export class OsmImageUploadHandler {
|
export class OsmImageUploadHandler {
|
||||||
private _tags: UIEventSource<any>;
|
private readonly _tags: UIEventSource<any>;
|
||||||
private _slideShow: SlideShow;
|
private readonly _slideShow: SlideShow;
|
||||||
private _preferedLicense: UIEventSource<string>;
|
private readonly _preferedLicense: UIEventSource<string>;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>,
|
constructor(tags: UIEventSource<any>,
|
||||||
preferedLicense: UIEventSource<string>,
|
preferedLicense: UIEventSource<string>,
|
||||||
|
|
|
@ -18,13 +18,21 @@ export abstract class OsmObject {
|
||||||
const splitted = id.split("/");
|
const splitted = id.split("/");
|
||||||
const type = splitted[0];
|
const type = splitted[0];
|
||||||
const idN = splitted[1];
|
const idN = splitted[1];
|
||||||
|
|
||||||
|
const newContinuation = (element: OsmObject) => {
|
||||||
|
|
||||||
|
console.log("Received: ",element);
|
||||||
|
|
||||||
|
continuation(element);
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case("node"):
|
case("node"):
|
||||||
return new OsmNode(idN).Download(continuation);
|
return new OsmNode(idN).Download(newContinuation);
|
||||||
case("way"):
|
case("way"):
|
||||||
return new OsmWay(idN).Download(continuation);
|
return new OsmWay(idN).Download(newContinuation);
|
||||||
case("relation"):
|
case("relation"):
|
||||||
return new OsmRelation(idN).Download(continuation);
|
return new OsmRelation(idN).Download(newContinuation);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,11 +46,9 @@ export abstract class OsmObject {
|
||||||
* @param string
|
* @param string
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
private Escape(string: string) {
|
private static Escape(string: string) {
|
||||||
while (string.indexOf('"') >= 0) {
|
return string.replace(/"/g, '"')
|
||||||
string = string.replace('"', '"');
|
.replace(/&/g, "&");
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +60,7 @@ export abstract class OsmObject {
|
||||||
for (const key in this.tags) {
|
for (const key in this.tags) {
|
||||||
const v = this.tags[key];
|
const v = this.tags[key];
|
||||||
if (v !== "") {
|
if (v !== "") {
|
||||||
tags += ' <tag k="' + this.Escape(key) + '" v="' + this.Escape(this.tags[key]) + '"/>\n'
|
tags += ' <tag k="' + OsmObject.Escape(key) + '" v="' + OsmObject.Escape(this.tags[key]) + '"/>\n'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tags;
|
return tags;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import {OsmConnection, UserDetails} from "./OsmConnection";
|
import {OsmConnection, UserDetails} from "./OsmConnection";
|
||||||
import {All} from "../../Customizations/Layouts/All";
|
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
|
||||||
export class OsmPreferences {
|
export class OsmPreferences {
|
||||||
|
@ -68,8 +67,6 @@ export class OsmPreferences {
|
||||||
}
|
}
|
||||||
if (l > 25) {
|
if (l > 25) {
|
||||||
throw "Length to long";
|
throw "Length to long";
|
||||||
source.setData(undefined);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const prefsCount = Number(l);
|
const prefsCount = Number(l);
|
||||||
let str = "";
|
let str = "";
|
||||||
|
@ -154,7 +151,7 @@ export class OsmPreferences {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
path: '/api/0.6/user/preferences/' + k,
|
path: '/api/0.6/user/preferences/' + k,
|
||||||
options: {header: {'Content-Type': 'text/plain'}},
|
options: {header: {'Content-Type': 'text/plain'}},
|
||||||
}, function (error, result) {
|
}, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("Could not remove preference", error);
|
console.log("Could not remove preference", error);
|
||||||
return;
|
return;
|
||||||
|
@ -172,7 +169,7 @@ export class OsmPreferences {
|
||||||
path: '/api/0.6/user/preferences/' + k,
|
path: '/api/0.6/user/preferences/' + k,
|
||||||
options: {header: {'Content-Type': 'text/plain'}},
|
options: {header: {'Content-Type': 'text/plain'}},
|
||||||
content: v
|
content: v
|
||||||
}, function (error, result) {
|
}, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log(`Could not set preference "${k}"'`, error);
|
console.log(`Could not set preference "${k}"'`, error);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/**
|
|
||||||
* Interfaces overpass to get all the latest data
|
|
||||||
*/
|
|
||||||
import {Bounds} from "../Bounds";
|
import {Bounds} from "../Bounds";
|
||||||
import {TagsFilter} from "../TagsFilter";
|
import {TagsFilter} from "../Tags";
|
||||||
import $ from "jquery"
|
import $ from "jquery"
|
||||||
import * as OsmToGeoJson from "osmtogeojson";
|
import * as OsmToGeoJson from "osmtogeojson";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interfaces overpass to get all the latest data
|
||||||
|
*/
|
||||||
export class Overpass {
|
export class Overpass {
|
||||||
private _filter: TagsFilter
|
private _filter: TagsFilter
|
||||||
public static testUrl: string = null
|
public static testUrl: string = null
|
||||||
|
|
|
@ -6,14 +6,8 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
import {Img} from "../UI/Img";
|
import {Img} from "../UI/Img";
|
||||||
import {CheckBox} from "../UI/Input/CheckBox";
|
import {CheckBox} from "../UI/Input/CheckBox";
|
||||||
import {VerticalCombine} from "../UI/Base/VerticalCombine";
|
|
||||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
|
||||||
import {SubtleButton} from "../UI/Base/SubtleButton";
|
|
||||||
import {PersonalLayout} from "./PersonalLayout";
|
import {PersonalLayout} from "./PersonalLayout";
|
||||||
import {All} from "../Customizations/Layouts/All";
|
|
||||||
import {Layout} from "../Customizations/Layout";
|
import {Layout} from "../Customizations/Layout";
|
||||||
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
|
|
||||||
import {TagRendering} from "../Customizations/TagRendering";
|
|
||||||
|
|
||||||
export class PersonalLayersPanel extends UIElement {
|
export class PersonalLayersPanel extends UIElement {
|
||||||
private checkboxes: UIElement[] = [];
|
private checkboxes: UIElement[] = [];
|
||||||
|
@ -22,7 +16,6 @@ export class PersonalLayersPanel extends UIElement {
|
||||||
super(State.state.favouriteLayers);
|
super(State.state.favouriteLayers);
|
||||||
|
|
||||||
this.ListenTo(State.state.osmConnection.userDetails);
|
this.ListenTo(State.state.osmConnection.userDetails);
|
||||||
const t = Translations.t.favourite;
|
|
||||||
|
|
||||||
this.UpdateView([]);
|
this.UpdateView([]);
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -38,9 +31,9 @@ export class PersonalLayersPanel extends UIElement {
|
||||||
const favs = State.state.favouriteLayers.data ?? [];
|
const favs = State.state.favouriteLayers.data ?? [];
|
||||||
const controls = new Map<string, UIEventSource<boolean>>();
|
const controls = new Map<string, UIEventSource<boolean>>();
|
||||||
const allLayouts = AllKnownLayouts.layoutsList.concat(extraThemes);
|
const allLayouts = AllKnownLayouts.layoutsList.concat(extraThemes);
|
||||||
|
console.log("ALL LAYOUTS", allLayouts)
|
||||||
for (const layout of allLayouts) {
|
for (const layout of allLayouts) {
|
||||||
|
if (layout.id === PersonalLayout.NAME) {
|
||||||
if (layout.name === PersonalLayout.NAME) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (layout.hideFromOverview &&
|
if (layout.hideFromOverview &&
|
||||||
|
@ -60,10 +53,9 @@ export class PersonalLayersPanel extends UIElement {
|
||||||
this.checkboxes.push(header);
|
this.checkboxes.push(header);
|
||||||
|
|
||||||
for (const layer of layout.layers) {
|
for (const layer of layout.layers) {
|
||||||
|
|
||||||
let icon = layer.icon;
|
let icon = layer.icon;
|
||||||
if (icon !== undefined && typeof (icon) !== "string") {
|
if (icon !== undefined && typeof (icon) !== "string") {
|
||||||
icon = icon.GetContent({"id": "node/-1"}) ?? "./assets/bug.svg";
|
icon = icon.GetContent({"id": "node/-1"}).txt ?? "./assets/bug.svg";
|
||||||
}
|
}
|
||||||
const image = (layer.icon ? `<img src='${layer.icon}'>` : Img.checkmark);
|
const image = (layer.icon ? `<img src='${layer.icon}'>` : Img.checkmark);
|
||||||
const noimage = (layer.icon ? `<img src='${layer.icon}'>` : Img.no_checkmark);
|
const noimage = (layer.icon ? `<img src='${layer.icon}'>` : Img.no_checkmark);
|
||||||
|
|
|
@ -5,7 +5,7 @@ export abstract class TagsFilter {
|
||||||
abstract asOverpass(): string[]
|
abstract asOverpass(): string[]
|
||||||
abstract substituteValues(tags: any) : TagsFilter;
|
abstract substituteValues(tags: any) : TagsFilter;
|
||||||
|
|
||||||
matchesProperties(properties: any) : boolean{
|
matchesProperties(properties: Map<string, string>): boolean {
|
||||||
return this.matches(TagUtils.proprtiesToKV(properties));
|
return this.matches(TagUtils.proprtiesToKV(properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,80 +13,60 @@ export abstract class TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Regex extends TagsFilter {
|
export class RegexTag extends TagsFilter {
|
||||||
private _k: string;
|
private readonly key: RegExp;
|
||||||
private _r: string;
|
private readonly value: RegExp;
|
||||||
|
private readonly invert: boolean;
|
||||||
|
|
||||||
constructor(k: string, r: string) {
|
constructor(key: RegExp, value: RegExp, invert: boolean = false) {
|
||||||
super();
|
super();
|
||||||
this._k = k;
|
this.key = key;
|
||||||
this._r = r;
|
this.value = value;
|
||||||
|
this.invert = invert;
|
||||||
}
|
}
|
||||||
|
|
||||||
asOverpass(): string[] {
|
asOverpass(): string[] {
|
||||||
return ["['" + this._k + "'~'" + this._r + "']"];
|
|
||||||
|
return [`['${this.key.source}'${this.invert ? "!" : ""}~'${this.value.source}']`];
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(tags: { k: string; v: string }[]): boolean {
|
matches(tags: { k: string; v: string }[]): boolean {
|
||||||
if(!(tags instanceof Array)){
|
|
||||||
throw "You used 'matches' on something that is not a list. Did you mean to use 'matchesProperties'?"
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (tag.k === this._k) {
|
if (tag.k.match(this.key)) {
|
||||||
if (tag.v === "") {
|
return tag.v.match(this.value) !== null;
|
||||||
// This tag has been removed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this._r === "*") {
|
|
||||||
// Any is allowed
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const matchCount =tag.v.match(this._r)?.length;
|
|
||||||
return (matchCount ?? 0) > 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
substituteValues(tags: any) : TagsFilter{
|
substituteValues(tags: any) : TagsFilter{
|
||||||
throw "Substituting values is not supported on regex tags"
|
console.warn("Not substituting values on regex tags");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString() {
|
||||||
return this._k+"~="+this._r;
|
return `${this.key}${this.invert ? "!" : ""}~${this.value}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Tag extends TagsFilter {
|
export class Tag extends TagsFilter {
|
||||||
public key: string
|
public key: string
|
||||||
public value: string | RegExp
|
public value: string
|
||||||
public invertValue: boolean
|
|
||||||
|
|
||||||
constructor(key: string | RegExp, value: string | RegExp, invertValue = false) {
|
|
||||||
|
|
||||||
if (value instanceof RegExp && invertValue) {
|
|
||||||
throw new Error("Unsupported combination: RegExp value and inverted value (use regex to invert the match)")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
constructor(key: string, value: string) {
|
||||||
super()
|
super()
|
||||||
// @ts-ignore
|
|
||||||
this.key = key
|
this.key = key
|
||||||
// @ts-ignore
|
|
||||||
this.value = value
|
this.value = value
|
||||||
this.invertValue = invertValue
|
if(key === undefined || key === ""){
|
||||||
}
|
throw "Invalid key";
|
||||||
|
}
|
||||||
private static regexOrStrMatches(regexOrStr: string | RegExp, testStr: string) {
|
if(value === undefined){
|
||||||
if (typeof regexOrStr === 'string') {
|
throw "Invalid value";
|
||||||
return regexOrStr === testStr
|
}
|
||||||
} else if (regexOrStr instanceof RegExp) {
|
if(value === undefined || value === "*"){
|
||||||
return (regexOrStr as RegExp).test(testStr)
|
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}!~*`)
|
||||||
}
|
}
|
||||||
throw new Error("<regexOrStr> must be of type RegExp or string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(tags: { k: string; v: string }[]): boolean {
|
matches(tags: { k: string; v: string }[]): boolean {
|
||||||
|
@ -95,70 +75,36 @@ export class Tag extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
|
if (this.key == tag.k) {
|
||||||
if (Tag.regexOrStrMatches(this.key, tag.k)) {
|
|
||||||
|
|
||||||
if (tag.v === "") {
|
if (tag.v === "") {
|
||||||
// This tag has been removed -> always matches false
|
// This tag has been removed -> always matches false
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.value === "*") {
|
|
||||||
// Any is allowed (as long as the tag is not empty)
|
if (this.value === tag.v) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.value === tag.v){
|
|
||||||
return !this.invertValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Tag.regexOrStrMatches(this.value, tag.v) !== this.invertValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.invertValue
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
asOverpass(): string[] {
|
asOverpass(): string[] {
|
||||||
// @ts-ignore
|
|
||||||
const keyIsRegex = this.key instanceof RegExp
|
|
||||||
// @ts-ignore
|
|
||||||
const key = keyIsRegex ? (this.key as RegExp).source : this.key
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const valIsRegex = this.value instanceof RegExp
|
|
||||||
// @ts-ignore
|
|
||||||
const val = valIsRegex ? (this.value as RegExp).source : this.value
|
|
||||||
|
|
||||||
const regexKeyPrefix = keyIsRegex ? '~' : ''
|
|
||||||
const anyVal = this.value === "*"
|
|
||||||
|
|
||||||
if (anyVal && !keyIsRegex) {
|
|
||||||
return [`[${regexKeyPrefix}"${key}"]`];
|
|
||||||
}
|
|
||||||
if (this.value === "") {
|
if (this.value === "") {
|
||||||
// NOT having this key
|
// NOT having this key
|
||||||
return ['[!"' + key + '"]'];
|
return ['[!"' + this.key + '"]'];
|
||||||
}
|
}
|
||||||
|
return [`["${this.key}"="${this.value}"]`];
|
||||||
const compareOperator = (valIsRegex || keyIsRegex) ? '~' : (this.invertValue ? '!=' : '=')
|
|
||||||
return [`[${regexKeyPrefix}"${key}"${compareOperator}"${keyIsRegex && anyVal ? '.' : val}"]`];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
substituteValues(tags: any) {
|
substituteValues(tags: any) {
|
||||||
if (typeof this.value !== 'string') {
|
|
||||||
throw new Error("substituteValues() only possible with tag value of type string")
|
|
||||||
}
|
|
||||||
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
|
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
||||||
let v = ""
|
let v = this.value;
|
||||||
if (typeof (this.value) === "string") {
|
|
||||||
v = this.value;
|
|
||||||
} else {
|
|
||||||
// value is a regex
|
|
||||||
v = this.value.source;
|
|
||||||
}
|
|
||||||
if (shorten) {
|
if (shorten) {
|
||||||
v = Utils.EllipsesAfter(v, 25);
|
v = Utils.EllipsesAfter(v, 25);
|
||||||
}
|
}
|
||||||
|
@ -167,26 +113,11 @@ export class Tag extends TagsFilter {
|
||||||
`=` +
|
`=` +
|
||||||
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
|
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
|
||||||
}
|
}
|
||||||
|
return this.key + "=" + v;
|
||||||
if (typeof (this.value) === "string") {
|
|
||||||
return this.key + (this.invertValue ? "!=": "=") + v;
|
|
||||||
}else{
|
|
||||||
// value is a regex
|
|
||||||
return this.key + "~=" + this.value.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function anyValueExcept(key: string, exceptValue: string) {
|
|
||||||
return new And([
|
|
||||||
new Tag(key, "*"),
|
|
||||||
new Tag(key, exceptValue, true)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Or extends TagsFilter {
|
export class Or extends TagsFilter {
|
||||||
public or: TagsFilter[]
|
public or: TagsFilter[]
|
||||||
|
|
||||||
|
@ -248,8 +179,8 @@ export class And extends TagsFilter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private combine(filter: string, choices: string[]): string[] {
|
private static combine(filter: string, choices: string[]): string[] {
|
||||||
var values = []
|
const values = [];
|
||||||
for (const or of choices) {
|
for (const or of choices) {
|
||||||
values.push(filter + or);
|
values.push(filter + or);
|
||||||
}
|
}
|
||||||
|
@ -257,19 +188,18 @@ export class And extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
asOverpass(): string[] {
|
asOverpass(): string[] {
|
||||||
var allChoices: string[] = null;
|
let allChoices: string[] = null;
|
||||||
|
|
||||||
for (const andElement of this.and) {
|
for (const andElement of this.and) {
|
||||||
var andElementFilter = andElement.asOverpass();
|
const andElementFilter = andElement.asOverpass();
|
||||||
if (allChoices === null) {
|
if (allChoices === null) {
|
||||||
allChoices = andElementFilter;
|
allChoices = andElementFilter;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newChoices: string[] = []
|
const newChoices: string[] = [];
|
||||||
for (var choice of allChoices) {
|
for (const choice of allChoices) {
|
||||||
newChoices.push(
|
newChoices.push(
|
||||||
...this.combine(choice, andElementFilter)
|
...And.combine(choice, andElementFilter)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
allChoices = newChoices;
|
allChoices = newChoices;
|
||||||
|
@ -291,31 +221,6 @@ export class And extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Not extends TagsFilter{
|
|
||||||
private not: TagsFilter;
|
|
||||||
|
|
||||||
constructor(not: TagsFilter) {
|
|
||||||
super();
|
|
||||||
this.not = not;
|
|
||||||
}
|
|
||||||
|
|
||||||
asOverpass(): string[] {
|
|
||||||
throw "Not supported yet"
|
|
||||||
}
|
|
||||||
|
|
||||||
matches(tags: { k: string; v: string }[]): boolean {
|
|
||||||
return !this.not.matches(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
substituteValues(tags: any): TagsFilter {
|
|
||||||
return new Not(this.not.substituteValues(tags));
|
|
||||||
}
|
|
||||||
|
|
||||||
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
|
||||||
return "!" + this.not.asHumanString(linkToWiki, shorten);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class TagUtils {
|
export class TagUtils {
|
||||||
static proprtiesToKV(properties: any): { k: string, v: string }[] {
|
static proprtiesToKV(properties: any): { k: string, v: string }[] {
|
|
@ -43,7 +43,7 @@ export class Imgur {
|
||||||
const apiUrl = 'https://api.imgur.com/3/image/'+hash;
|
const apiUrl = 'https://api.imgur.com/3/image/'+hash;
|
||||||
const apiKey = '7070e7167f0a25a';
|
const apiKey = '7070e7167f0a25a';
|
||||||
|
|
||||||
var settings = {
|
const settings = {
|
||||||
async: true,
|
async: true,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
processData: false,
|
processData: false,
|
||||||
|
@ -86,7 +86,7 @@ export class Imgur {
|
||||||
const apiUrl = 'https://api.imgur.com/3/image';
|
const apiUrl = 'https://api.imgur.com/3/image';
|
||||||
const apiKey = '7070e7167f0a25a';
|
const apiKey = '7070e7167f0a25a';
|
||||||
|
|
||||||
var settings = {
|
const settings = {
|
||||||
async: true,
|
async: true,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
processData: false,
|
processData: false,
|
||||||
|
@ -99,7 +99,7 @@ export class Imgur {
|
||||||
},
|
},
|
||||||
mimeType: 'multipart/form-data',
|
mimeType: 'multipart/form-data',
|
||||||
};
|
};
|
||||||
var formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('image', blob);
|
formData.append('image', blob);
|
||||||
formData.append("title", title);
|
formData.append("title", title);
|
||||||
formData.append("description", description)
|
formData.append("description", description)
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class Wikimedia {
|
||||||
"api.php?action=query&prop=imageinfo&iiprop=extmetadata&" +
|
"api.php?action=query&prop=imageinfo&iiprop=extmetadata&" +
|
||||||
"titles=" + filename +
|
"titles=" + filename +
|
||||||
"&format=json&origin=*";
|
"&format=json&origin=*";
|
||||||
$.getJSON(url, function (data, status) {
|
$.getJSON(url, function (data) {
|
||||||
const licenseInfo = new LicenseInfo();
|
const licenseInfo = new LicenseInfo();
|
||||||
const license = data.query.pages[-1].imageinfo[0].extmetadata;
|
const license = data.query.pages[-1].imageinfo[0].extmetadata;
|
||||||
|
|
||||||
|
|
17
State.ts
17
State.ts
|
@ -1,19 +1,18 @@
|
||||||
import {UIElement} from "./UI/UIElement";
|
import {UIElement} from "./UI/UIElement";
|
||||||
import {Layout} from "./Customizations/Layout";
|
import {Layout} from "./Customizations/Layout";
|
||||||
import {Utils} from "./Utils";
|
import {Utils} from "./Utils";
|
||||||
import {LayerDefinition, Preset} from "./Customizations/LayerDefinition";
|
import {Preset} from "./Customizations/LayerDefinition";
|
||||||
import {ElementStorage} from "./Logic/ElementStorage";
|
import {ElementStorage} from "./Logic/ElementStorage";
|
||||||
import {Changes} from "./Logic/Osm/Changes";
|
import {Changes} from "./Logic/Osm/Changes";
|
||||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||||
import Locale from "./UI/i18n/Locale";
|
import Locale from "./UI/i18n/Locale";
|
||||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
|
||||||
import Translations from "./UI/i18n/Translations";
|
import Translations from "./UI/i18n/Translations";
|
||||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||||
import {LayerUpdater} from "./Logic/LayerUpdater";
|
import {LayerUpdater} from "./Logic/LayerUpdater";
|
||||||
import {UIEventSource} from "./Logic/UIEventSource";
|
import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||||
import {CustomLayoutFromJSON} from "./Customizations/JSON/CustomLayoutFromJSON";
|
import {FromJSON} from "./Customizations/JSON/FromJSON";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the global state: a bunch of UI-event sources
|
* Contains the global state: a bunch of UI-event sources
|
||||||
|
@ -24,7 +23,7 @@ export class State {
|
||||||
// The singleton of the global state
|
// The singleton of the global state
|
||||||
public static state: State;
|
public static state: State;
|
||||||
|
|
||||||
public static vNumber = "0.0.7c mutlizoom";
|
public static vNumber = "0.0.7d Refactored";
|
||||||
|
|
||||||
// The user journey states thresholds when a new feature gets unlocked
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
public static userJourney = {
|
public static userJourney = {
|
||||||
|
@ -124,6 +123,9 @@ export class State {
|
||||||
public layoutDefinition: string;
|
public layoutDefinition: string;
|
||||||
public installedThemes: UIEventSource<{ layout: Layout; definition: string }[]>;
|
public installedThemes: UIEventSource<{ layout: Layout; definition: string }[]>;
|
||||||
|
|
||||||
|
public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab","0").map<number>(
|
||||||
|
str => isNaN(Number(str)) ? 0 : Number(str),[],n => ""+n
|
||||||
|
);
|
||||||
|
|
||||||
constructor(layoutToUse: Layout, useDevServer = false) {
|
constructor(layoutToUse: Layout, useDevServer = false) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -175,9 +177,8 @@ export class State {
|
||||||
this.osmConnection = new OsmConnection(
|
this.osmConnection = new OsmConnection(
|
||||||
testParam === "true",
|
testParam === "true",
|
||||||
QueryParameters.GetQueryParameter("oauth_token", undefined),
|
QueryParameters.GetQueryParameter("oauth_token", undefined),
|
||||||
layoutToUse.name,
|
layoutToUse.id,
|
||||||
true,
|
true
|
||||||
testParam === "dev"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,7 +202,7 @@ export class State {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
installedThemes.push({
|
installedThemes.push({
|
||||||
layout: CustomLayoutFromJSON.FromQueryParam(customLayout.data),
|
layout: FromJSON.FromBase64(customLayout.data),
|
||||||
definition: customLayout.data
|
definition: customLayout.data
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -7,8 +7,8 @@ export class TabbedComponent extends UIElement {
|
||||||
private headers: UIElement[] = [];
|
private headers: UIElement[] = [];
|
||||||
private content: UIElement[] = [];
|
private content: UIElement[] = [];
|
||||||
|
|
||||||
constructor(elements: { header: UIElement | string, content: UIElement | string }[]) {
|
constructor(elements: { header: UIElement | string, content: UIElement | string }[], openedTab : UIEventSource<number> = new UIEventSource<number>(0)) {
|
||||||
super(new UIEventSource<number>(0));
|
super(openedTab);
|
||||||
const self = this;
|
const self = this;
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
let element = elements[i];
|
let element = elements[i];
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
import {LayoutConfigJson} from "../../Customizations/JSON/CustomLayoutFromJSON";
|
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import Combine from "../Base/Combine";
|
|
||||||
import {Button} from "../Base/Button";
|
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
|
||||||
|
|
||||||
export class Preview extends UIElement {
|
|
||||||
private url: UIEventSource<string>;
|
|
||||||
private config: UIEventSource<LayoutConfigJson>;
|
|
||||||
|
|
||||||
private currentPreview = new UIEventSource<string>("")
|
|
||||||
private reloadButton: Button;
|
|
||||||
private otherPreviews: VariableUiElement;
|
|
||||||
|
|
||||||
constructor(url: UIEventSource<string>, testurl: UIEventSource<string>, config: UIEventSource<LayoutConfigJson>) {
|
|
||||||
super(undefined);
|
|
||||||
this.config = config;
|
|
||||||
this.url = url;
|
|
||||||
this.reloadButton = new Button("Reload the preview", () => {
|
|
||||||
this.currentPreview.setData(`<iframe width="99%" height="70%" src="${this.url.data}"></iframe>` +
|
|
||||||
'<p class="alert">The above preview is in testmode. Changes will not be sent to OSM, so feel free to add points and answer questions</p> ',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this.ListenTo(this.currentPreview);
|
|
||||||
|
|
||||||
|
|
||||||
this.otherPreviews = new VariableUiElement(this.url.map(url => {
|
|
||||||
|
|
||||||
return [
|
|
||||||
`<h2>Your link</h2>`,
|
|
||||||
'<span class="alert">Bookmark the link below</span><br/>',
|
|
||||||
'MapComplete has no backend. The <i>entire</i> theme configuration is saved in the following URL. This means that this URL is needed to revive and change your MapComplete instance.<br/>',
|
|
||||||
`<a target='_blank' href='${this.url.data}'>${this.url.data}</a><br/>`,
|
|
||||||
'<h2>JSON-configuration</h2>',
|
|
||||||
'You can see the configuration in JSON format below.<br/>',
|
|
||||||
'<span class=\'literal-code iframe-code-block\' style="width:95%">',
|
|
||||||
JSON.stringify(this.config.data, null, 2).replace(/\n/g, "<br/>").replace(/ /g, " "),
|
|
||||||
'</span>'
|
|
||||||
|
|
||||||
].join("")
|
|
||||||
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
const url = this.url.data;
|
|
||||||
return new Combine([
|
|
||||||
new VariableUiElement(this.currentPreview),
|
|
||||||
this.reloadButton,
|
|
||||||
"<h2>Statistics</h2>",
|
|
||||||
"We track statistics with goatcounter. <a href='https://pietervdvn.goatcounter.com' target='_blank'>The statistics can be seen by anyone, so if you want to see where your theme ends up, click here</a>",
|
|
||||||
this.otherPreviews
|
|
||||||
]).Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,712 +0,0 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {VerticalCombine} from "../Base/VerticalCombine";
|
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
|
||||||
import Combine from "../Base/Combine";
|
|
||||||
import {
|
|
||||||
CustomLayoutFromJSON,
|
|
||||||
LayerConfigJson,
|
|
||||||
LayoutConfigJson,
|
|
||||||
TagRenderingConfigJson
|
|
||||||
} from "../../Customizations/JSON/CustomLayoutFromJSON";
|
|
||||||
import {TabbedComponent} from "../Base/TabbedComponent";
|
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection";
|
|
||||||
import {Button} from "../Base/Button";
|
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import {TextField, ValidatedTextField} from "../Input/TextField";
|
|
||||||
import {Tag} from "../../Logic/TagsFilter";
|
|
||||||
import {DropDown} from "../Input/DropDown";
|
|
||||||
import {TagRendering} from "../../Customizations/TagRendering";
|
|
||||||
import {LayerDefinition} from "../../Customizations/LayerDefinition";
|
|
||||||
import {State} from "../../State";
|
|
||||||
|
|
||||||
|
|
||||||
TagRendering.injectFunction();
|
|
||||||
|
|
||||||
function TagsToString(tags: string | string [] | { k: string, v: string }[]) {
|
|
||||||
if (tags === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof (tags) == "string") {
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
const newTags = [];
|
|
||||||
console.log(tags)
|
|
||||||
for (const tag of tags) {
|
|
||||||
if (typeof (tag) == "string") {
|
|
||||||
newTags.push(tag)
|
|
||||||
} else {
|
|
||||||
newTags.push(tag.k + "=" + tag.v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newTags.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defined below, as it needs some context/closure
|
|
||||||
let createFieldUI: (label: string, key: string, root: any, options: { deflt?: string, type?: string, description: string, emptyAllowed?: boolean }) => UIElement;
|
|
||||||
let pingThemeObject: () => void;
|
|
||||||
|
|
||||||
class MappingGenerator extends UIElement {
|
|
||||||
|
|
||||||
private elements: UIElement[];
|
|
||||||
|
|
||||||
constructor(tagRendering: TagRenderingConfigJson,
|
|
||||||
mapping: { if: string | string[] | { k: string, v: string }[] }) {
|
|
||||||
super(undefined);
|
|
||||||
this.CreateElements(tagRendering, mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateElements(tagRendering: TagRenderingConfigJson,
|
|
||||||
mapping) {
|
|
||||||
{
|
|
||||||
const self = this;
|
|
||||||
this.elements = [
|
|
||||||
new FixedUiElement("A mapping shows a specific piece of text if a specific tag is present. If no mapping is known and no key matches (and the question is defined), then the mappings show up as radio buttons to answer the question and to update OSM"),
|
|
||||||
createFieldUI("If these tags apply", "if", mapping, {
|
|
||||||
type: "tags",
|
|
||||||
description: "The tags that have to be present. Use <span class='literal-code'>key=</span> to indicate an implicit assumption. 'key=' can be used to indicate: 'if this key is missing'"
|
|
||||||
}),
|
|
||||||
createFieldUI("Then: show this text", "then", mapping, {description: "The text that is shown"}),
|
|
||||||
new Button("Remove this mapping", () => {
|
|
||||||
for (let i = 0; i < tagRendering.mappings.length; i++) {
|
|
||||||
if (tagRendering.mappings[i] === mapping) {
|
|
||||||
tagRendering.mappings.splice(i, 1);
|
|
||||||
self.elements = [
|
|
||||||
new FixedUiElement("Tag mapping removed")
|
|
||||||
]
|
|
||||||
self.Update();
|
|
||||||
pingThemeObject();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
const combine = new VerticalCombine(this.elements).SetClass("bordered");
|
|
||||||
return combine.Render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TagRenderingGenerator
|
|
||||||
extends UIElement {
|
|
||||||
|
|
||||||
private elements: UIElement[];
|
|
||||||
|
|
||||||
constructor(fullConfig: UIEventSource<LayoutConfigJson>,
|
|
||||||
layerConfig: LayerConfigJson,
|
|
||||||
tagRendering: TagRenderingConfigJson,
|
|
||||||
options: { header: string, description: string, removable: boolean, hideQuestion: boolean }) {
|
|
||||||
super(undefined);
|
|
||||||
this.CreateElements(fullConfig, layerConfig, tagRendering, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateElements(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson, tagRendering: TagRenderingConfigJson,
|
|
||||||
options: { header: string, description: string, removable: boolean, hideQuestion: boolean }) {
|
|
||||||
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
this.elements = [
|
|
||||||
new FixedUiElement(`<h3>${options.header}</h3>`),
|
|
||||||
new FixedUiElement(options.description),
|
|
||||||
options.hideQuestion ? new FixedUiElement("") : createFieldUI("Key", "key", tagRendering, {
|
|
||||||
deflt: "name",
|
|
||||||
type: "key",
|
|
||||||
description: "Optional. A single key, such as <span class='literal-code'>name</span> &npbs, &npbs <span class='literal-code'>surface</span>. If the object contains a tag with the specified key, the rendering below will be shown. Use <span class='literal-code'>*</span> if you want to show the rendering by default. Note that a mapping overrides this"
|
|
||||||
}),
|
|
||||||
createFieldUI("Rendering", "render", tagRendering, {
|
|
||||||
deflt: "The name of this object is {name}",
|
|
||||||
description: "Optional. If the above key is present, this rendering will be used. Note that <span class='literal-code'>{key}</span> will be replaced by the value - if that key is present. This is _not_ limited to the given key above, it is allowed to use multiple subsitutions." +
|
|
||||||
"If the above key is _not_ present, the question will be shown and the rendering will be used as answer with {key} as textfield"
|
|
||||||
}),
|
|
||||||
options.hideQuestion ? new FixedUiElement("") : createFieldUI("Type", "type", tagRendering, {
|
|
||||||
deflt: "string",
|
|
||||||
description: "Input validation of this type",
|
|
||||||
type: "typeSelector",
|
|
||||||
|
|
||||||
}),
|
|
||||||
options.hideQuestion ? new FixedUiElement("") :
|
|
||||||
createFieldUI("Question", "question", tagRendering, {
|
|
||||||
deflt: "",
|
|
||||||
description: "Optional. If 'key' is not present (or not given) and none of the mappings below match, then this will be shown as question. Users are then able to answer this question and save the data to OSM. If no question is given, values can still be shown but not answered",
|
|
||||||
type: "string"
|
|
||||||
}),
|
|
||||||
options.hideQuestion ? new FixedUiElement("") :
|
|
||||||
createFieldUI("Extra tags", "addExtraTags", tagRendering,
|
|
||||||
{
|
|
||||||
deflt: "",
|
|
||||||
type: "tags",
|
|
||||||
emptyAllowed: true,
|
|
||||||
description: "Optional. If the freeform text field is used to fill out the tag, these tags are applied as well. The main use case is to flag the object for review. (A prime example is access. A few predefined values are given and the option to fill out something. Here, one can add e.g. <span class='literal-code'>fixme=access was filled out by user, value might not be correct</span>"
|
|
||||||
}),
|
|
||||||
|
|
||||||
options.hideQuestion ? new FixedUiElement("") : createFieldUI(
|
|
||||||
"Only show if", "condition", tagRendering,
|
|
||||||
{
|
|
||||||
deflt: "",
|
|
||||||
type: "tags",
|
|
||||||
emptyAllowed: true,
|
|
||||||
description: "Only show this question/rendering if the object also has the specified tag. This can be useful to ask a follow up question only if the prerequisite is met"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
|
|
||||||
...(tagRendering.mappings ?? []).map((mapping) => {
|
|
||||||
return new MappingGenerator(tagRendering, mapping)
|
|
||||||
}),
|
|
||||||
new Button("Add mapping", () => {
|
|
||||||
if (tagRendering.mappings === undefined) {
|
|
||||||
tagRendering.mappings = []
|
|
||||||
}
|
|
||||||
tagRendering.mappings.push({if: "", then: ""});
|
|
||||||
self.CreateElements(fullConfig, layerConfig, tagRendering, options);
|
|
||||||
self.Update();
|
|
||||||
})
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
if (!!options.removable) {
|
|
||||||
const b = new Button("Remove this tag rendering", () => {
|
|
||||||
for (let i = 0; i < layerConfig.tagRenderings.length; i++) {
|
|
||||||
if (layerConfig.tagRenderings[i] === tagRendering) {
|
|
||||||
layerConfig.tagRenderings.splice(i, 1);
|
|
||||||
self.elements = [
|
|
||||||
new FixedUiElement("Tag rendering removed")
|
|
||||||
]
|
|
||||||
self.Update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.elements.push(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
const combine = new VerticalCombine(this.elements).SetClass("bordered");
|
|
||||||
return combine.Render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PresetGenerator extends UIElement {
|
|
||||||
|
|
||||||
private elements: UIElement[];
|
|
||||||
|
|
||||||
constructor(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson,
|
|
||||||
preset0: { title?: string, description?: string, icon?: string, tags?: string | string[] | { k: string, v: string }[] }) {
|
|
||||||
super(undefined);
|
|
||||||
const self = this;
|
|
||||||
this.elements = [
|
|
||||||
new FixedUiElement("<h3>Preset</h3>"),
|
|
||||||
new FixedUiElement("A preset allows the user to add a new point at a location that was clicked. Note that one layer can have zero, one or multiple presets"),
|
|
||||||
createFieldUI("Title", "title", preset0, {
|
|
||||||
description: "The title of this preset, shown in the 'add new {Title} here'-dialog"
|
|
||||||
}),
|
|
||||||
createFieldUI("Description", "description", preset0,
|
|
||||||
{
|
|
||||||
deflt: layerConfig.description,
|
|
||||||
type: "string",
|
|
||||||
description: "A description shown alongside the 'add new'-button"
|
|
||||||
}),
|
|
||||||
createFieldUI("tags", "tags", preset0,
|
|
||||||
{
|
|
||||||
deflt: TagsToString(layerConfig.overpassTags), type: "tags",
|
|
||||||
description: "The tags that are added to the newly created point"
|
|
||||||
}),
|
|
||||||
new Button("Remove this preset", () => {
|
|
||||||
for (let i = 0; i < layerConfig.presets.length; i++) {
|
|
||||||
if (layerConfig.presets[i] === preset0) {
|
|
||||||
layerConfig.presets.splice(i, 1);
|
|
||||||
self.elements = [
|
|
||||||
new FixedUiElement("Preset removed")
|
|
||||||
]
|
|
||||||
self.Update();
|
|
||||||
pingThemeObject();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
const combine = new VerticalCombine(this.elements).SetClass("bordered");
|
|
||||||
return combine.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class LayerGenerator extends UIElement {
|
|
||||||
private fullConfig: UIEventSource<LayoutConfigJson>;
|
|
||||||
private layerConfig: UIEventSource<LayerConfigJson>;
|
|
||||||
private generateField: ((label: string, key: string, root: any, deflt?: string) => UIElement);
|
|
||||||
private uielements: UIElement[];
|
|
||||||
|
|
||||||
constructor(fullConfig: UIEventSource<LayoutConfigJson>,
|
|
||||||
layerConfig: LayerConfigJson) {
|
|
||||||
super(undefined);
|
|
||||||
this.layerConfig = new UIEventSource<LayerConfigJson>(layerConfig);
|
|
||||||
this.fullConfig = fullConfig;
|
|
||||||
this.CreateElements(fullConfig, layerConfig)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateElements(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson) {
|
|
||||||
|
|
||||||
|
|
||||||
// Init some defaults
|
|
||||||
layerConfig.title = layerConfig.title ?? {
|
|
||||||
key: "*",
|
|
||||||
addExtraTags: "",
|
|
||||||
mappings: [],
|
|
||||||
question: "",
|
|
||||||
render: "Title",
|
|
||||||
type: "text"
|
|
||||||
};
|
|
||||||
layerConfig.title.key = "*";
|
|
||||||
|
|
||||||
layerConfig.icon = layerConfig.icon ?? {
|
|
||||||
key: "*",
|
|
||||||
addExtraTags: "",
|
|
||||||
mappings: [],
|
|
||||||
question: "",
|
|
||||||
render: "./assets/bug.svg",
|
|
||||||
type: "text"
|
|
||||||
};
|
|
||||||
layerConfig.icon.key = "*";
|
|
||||||
|
|
||||||
|
|
||||||
layerConfig.color = layerConfig.color ?? {
|
|
||||||
key: "*",
|
|
||||||
addExtraTags: "",
|
|
||||||
mappings: [],
|
|
||||||
question: "",
|
|
||||||
render: "#00f",
|
|
||||||
type: "text"
|
|
||||||
};
|
|
||||||
layerConfig.color.key = "*";
|
|
||||||
|
|
||||||
layerConfig.width = layerConfig.width?? {
|
|
||||||
key: "*",
|
|
||||||
addExtraTags: "",
|
|
||||||
mappings: [],
|
|
||||||
question: "",
|
|
||||||
render: "10",
|
|
||||||
type: "nat"
|
|
||||||
};
|
|
||||||
layerConfig.width.key = "*"
|
|
||||||
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
this.uielements = [
|
|
||||||
|
|
||||||
new FixedUiElement("<p>A layer is a collection of related objects which have the same or very similar tags renderings. In general, all objects of one layer have the same icon (or at least very similar icons)</p>"),
|
|
||||||
|
|
||||||
createFieldUI("Name", "name", layerConfig, {description: "The name of this layer"}),
|
|
||||||
createFieldUI("A description of objects for this layer", "description", layerConfig, {description: "The description of this layer"}),
|
|
||||||
createFieldUI("Minimum zoom level", "minzoom", layerConfig, {
|
|
||||||
type: "nat",
|
|
||||||
deflt: "12",
|
|
||||||
description: "The minimum zoom level to start loading data. This is mainly limited by the expected number of objects: if there are a lot of objects, then pick something higher. A generous bounding box is put around the map, so some scrolling should be possible"
|
|
||||||
}),
|
|
||||||
createFieldUI("The tags to load from overpass", "overpassTags", layerConfig, {
|
|
||||||
type: "tags",
|
|
||||||
description: "Tags to load from overpass. " +
|
|
||||||
"The format is <span class='literal-code'>key=value&key0=value0&key1=value1</span>, e.g. <span class='literal-code'>amenity=public_bookcase</span> or <span class='literal-code'>amenity=compressed_air&bicycle=yes</span>." +
|
|
||||||
"Special values are:" +
|
|
||||||
"<ul>" +
|
|
||||||
"<li> <span class='literal-code'>key=*</span> to indicate that this key can be anything</li>. " +
|
|
||||||
"<li><span class='literal-code'>key=</span> means 'key is NOT present'</li>" +
|
|
||||||
"<li><span class='literal-code'>key!=value</span> means 'key does NOT have this value'</li>" +
|
|
||||||
"<li><span class='literal-code'>key~=regex</span> indicates a regex, e.g. <b>highway~=residential|tertiary</b></li>"+
|
|
||||||
"</ul>"+
|
|
||||||
". E.g. something that is indoor, not private and has no name tag can be queried as <span class='literal-code'>indoor=yes&name=&access!=private</span>"
|
|
||||||
}),
|
|
||||||
|
|
||||||
createFieldUI("Wayhandling","wayHandling", layerConfig, {
|
|
||||||
type:"wayhandling",
|
|
||||||
description: "Specifies how ways (lines and areas) are handled: either the way is shown, a center point is shown or both"
|
|
||||||
}),
|
|
||||||
|
|
||||||
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.title, {
|
|
||||||
header: "Title element",
|
|
||||||
description: "This element is shown in the title of the popup in a header-tag",
|
|
||||||
removable: false,
|
|
||||||
hideQuestion: true
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
|
||||||
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.icon , {
|
|
||||||
header: "Icon",
|
|
||||||
description: "This decides which icon is used to represent an element on the map. Leave blank if you don't want icons to pop up",
|
|
||||||
removable: false,
|
|
||||||
hideQuestion: true
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
|
||||||
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.color, {
|
|
||||||
header: "Colour",
|
|
||||||
description: "This decides which color is used to represent a way on the map. Note that if an icon is defined as well, the icon will be showed too",
|
|
||||||
removable: false,
|
|
||||||
hideQuestion: true
|
|
||||||
}),
|
|
||||||
|
|
||||||
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.width , {
|
|
||||||
header: "Line thickness",
|
|
||||||
description: "This decides the line thickness of ways (in pixels)",
|
|
||||||
removable: false,
|
|
||||||
hideQuestion: true
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
|
||||||
...layerConfig.tagRenderings.map(tr => new TagRenderingGenerator(fullConfig, layerConfig, tr, {
|
|
||||||
header: "Tag rendering",
|
|
||||||
description: "A single tag rendering",
|
|
||||||
removable: true,
|
|
||||||
hideQuestion: false
|
|
||||||
})),
|
|
||||||
new Button("Add a tag rendering", () => {
|
|
||||||
layerConfig.tagRenderings.push({
|
|
||||||
key: undefined,
|
|
||||||
addExtraTags: undefined,
|
|
||||||
mappings: [],
|
|
||||||
question: undefined,
|
|
||||||
render: undefined,
|
|
||||||
type: "text"
|
|
||||||
});
|
|
||||||
self.CreateElements(fullConfig, layerConfig);
|
|
||||||
self.Update();
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
|
||||||
...layerConfig.presets.map(preset => new PresetGenerator(fullConfig, layerConfig, preset)),
|
|
||||||
new Button("Add a preset", () => {
|
|
||||||
layerConfig.presets.push({
|
|
||||||
icon: undefined,
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
tags: TagsToString(layerConfig.overpassTags)
|
|
||||||
});
|
|
||||||
self.CreateElements(fullConfig, layerConfig);
|
|
||||||
self.Update();
|
|
||||||
}),
|
|
||||||
|
|
||||||
new Button("Remove this layer", () => {
|
|
||||||
const layers = fullConfig.data.layers;
|
|
||||||
for (let i = 0; i < layers.length; i++) {
|
|
||||||
if(layers[i] === layerConfig){
|
|
||||||
layers.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.Update();
|
|
||||||
pingThemeObject();
|
|
||||||
})
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
return new VerticalCombine(this.uielements).Render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AllLayerComponent extends UIElement {
|
|
||||||
|
|
||||||
private tabs: TabbedComponent;
|
|
||||||
private config: UIEventSource<LayoutConfigJson>;
|
|
||||||
|
|
||||||
constructor(config: UIEventSource<LayoutConfigJson>) {
|
|
||||||
super(undefined);
|
|
||||||
this.config = config;
|
|
||||||
const self = this;
|
|
||||||
let previousLayerAmount = config.data.layers.length;
|
|
||||||
config.addCallback((data) => {
|
|
||||||
if (data.layers.length != previousLayerAmount) {
|
|
||||||
previousLayerAmount = data.layers.length;
|
|
||||||
self.UpdateTabs();
|
|
||||||
self.Update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.UpdateTabs();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private UpdateTabs() {
|
|
||||||
const layerPanes: { header: UIElement | string, content: UIElement | string }[] = [];
|
|
||||||
const config = this.config;
|
|
||||||
for (const layer of this.config.data.layers) {
|
|
||||||
const iconUrl = CustomLayoutFromJSON.TagRenderingFromJson(layer?.icon)
|
|
||||||
?.GetContent({id: "node/-1"});
|
|
||||||
const header = this.config.map(() => {
|
|
||||||
|
|
||||||
return `<img src="${iconUrl ?? "./assets/help.svg"}">`
|
|
||||||
});
|
|
||||||
layerPanes.push({
|
|
||||||
header: new VariableUiElement(header),
|
|
||||||
content: new LayerGenerator(config, layer)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
layerPanes.push({
|
|
||||||
header: "<img src='./assets/add.svg'>",
|
|
||||||
content: new Button("Add a new layer", () => {
|
|
||||||
config.data.layers.push({
|
|
||||||
name: "",
|
|
||||||
title: {
|
|
||||||
key: "*",
|
|
||||||
render: "Title"
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
key: "*",
|
|
||||||
render: "./assets/bug.svg"
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
key: "*",
|
|
||||||
render: "#0000ff"
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
key:"*",
|
|
||||||
render: "10"
|
|
||||||
},
|
|
||||||
description: "",
|
|
||||||
minzoom: 12,
|
|
||||||
overpassTags: "",
|
|
||||||
wayHandling: LayerDefinition.WAYHANDLING_CENTER_AND_WAY,
|
|
||||||
presets: [],
|
|
||||||
tagRenderings: []
|
|
||||||
});
|
|
||||||
|
|
||||||
config.ping();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.tabs = new TabbedComponent(layerPanes);
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
return this.tabs.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class ThemeGenerator extends UIElement {
|
|
||||||
|
|
||||||
private readonly userDetails: UIEventSource<UserDetails>;
|
|
||||||
|
|
||||||
public readonly themeObject: UIEventSource<LayoutConfigJson>;
|
|
||||||
private readonly allQuestionFields: UIElement[];
|
|
||||||
public url: UIEventSource<string>;
|
|
||||||
public testurl: UIEventSource<string>;
|
|
||||||
|
|
||||||
private loginButton: Button
|
|
||||||
|
|
||||||
constructor(connection: OsmConnection, windowHash) {
|
|
||||||
super(connection.userDetails);
|
|
||||||
this.userDetails = connection.userDetails;
|
|
||||||
this.loginButton = new Button("Log in with OSM", () => {
|
|
||||||
connection.AttemptLogin()
|
|
||||||
})
|
|
||||||
|
|
||||||
const defaultTheme = {layers: [], icon: "./assets/bug.svg"};
|
|
||||||
let loadedTheme = undefined;
|
|
||||||
if (windowHash !== undefined && windowHash.length > 4) {
|
|
||||||
loadedTheme = JSON.parse(atob(windowHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.themeObject = new UIEventSource<LayoutConfigJson>(loadedTheme ?? defaultTheme);
|
|
||||||
const jsonObjectRoot = this.themeObject.data;
|
|
||||||
connection.userDetails.addCallback((userDetails) => {
|
|
||||||
jsonObjectRoot.maintainer = userDetails.name;
|
|
||||||
});
|
|
||||||
jsonObjectRoot.maintainer = connection.userDetails.data.name;
|
|
||||||
|
|
||||||
|
|
||||||
const base64 = this.themeObject.map(JSON.stringify).map(btoa);
|
|
||||||
let baseUrl = "https://pietervdvn.github.io/MapComplete";
|
|
||||||
if (window.location.hostname === "127.0.0.1") {
|
|
||||||
baseUrl = "http://127.0.0.1:1234";
|
|
||||||
}
|
|
||||||
this.url = base64.map((data) => `${baseUrl}/index.html?userlayout=${this.themeObject.data.name}#${data}`);
|
|
||||||
this.testurl = base64.map((data) => `${baseUrl}/index.html?test=true&userlayout=${this.themeObject.data.name}#${data}`);
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
pingThemeObject = () => {self.themeObject.ping()};
|
|
||||||
createFieldUI = (label, key, root, options) => {
|
|
||||||
|
|
||||||
options = options ?? {description: "?"};
|
|
||||||
options.type = options.type ?? "string";
|
|
||||||
|
|
||||||
const value = new UIEventSource<string>(TagsToString(root[key]) ?? options?.deflt);
|
|
||||||
|
|
||||||
let textField: UIElement;
|
|
||||||
if (options.type === "typeSelector") {
|
|
||||||
const options: { value: string, shown: string | UIElement }[] = [];
|
|
||||||
for (const possibleType in ValidatedTextField.inputValidation) {
|
|
||||||
if (possibleType !== "$") {
|
|
||||||
options.push({value: possibleType, shown: possibleType});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textField = new DropDown<string>("",
|
|
||||||
options,
|
|
||||||
value)
|
|
||||||
} else if (options.type === "wayhandling") {
|
|
||||||
const options: { value: string, shown: string | UIElement }[] =
|
|
||||||
[{value: "" + LayerDefinition.WAYHANDLING_DEFAULT, shown: "Show a line/area as line/area"},
|
|
||||||
{
|
|
||||||
value: "" + LayerDefinition.WAYHANDLING_CENTER_AND_WAY,
|
|
||||||
shown: "Show a line/area as line/area AND show an icon at the center"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "" + LayerDefinition.WAYHANDLING_CENTER_ONLY,
|
|
||||||
shown: "Only show the centerpoint of a way"
|
|
||||||
}];
|
|
||||||
|
|
||||||
textField = new DropDown<string>("",
|
|
||||||
options,
|
|
||||||
value)
|
|
||||||
|
|
||||||
} else if (options.type === "key") {
|
|
||||||
textField = new TextField<string>({
|
|
||||||
placeholder: "single key",
|
|
||||||
startValidated: false,
|
|
||||||
value:value,
|
|
||||||
toString: str => str,
|
|
||||||
fromString: str => {
|
|
||||||
if(str === undefined){
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (str === "*") {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
str = str.trim();
|
|
||||||
if (str.match("^_*[a-zA-Z]*[a-zA-Z0-9:_]*$") == null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
} else if (options.type === "tags") {
|
|
||||||
textField = ValidatedTextField.TagTextField(value.map(CustomLayoutFromJSON.TagsFromJson, [], tags => {
|
|
||||||
if (tags === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return tags.map((tag: Tag) => tag.key + "=" + tag.value).join("&");
|
|
||||||
}), options?.emptyAllowed ?? false);
|
|
||||||
} else if (options.type === "img" || options.type === "colour") {
|
|
||||||
textField = new TextField<string>({
|
|
||||||
placeholder: options.type,
|
|
||||||
fromString: (str) => str,
|
|
||||||
toString: (str) => str,
|
|
||||||
value: value,
|
|
||||||
startValidated: true
|
|
||||||
});
|
|
||||||
} else if (options.type) {
|
|
||||||
textField = ValidatedTextField.ValidatedTextField(options.type, {value: value});
|
|
||||||
} else {
|
|
||||||
textField = new TextField<string>({
|
|
||||||
placeholder: options.type,
|
|
||||||
fromString: (str) => str,
|
|
||||||
toString: (str) => str,
|
|
||||||
value: value,
|
|
||||||
startValidated: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendingPing = false;
|
|
||||||
value.addCallback((v) => {
|
|
||||||
if (v === undefined || v === "") {
|
|
||||||
delete root[key];
|
|
||||||
} else {
|
|
||||||
root[key] = v;
|
|
||||||
}
|
|
||||||
if(!sendingPing){
|
|
||||||
sendingPing = true;
|
|
||||||
self.themeObject.ping(); // We assume the root is a part of the themeObject
|
|
||||||
sendingPing = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.themeObject.addCallback(() => {
|
|
||||||
value.setData(root[key]);
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
label,
|
|
||||||
textField,
|
|
||||||
"<br>",
|
|
||||||
"<span class='subtle'>" + options.description + "</span>"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.allQuestionFields = [
|
|
||||||
createFieldUI("Name of this theme", "name", jsonObjectRoot, {description: "An identifier for this theme"}),
|
|
||||||
createFieldUI("Title", "title", jsonObjectRoot, {
|
|
||||||
deflt: "Title",
|
|
||||||
description: "The title of this theme, as shown in the welcome message and in the title bar of the browser"
|
|
||||||
}),
|
|
||||||
createFieldUI("icon", "icon", jsonObjectRoot, {
|
|
||||||
deflt: "./assets/bug.svg",
|
|
||||||
type: "img",
|
|
||||||
description: "The icon representing this MapComplete instance. It is shown in the welcome message and -if adopted as official theme- used as favicon and to browse themes"
|
|
||||||
}),
|
|
||||||
createFieldUI("Description", "description", jsonObjectRoot, {
|
|
||||||
description: "Shown in the welcome message",
|
|
||||||
deflt: "Description"
|
|
||||||
}),
|
|
||||||
createFieldUI("The supported language", "language", jsonObjectRoot, {
|
|
||||||
description: "The language of this mapcomplete instance. MapComplete can be translated, see <a href='https://github.com/pietervdvn/MapComplete#translating-mapcomplete' target='_blank'> here for more information</a>",
|
|
||||||
deflt: "en"
|
|
||||||
}),
|
|
||||||
createFieldUI("startLat", "startLat", jsonObjectRoot, {
|
|
||||||
type: "float",
|
|
||||||
deflt: "0",
|
|
||||||
description: "The latitude where this theme should start. Note that this is only for completely fresh users, as the last location is saved"
|
|
||||||
}),
|
|
||||||
createFieldUI("startLon", "startLon", jsonObjectRoot, {
|
|
||||||
type: "float",
|
|
||||||
deflt: "0",
|
|
||||||
description: "The longitude where this theme should start. Note that this is only for completely fresh users, as the last location is saved"
|
|
||||||
}),
|
|
||||||
createFieldUI("startzoom", "startZoom", jsonObjectRoot, {
|
|
||||||
type: "nat",
|
|
||||||
deflt: "12",
|
|
||||||
description: "The initial zoom level where the map is located"
|
|
||||||
}),
|
|
||||||
createFieldUI("Query widening factor", "widenFactor", jsonObjectRoot, {
|
|
||||||
type: "pfloat",
|
|
||||||
deflt: "0.05",
|
|
||||||
description: "When a query is run, the current map view is taken and a margin with a certain factor is added to allow panning and zooming. If you are running heavy queries (e.g. highway=residential), to much data is returned. In that case, lower the widenfactor, e.g. to 0.01-0.02"
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
|
||||||
new AllLayerComponent(this.themeObject)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
|
|
||||||
if (!this.userDetails.data.loggedIn) {
|
|
||||||
return new Combine(["Not logged in. You need to be logged in to create a theme.", this.loginButton]).Render();
|
|
||||||
}
|
|
||||||
if (this.userDetails.data.csCount < State.userJourney.themeGeneratorUnlock ) {
|
|
||||||
return `You need at least ${State.userJourney.themeGeneratorUnlock} changesets to create your own theme.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return new VerticalCombine([
|
|
||||||
...this.allQuestionFields,
|
|
||||||
]).Render();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,11 @@
|
||||||
import {UIElement} from "./UIElement";
|
import {UIElement} from "./UIElement";
|
||||||
import {ImageCarousel} from "./Image/ImageCarousel";
|
|
||||||
import {VerticalCombine} from "./Base/VerticalCombine";
|
import {VerticalCombine} from "./Base/VerticalCombine";
|
||||||
import {OsmLink} from "../Customizations/Questions/OsmLink";
|
import {OsmLink} from "../Customizations/Questions/OsmLink";
|
||||||
import {WikipediaLink} from "../Customizations/Questions/WikipediaLink";
|
import {WikipediaLink} from "../Customizations/Questions/WikipediaLink";
|
||||||
import {And} from "../Logic/TagsFilter";
|
import {And} from "../Logic/Tags";
|
||||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor";
|
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import {Changes} from "../Logic/Osm/Changes";
|
import {Changes} from "../Logic/Osm/Changes";
|
||||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
|
||||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
||||||
|
@ -23,13 +21,11 @@ export class FeatureInfoBox extends UIElement {
|
||||||
/**
|
/**
|
||||||
* The tags, wrapped in a global event source
|
* The tags, wrapped in a global event source
|
||||||
*/
|
*/
|
||||||
private _tagsES: UIEventSource<any>;
|
private readonly _tagsES: UIEventSource<any>;
|
||||||
private _changes: Changes;
|
private readonly _changes: Changes;
|
||||||
|
private readonly _title: UIElement;
|
||||||
private _title: UIElement;
|
private readonly _osmLink: UIElement;
|
||||||
private _osmLink: UIElement;
|
private readonly _wikipedialink: UIElement;
|
||||||
private _wikipedialink: UIElement;
|
|
||||||
|
|
||||||
private _infoboxes: TagDependantUIElement[];
|
private _infoboxes: TagDependantUIElement[];
|
||||||
|
|
||||||
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
|
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
TagDependantUIElementConstructor
|
TagDependantUIElementConstructor
|
||||||
} from "../../Customizations/UIElementConstructor";
|
} from "../../Customizations/UIElementConstructor";
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
|
import Translation from "../i18n/Translation";
|
||||||
|
|
||||||
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{
|
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{
|
||||||
IsKnown(properties: any): boolean {
|
IsKnown(properties: any): boolean {
|
||||||
|
@ -29,8 +30,8 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo
|
||||||
return new ImageCarousel(dependencies.tags);
|
return new ImageCarousel(dependencies.tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetContent(tags: any): string {
|
GetContent(tags: any): Translation {
|
||||||
return undefined;
|
return new Translation({"en":"Images without upload"});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {ImageCarousel} from "./ImageCarousel";
|
||||||
import {ImageUploadFlow} from "../ImageUploadFlow";
|
import {ImageUploadFlow} from "../ImageUploadFlow";
|
||||||
import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
|
import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
|
import Translation from "../i18n/Translation";
|
||||||
|
|
||||||
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
||||||
IsKnown(properties: any): boolean {
|
IsKnown(properties: any): boolean {
|
||||||
|
@ -25,8 +26,8 @@ export class ImageCarouselWithUploadConstructor implements TagDependantUIElement
|
||||||
return new ImageCarouselWithUpload(dependencies);
|
return new ImageCarouselWithUpload(dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetContent(tags: any): string {
|
GetContent(tags: any): Translation {
|
||||||
return undefined;
|
return new Translation({"en":"Image carousel with uploader"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +39,6 @@ class ImageCarouselWithUpload extends TagDependantUIElement {
|
||||||
super(dependencies.tags);
|
super(dependencies.tags);
|
||||||
const tags = dependencies.tags;
|
const tags = dependencies.tags;
|
||||||
this._imageElement = new ImageCarousel(tags);
|
this._imageElement = new ImageCarousel(tags);
|
||||||
const userDetails = State.state.osmConnection.userDetails;
|
|
||||||
const license = State.state.osmConnection.GetPreference( "pictures-license");
|
const license = State.state.osmConnection.GetPreference( "pictures-license");
|
||||||
this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI();
|
this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI();
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,13 @@ import Translations from "../i18n/Translations";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import * as EmailValidator from "email-validator";
|
import * as EmailValidator from "email-validator";
|
||||||
import {parsePhoneNumberFromString} from "libphonenumber-js";
|
import {parsePhoneNumberFromString} from "libphonenumber-js";
|
||||||
import {TagRenderingOptions} from "../../Customizations/TagRenderingOptions";
|
|
||||||
import {CustomLayoutFromJSON} from "../../Customizations/JSON/CustomLayoutFromJSON";
|
|
||||||
import {And, Tag} from "../../Logic/TagsFilter";
|
|
||||||
|
|
||||||
export class ValidatedTextField {
|
export class ValidatedTextField {
|
||||||
public static inputValidation = {
|
public static inputValidation = {
|
||||||
"$": (str) => true,
|
"$": () => true,
|
||||||
"string": (str) => true,
|
"string": () => true,
|
||||||
"date": (str) => true, // TODO validate and add a date picker
|
"date": () => true, // TODO validate and add a date picker
|
||||||
"wikidata": (str) => true, // TODO validate wikidata IDS
|
"wikidata": () => true, // TODO validate wikidata IDS
|
||||||
"int": (str) => {str = ""+str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str))},
|
"int": (str) => {str = ""+str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str))},
|
||||||
"nat": (str) => {str = ""+str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0},
|
"nat": (str) => {str = ""+str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0},
|
||||||
"float": (str) => !isNaN(Number(str)),
|
"float": (str) => !isNaN(Number(str)),
|
||||||
|
@ -31,75 +28,18 @@ export class ValidatedTextField {
|
||||||
return parsePhoneNumberFromString(str, country.toUpperCase()).formatInternational()
|
return parsePhoneNumberFromString(str, country.toUpperCase()).formatInternational()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TagTextField(value: UIEventSource<Tag[]> = undefined, allowEmpty: boolean) {
|
|
||||||
allowEmpty = allowEmpty ?? false;
|
|
||||||
return new TextField<Tag[]>({
|
|
||||||
placeholder: "Tags",
|
|
||||||
fromString: str => {
|
|
||||||
const tags = CustomLayoutFromJSON.TagsFromJson(str);
|
|
||||||
console.log("Parsed",str," --> ",tags)
|
|
||||||
if (tags === []) {
|
|
||||||
if (allowEmpty) {
|
|
||||||
return []
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
,
|
|
||||||
toString: (tags: Tag[]) => {
|
|
||||||
if (tags === undefined || tags === []) {
|
|
||||||
if (allowEmpty) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new And(tags).asHumanString(false, false);
|
|
||||||
},
|
|
||||||
value: value,
|
|
||||||
startValidated: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static
|
|
||||||
|
|
||||||
ValidatedTextField(type: string, options: { value?: UIEventSource<string>, country?: string })
|
|
||||||
: TextField<string> {
|
|
||||||
let isValid = ValidatedTextField.inputValidation[type];
|
|
||||||
if (isValid === undefined
|
|
||||||
) {
|
|
||||||
throw "Invalid type for textfield: " + type
|
|
||||||
}
|
|
||||||
let formatter = ValidatedTextField.formatting[type] ?? ((str) => str);
|
|
||||||
return new TextField<string>({
|
|
||||||
placeholder: type,
|
|
||||||
toString: str => str,
|
|
||||||
fromString: str => isValid(str, options?.country) ? formatter(str, options.country) : undefined,
|
|
||||||
value: options.value,
|
|
||||||
startValidated: true
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextField<T> extends InputElement<T> {
|
export class TextField<T> extends InputElement<T> {
|
||||||
|
|
||||||
|
|
||||||
private value: UIEventSource<string>;
|
private readonly value: UIEventSource<string>;
|
||||||
private mappedValue: UIEventSource<T>;
|
private readonly mappedValue: UIEventSource<T>;
|
||||||
/**
|
public readonly enterPressed = new UIEventSource<string>(undefined);
|
||||||
* Pings and has the value data
|
private readonly _placeholder: UIElement;
|
||||||
*/
|
private readonly _fromString?: (string: string) => T;
|
||||||
public enterPressed = new UIEventSource<string>(undefined);
|
private readonly _toString: (t: T) => string;
|
||||||
private _placeholder: UIElement;
|
private readonly startValidated: boolean;
|
||||||
private _fromString?: (string: string) => T;
|
|
||||||
private _toString: (t: T) => string;
|
|
||||||
private startValidated: boolean;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
|
@ -157,14 +97,6 @@ export class TextField<T> extends InputElement<T> {
|
||||||
GetValue(): UIEventSource<T> {
|
GetValue(): UIEventSource<T> {
|
||||||
return this.mappedValue;
|
return this.mappedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowValue(t: T): boolean {
|
|
||||||
if (!this.IsValid(t)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.mappedValue.setData(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
return `<form onSubmit='return false' class='form-text-field'>` +
|
return `<form onSubmit='return false' class='form-text-field'>` +
|
||||||
`<input type='text' placeholder='${this._placeholder.InnerRender()}' id='text-${this.id}'>` +
|
`<input type='text' placeholder='${this._placeholder.InnerRender()}' id='text-${this.id}'>` +
|
||||||
|
@ -178,7 +110,7 @@ export class TextField<T> extends InputElement<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mappedValue.addCallback((data) => {
|
this.mappedValue.addCallback((data) => {
|
||||||
field.className = this.mappedValue.data !== undefined ? "valid" : "invalid";
|
field.className = data !== undefined ? "valid" : "invalid";
|
||||||
});
|
});
|
||||||
|
|
||||||
field.className = this.mappedValue.data !== undefined ? "valid" : "invalid";
|
field.className = this.mappedValue.data !== undefined ? "valid" : "invalid";
|
||||||
|
|
|
@ -7,10 +7,7 @@ import {SubtleButton} from "./Base/SubtleButton";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||||
import {PersonalLayout} from "../Logic/PersonalLayout";
|
import {PersonalLayout} from "../Logic/PersonalLayout";
|
||||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
|
||||||
import {Layout} from "../Customizations/Layout";
|
import {Layout} from "../Customizations/Layout";
|
||||||
import {CustomLayoutFromJSON} from "../Customizations/JSON/CustomLayoutFromJSON";
|
|
||||||
import {All} from "../Customizations/Layouts/All";
|
|
||||||
|
|
||||||
|
|
||||||
export class MoreScreen extends UIElement {
|
export class MoreScreen extends UIElement {
|
||||||
|
@ -22,26 +19,29 @@ export class MoreScreen extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) {
|
private static createLinkButton(layout: Layout, customThemeDefinition: string = undefined) {
|
||||||
|
if (layout === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
if (layout.hideFromOverview) {
|
if (layout.hideFromOverview) {
|
||||||
if (State.state.osmConnection.GetPreference("hidden-theme-" + layout.name + "-enabled").data !== "true") {
|
if (State.state.osmConnection.GetPreference("hidden-theme-" + layout.id + "-enabled").data !== "true") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layout.name === State.state.layoutToUse.data.name) {
|
if (layout.id === State.state.layoutToUse.data.id) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLocation = State.state.locationControl.data;
|
const currentLocation = State.state.locationControl.data;
|
||||||
let linkText =
|
let linkText =
|
||||||
`./${layout.name.toLowerCase()}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
`./${layout.id.toLowerCase()}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||||
|
|
||||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||||
linkText = `./index.html?layout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
linkText = `./index.html?layout=${layout.id}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customThemeDefinition) {
|
if (customThemeDefinition) {
|
||||||
linkText = `./index.html?userlayout=${layout.name}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}#${customThemeDefinition}`
|
linkText = `./index.html?userlayout=${layout.id}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}#${customThemeDefinition}`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,20 +86,21 @@ export class MoreScreen extends UIElement {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(layout.name !== k){
|
if (layout.id !== k) {
|
||||||
continue; // This layout was added multiple time due to an uppercase
|
continue; // This layout was added multiple time due to an uppercase
|
||||||
}
|
}
|
||||||
els.push(this.createLinkButton(layout));
|
els.push(MoreScreen.createLinkButton(layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const customThemesNames = State.state.installedThemes.data ?? [];
|
const customThemesNames = State.state.installedThemes.data ?? [];
|
||||||
if (customThemesNames !== []) {
|
if (customThemesNames.length > 0) {
|
||||||
|
console.log(customThemesNames)
|
||||||
els.push(Translations.t.general.customThemeIntro)
|
els.push(Translations.t.general.customThemeIntro)
|
||||||
}
|
|
||||||
|
|
||||||
for (const installed of State.state.installedThemes.data) {
|
for (const installed of State.state.installedThemes.data) {
|
||||||
els.push(this.createLinkButton(installed.layout, installed.definition));
|
els.push(MoreScreen.createLinkButton(installed.layout, installed.definition));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,19 +11,15 @@ import {Basemap} from "../Logic/Leaflet/Basemap";
|
||||||
import {FilteredLayer} from "../Logic/FilteredLayer";
|
import {FilteredLayer} from "../Logic/FilteredLayer";
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
|
||||||
import Translation from "./i18n/Translation";
|
import Translation from "./i18n/Translation";
|
||||||
import {SubtleButton} from "./Base/SubtleButton";
|
import {SubtleButton} from "./Base/SubtleButton";
|
||||||
|
|
||||||
export class ShareScreen extends UIElement {
|
export class ShareScreen extends UIElement {
|
||||||
|
private readonly _options: UIElement;
|
||||||
private _shareButton: UIElement;
|
private readonly _iframeCode: UIElement;
|
||||||
|
private readonly _link: UIElement;
|
||||||
private _options: UIElement;
|
private readonly _linkStatus: UIEventSource<string | UIElement>;
|
||||||
private _iframeCode: UIElement;
|
private readonly _editLayout: UIElement;
|
||||||
private _link: UIElement;
|
|
||||||
private _linkStatus: UIEventSource<string | UIElement>;
|
|
||||||
private _editLayout: UIElement;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(undefined)
|
super(undefined)
|
||||||
|
@ -33,8 +29,8 @@ export class ShareScreen extends UIElement {
|
||||||
const optionParts: (UIEventSource<string>)[] = [];
|
const optionParts: (UIEventSource<string>)[] = [];
|
||||||
|
|
||||||
const includeLocation = new CheckBox(
|
const includeLocation = new CheckBox(
|
||||||
new Combine([Img.checkmark, "Include current location"]),
|
new Combine([Img.checkmark, tr.fsIncludeCurrentLocation]),
|
||||||
new Combine([Img.no_checkmark, "Include current location"]),
|
new Combine([Img.no_checkmark, tr.fsIncludeCurrentLocation]),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
optionCheckboxes.push(includeLocation);
|
optionCheckboxes.push(includeLocation);
|
||||||
|
@ -52,11 +48,7 @@ export class ShareScreen extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = (State.state.bm as Basemap).CurrentLayer;
|
const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = (State.state.bm as Basemap).CurrentLayer;
|
||||||
const currentBackground = new VariableUiElement(
|
const currentBackground = tr.fsIncludeCurrentBackgroundMap.Subs({name: layout.id});
|
||||||
currentLayer.map(
|
|
||||||
(layer) => `Include the current background choice <b>${layer.name}</b>`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const includeCurrentBackground = new CheckBox(
|
const includeCurrentBackground = new CheckBox(
|
||||||
new Combine([Img.checkmark, currentBackground]),
|
new Combine([Img.checkmark, currentBackground]),
|
||||||
new Combine([Img.no_checkmark, currentBackground]),
|
new Combine([Img.no_checkmark, currentBackground]),
|
||||||
|
@ -73,8 +65,8 @@ export class ShareScreen extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
const includeLayerChoices = new CheckBox(
|
const includeLayerChoices = new CheckBox(
|
||||||
new Combine([Img.checkmark, "Include the current layer choices"]),
|
new Combine([Img.checkmark, tr.fsIncludeCurrentLayers]),
|
||||||
new Combine([Img.no_checkmark, "Include the current layer choices"]),
|
new Combine([Img.no_checkmark, tr.fsIncludeCurrentLayers]),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
optionCheckboxes.push(includeLayerChoices);
|
optionCheckboxes.push(includeLayerChoices);
|
||||||
|
@ -110,8 +102,7 @@ export class ShareScreen extends UIElement {
|
||||||
|
|
||||||
const checkbox = new CheckBox(
|
const checkbox = new CheckBox(
|
||||||
new Combine([Img.checkmark, Translations.W(swtch.human)]),
|
new Combine([Img.checkmark, Translations.W(swtch.human)]),
|
||||||
new Combine([Img.no_checkmark, Translations.W(swtch.human)]),
|
new Combine([Img.no_checkmark, Translations.W(swtch.human)]), !swtch.reverse
|
||||||
swtch.reverse ? false : true
|
|
||||||
);
|
);
|
||||||
optionCheckboxes.push(checkbox);
|
optionCheckboxes.push(checkbox);
|
||||||
optionParts.push(checkbox.isEnabled.map((isEn) => {
|
optionParts.push(checkbox.isEnabled.map((isEn) => {
|
||||||
|
@ -136,7 +127,7 @@ export class ShareScreen extends UIElement {
|
||||||
const url = currentLocation.map(() => {
|
const url = currentLocation.map(() => {
|
||||||
|
|
||||||
|
|
||||||
let literalText = "https://pietervdvn.github.io/MapComplete/" + layout.name + ".html"
|
let literalText = "https://pietervdvn.github.io/MapComplete/" + layout.id.toLowerCase() + ".html"
|
||||||
|
|
||||||
const parts = Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data)));
|
const parts = Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data)));
|
||||||
|
|
||||||
|
@ -190,18 +181,18 @@ export class ShareScreen extends UIElement {
|
||||||
const self = this;
|
const self = this;
|
||||||
this._link = new VariableUiElement(
|
this._link = new VariableUiElement(
|
||||||
url.map((url) => {
|
url.map((url) => {
|
||||||
return `<input type="text" value=" ${url}" id="code-link--copyable" style="width:90%"readonly>`
|
return `<input type="text" value=" ${url}" id="code-link--copyable" style="width:90%">`
|
||||||
})
|
})
|
||||||
).onClick(async () => {
|
).onClick(async () => {
|
||||||
|
|
||||||
const shareData = {
|
const shareData = {
|
||||||
title: Translations.W(layout.name)?.InnerRender() ?? "",
|
title: Translations.W(layout.id)?.InnerRender() ?? "",
|
||||||
text: Translations.W(layout.description)?.InnerRender() ?? "",
|
text: Translations.W(layout.description)?.InnerRender() ?? "",
|
||||||
url: self._link.data,
|
url: self._link.data,
|
||||||
}
|
}
|
||||||
|
|
||||||
function rejected() {
|
function rejected() {
|
||||||
var copyText = document.getElementById("code-link--copyable");
|
const copyText = document.getElementById("code-link--copyable");
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
copyText.select();
|
copyText.select();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {UIElement} from "./UIElement";
|
import {UIElement} from "./UIElement";
|
||||||
import {Tag, TagUtils} from "../Logic/TagsFilter";
|
import {Tag, TagUtils} from "../Logic/Tags";
|
||||||
import {FilteredLayer} from "../Logic/FilteredLayer";
|
import {FilteredLayer} from "../Logic/FilteredLayer";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import Combine from "./Base/Combine";
|
import Combine from "./Base/Combine";
|
||||||
|
@ -8,8 +8,6 @@ import Locale from "./i18n/Locale";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
|
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
|
||||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,8 +42,7 @@ export class SimpleAddUI extends UIElement {
|
||||||
this._loginButton = Translations.t.general.add.pleaseLogin.Clone().onClick(() => State.state.osmConnection.AttemptLogin());
|
this._loginButton = Translations.t.general.add.pleaseLogin.Clone().onClick(() => State.state.osmConnection.AttemptLogin());
|
||||||
|
|
||||||
this._addButtons = [];
|
this._addButtons = [];
|
||||||
this.clss = "add-ui"
|
this.SetClass("add-ui");
|
||||||
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
for (const layer of State.state.filteredLayers.data) {
|
for (const layer of State.state.filteredLayers.data) {
|
||||||
|
@ -67,7 +64,7 @@ export class SimpleAddUI extends UIElement {
|
||||||
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
let tagInfo = "";
|
let tagInfo = "";
|
||||||
if (csCount > State.userJourney.tagsVisibleAt) {
|
if (csCount > State.userJourney.tagsVisibleAt) {
|
||||||
tagInfo = preset.tags.map(t => t.asHumanString(false)).join("&");
|
tagInfo = preset.tags.map(t => t.asHumanString(false, true)).join("&");
|
||||||
tagInfo = `<br/><span class='subtle'>${tagInfo}</span>`
|
tagInfo = `<br/><span class='subtle'>${tagInfo}</span>`
|
||||||
}
|
}
|
||||||
const button: UIElement =
|
const button: UIElement =
|
||||||
|
@ -115,7 +112,6 @@ export class SimpleAddUI extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreatePoint(tags: Tag[], layerToAddTo: FilteredLayer) {
|
private CreatePoint(tags: Tag[], layerToAddTo: FilteredLayer) {
|
||||||
const self = this;
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
||||||
const loc = State.state.bm.LastClickLocation.data;
|
const loc = State.state.bm.LastClickLocation.data;
|
||||||
|
@ -139,7 +135,7 @@ export class SimpleAddUI extends UIElement {
|
||||||
let tagInfo = "";
|
let tagInfo = "";
|
||||||
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
if (csCount > State.userJourney.tagsVisibleAt) {
|
if (csCount > State.userJourney.tagsVisibleAt) {
|
||||||
tagInfo = this._confirmPreset.data .tags.map(t => t.asHumanString(csCount > State.userJourney.tagsVisibleAndWikiLinked)).join("&");
|
tagInfo = this._confirmPreset.data .tags.map(t => t.asHumanString(csCount > State.userJourney.tagsVisibleAndWikiLinked, true)).join("&");
|
||||||
tagInfo = `<br/>More information about the preset: ${tagInfo}`
|
tagInfo = `<br/>More information about the preset: ${tagInfo}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,14 +193,7 @@ export class SimpleAddUI extends UIElement {
|
||||||
return new Combine([header, Translations.t.general.add.stillLoading]).Render()
|
return new Combine([header, Translations.t.general.add.stillLoading]).Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return header.Render() + new Combine(this._addButtons, "add-popup-all-buttons").Render();
|
||||||
var html = "";
|
|
||||||
for (const button of this._addButtons) {
|
|
||||||
html += button.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return header.Render() + new Combine([html], "add-popup-all-buttons").Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection";
|
|
||||||
import Locale from "../UI/i18n/Locale";
|
import Locale from "../UI/i18n/Locale";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {Layout} from "../Customizations/Layout";
|
import {Layout} from "../Customizations/Layout";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
|
||||||
import Combine from "./Base/Combine";
|
import Combine from "./Base/Combine";
|
||||||
|
|
||||||
|
|
||||||
export class WelcomeMessage extends UIElement {
|
export class WelcomeMessage extends UIElement {
|
||||||
private readonly layout: Layout;
|
|
||||||
private languagePicker: UIElement;
|
private languagePicker: UIElement;
|
||||||
private osmConnection: OsmConnection;
|
|
||||||
|
|
||||||
private readonly description: UIElement;
|
private readonly description: UIElement;
|
||||||
private readonly plzLogIn: UIElement;
|
private readonly plzLogIn: UIElement;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import {UIElement} from "../UIElement"
|
import {UIElement} from "../UIElement"
|
||||||
import Locale from "./Locale"
|
import Locale from "./Locale"
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import {TagUtils} from "../../Logic/TagsFilter";
|
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +15,9 @@ export default class Translation extends UIElement {
|
||||||
const combined = [];
|
const combined = [];
|
||||||
const parts = template.split("{" + k + "}");
|
const parts = template.split("{" + k + "}");
|
||||||
const el: string | UIElement = text[k];
|
const el: string | UIElement = text[k];
|
||||||
|
if(el === undefined){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let rtext: string = "";
|
let rtext: string = "";
|
||||||
if (typeof (el) === "string") {
|
if (typeof (el) === "string") {
|
||||||
rtext = el;
|
rtext = el;
|
||||||
|
@ -51,6 +52,7 @@ export default class Translation extends UIElement {
|
||||||
for (const i in this.translations) {
|
for (const i in this.translations) {
|
||||||
return this.translations[i]; // Return a random language
|
return this.translations[i]; // Return a random language
|
||||||
}
|
}
|
||||||
|
console.log("Missing language ",Locale.language.data,"for",this.translations)
|
||||||
return "Missing translation"
|
return "Missing translation"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +64,10 @@ export default class Translation extends UIElement {
|
||||||
|
|
||||||
constructor(translations: object) {
|
constructor(translations: object) {
|
||||||
super(Locale.language)
|
super(Locale.language)
|
||||||
|
let count = 0;
|
||||||
|
for (const translationsKey in translations) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
this.translations = translations
|
this.translations = translations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,19 +128,16 @@ export default class Translations {
|
||||||
question: new T({
|
question: new T({
|
||||||
en: 'Is this parking covered? Also select "covered" for indoor parkings.',
|
en: 'Is this parking covered? Also select "covered" for indoor parkings.',
|
||||||
nl: 'Is deze parking overdekt? Selecteer ook "overdekt" voor fietsparkings binnen een gebouw.',
|
nl: 'Is deze parking overdekt? Selecteer ook "overdekt" voor fietsparkings binnen een gebouw.',
|
||||||
fr: 'TODO: fr',
|
|
||||||
gl: 'Este aparcadoiro está cuberto? Tamén escolle "cuberto" para aparcadoiros interiores.'
|
gl: 'Este aparcadoiro está cuberto? Tamén escolle "cuberto" para aparcadoiros interiores.'
|
||||||
}),
|
}),
|
||||||
yes: new T({
|
yes: new T({
|
||||||
en: 'This parking is covered (it has a roof)',
|
en: 'This parking is covered (it has a roof)',
|
||||||
nl: 'Deze parking is overdekt (er is een afdak)',
|
nl: 'Deze parking is overdekt (er is een afdak)',
|
||||||
fr: 'TODO: fr',
|
|
||||||
gl: 'Este aparcadoiro está cuberto (ten un teito)'
|
gl: 'Este aparcadoiro está cuberto (ten un teito)'
|
||||||
}),
|
}),
|
||||||
no: new T({
|
no: new T({
|
||||||
en: 'This parking is not covered',
|
en: 'This parking is not covered',
|
||||||
nl: 'Deze parking is niet overdekt',
|
nl: 'Deze parking is niet overdekt',
|
||||||
fr: 'TODO: fr',
|
|
||||||
gl: 'Este aparcadoiro non está cuberto'
|
gl: 'Este aparcadoiro non está cuberto'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -148,19 +145,16 @@ export default class Translations {
|
||||||
question: new T({
|
question: new T({
|
||||||
en: "How many bicycles fit in this bicycle parking (including possible cargo bicycles)?",
|
en: "How many bicycles fit in this bicycle parking (including possible cargo bicycles)?",
|
||||||
nl: "Voor hoeveel fietsen is er bij deze fietsparking plaats (inclusief potentiëel bakfietsen)?",
|
nl: "Voor hoeveel fietsen is er bij deze fietsparking plaats (inclusief potentiëel bakfietsen)?",
|
||||||
fr: "TODO: fr",
|
|
||||||
gl: "Cantas bicicletas caben neste aparcadoiro de bicicletas (incluídas as posíbeis bicicletas de carga)?"
|
gl: "Cantas bicicletas caben neste aparcadoiro de bicicletas (incluídas as posíbeis bicicletas de carga)?"
|
||||||
}),
|
}),
|
||||||
template: new T({
|
template: new T({
|
||||||
en: "This parking fits $nat$ bikes",
|
en: "This parking fits $nat$ bikes",
|
||||||
nl: "Deze parking heeft plaats voor $nat$ fietsen",
|
nl: "Deze parking heeft plaats voor $nat$ fietsen",
|
||||||
fr: "TODO: fr",
|
|
||||||
gl: "Neste aparcadoiro caben $nat$ bicicletas"
|
gl: "Neste aparcadoiro caben $nat$ bicicletas"
|
||||||
}),
|
}),
|
||||||
render: new T({
|
render: new T({
|
||||||
en: "Place for {capacity} bikes (in total)",
|
en: "Place for {capacity} bikes (in total)",
|
||||||
nl: "Plaats voor {capacity} fietsen (in totaal)",
|
nl: "Plaats voor {capacity} fietsen (in totaal)",
|
||||||
fr: "TODO: fr",
|
|
||||||
gl: "Lugar para {capacity} bicicletas (en total)"
|
gl: "Lugar para {capacity} bicicletas (en total)"
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -1308,6 +1302,18 @@ export default class Translations {
|
||||||
en: "Enable the 'geolocate-me' button (mobile only)",
|
en: "Enable the 'geolocate-me' button (mobile only)",
|
||||||
gl: "Activar o botón de 'xeolocalizarme' (só móbil)",
|
gl: "Activar o botón de 'xeolocalizarme' (só móbil)",
|
||||||
nl: "Toon het knopje voor geolocalisatie (enkel op mobiel)"
|
nl: "Toon het knopje voor geolocalisatie (enkel op mobiel)"
|
||||||
|
}),
|
||||||
|
fsIncludeCurrentBackgroundMap: new T({
|
||||||
|
en: "Include the current background choice <b>{name}</b>",
|
||||||
|
nl: "Gebruik de huidige achtergrond <b>{name}</b>"
|
||||||
|
}),
|
||||||
|
fsIncludeCurrentLayers: new T({
|
||||||
|
en: "Include the current layer choices",
|
||||||
|
nl: "Toon enkel de huidig getoonde lagen"
|
||||||
|
}),
|
||||||
|
fsIncludeCurrentLocation: new T({
|
||||||
|
en: "Include current location",
|
||||||
|
nl: "Start op de huidige locatie"
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
morescreen: {
|
morescreen: {
|
||||||
|
|
|
@ -1,134 +1,171 @@
|
||||||
{
|
{
|
||||||
|
"id": "aed",
|
||||||
|
"title": {
|
||||||
|
"en": "Open AED Map",
|
||||||
|
"fr": "Carte AED",
|
||||||
|
"nl": "Open AED-kaart"
|
||||||
|
},
|
||||||
|
"maintainer": "MapComplete",
|
||||||
|
"icon": "./assets/themes/aed/aed.svg",
|
||||||
|
"description": {
|
||||||
|
"en": "On this map, one can find and mark nearby defibrillators",
|
||||||
|
"fr": "Sur cette carte, vous pouvez trouver et améliorer les informations sur les défibrillateurs",
|
||||||
|
"nl": "Op deze kaart kan je informatie over AEDs vinden en verbeteren"
|
||||||
|
},
|
||||||
"language": [
|
"language": [
|
||||||
"en",
|
"en",
|
||||||
"fr"
|
"fr",
|
||||||
|
"nl"
|
||||||
],
|
],
|
||||||
"startLat": "0",
|
"version": "2020-08-29",
|
||||||
"startLon": "0",
|
"startLat": 0,
|
||||||
"startZoom": "12",
|
"startLon": 0,
|
||||||
"maintainer": "Pieter Vander Vennet",
|
"startZoom": 12,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"name": "Defibrillator",
|
"id": "Defibrillator",
|
||||||
|
"name": {
|
||||||
|
"en": "Defibrillators",
|
||||||
|
"fr": "Défibrillateurs",
|
||||||
|
"nl": "Defibrillatoren"
|
||||||
|
},
|
||||||
|
"overpassTags": "emergency=defibrillator",
|
||||||
|
"minzoom": 12,
|
||||||
"title": {
|
"title": {
|
||||||
"key": "*",
|
|
||||||
"render": {
|
"render": {
|
||||||
"en": "Defibrillator",
|
"en": "Defibrillator",
|
||||||
"fr": "Défibrillateur"
|
"fr": "Défibrillateur",
|
||||||
|
"nl": "Defibrillator"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"icon": {
|
"icon": "./assets/themes/aed/aed.svg",
|
||||||
"key": "*",
|
"color": "#0000ff",
|
||||||
"render": "./assets/themes/aed/aed.svg"
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"render": "#0000ff",
|
|
||||||
"key": "*"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"en": "A defibrillator",
|
|
||||||
"fr": "Un défibrillateur"
|
|
||||||
},
|
|
||||||
"minzoom": "12",
|
|
||||||
"presets": [
|
"presets": [
|
||||||
{
|
{
|
||||||
"title": {
|
"title": {
|
||||||
"en": "Defibrillator",
|
"en": "Defibrillator",
|
||||||
"fr": "Défibrillateur"
|
"fr": "Défibrillateur",
|
||||||
|
"nl": "Defibrillator"
|
||||||
},
|
},
|
||||||
"tags": "emergency=defibrillator",
|
"tags": [
|
||||||
"description": "A defibrillator"
|
"emergency=defibrillator"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tagRenderings": [
|
"tagRenderings": [
|
||||||
|
"pictures",
|
||||||
{
|
{
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
|
"question": {
|
||||||
|
"en": "Is this defibrillator located indoors?",
|
||||||
|
"fr": "Ce défibrillateur est-il disposé en intérieur ?",
|
||||||
|
"nl": "Hangt deze defibrillator binnen of buiten?"
|
||||||
|
},
|
||||||
|
"if": "indoor=yes",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "This defibrillator is located indoors",
|
"en": "This defibrillator is located indoors",
|
||||||
"fr": "Ce défibrillateur est en intérieur (dans un batiment)"
|
"fr": "Ce défibrillateur est en intérieur (dans un batiment)",
|
||||||
},
|
"nl": "Deze defibrillator bevindt zich in een gebouw"
|
||||||
"if": "indoor=yes"
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"if": "indoor=no",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "This defibrillator is located outdoors",
|
"en": "This defibrillator is located outdoors",
|
||||||
"fr": "Ce défibrillateur est situé en extérieur"
|
"fr": "Ce défibrillateur est situé en extérieur",
|
||||||
},
|
"nl": "Deze defibrillator hangt buiten"
|
||||||
"if": "indoor=no"
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"question": {
|
|
||||||
"en": "Is this defibrillator located indoors?",
|
|
||||||
"fr": "Ce défibrillateur est-il disposé en intérieur ?"
|
|
||||||
},
|
|
||||||
"type": "text"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"question": {
|
||||||
|
"en": "Is this defibrillator freely accessible?",
|
||||||
|
"fr": "Ce défibrillateur est-il librement accessible?",
|
||||||
|
"nl": "Is deze defibrillator vrij toegankelijk?"
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"en": "Access is {access}",
|
||||||
|
"nl": "Toegankelijkheid is {access}",
|
||||||
|
"fr": "{access} accessible"
|
||||||
|
},
|
||||||
|
"condition": "indoor=yes",
|
||||||
|
"freeform": {
|
||||||
|
"key": "access",
|
||||||
|
"addExtraTags": "fixme=Freeform field used for access - doublecheck the value"
|
||||||
|
},
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
|
"if": "access=yes",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "Publicly accessible",
|
"en": "Publicly accessible",
|
||||||
"fr": "Librement accessible"
|
"fr": "Librement accessible",
|
||||||
},
|
"nl": "Publiek toegankelijk"
|
||||||
"if": "access=yes"
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"if": "access=public",
|
||||||
|
"then": {
|
||||||
|
"en": "Publicly accessible",
|
||||||
|
"fr": "Librement accessible",
|
||||||
|
"nl": "Publiek toegankelijk"
|
||||||
|
},
|
||||||
|
"hideInAnswer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": "access=customers",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "Only accessible to customers",
|
"en": "Only accessible to customers",
|
||||||
"fr": "Réservé aux clients du lieu"
|
"fr": "Réservé aux clients du lieu",
|
||||||
},
|
"nl": "Enkel toegankeleijk voor klanten"
|
||||||
"if": "access=customers"
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"if": "access=private",
|
"if": "access=private",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "Not accessible to the general public (e.g. only accesible to staff, the owners, ...)",
|
"en": "Not accessible to the general public (e.g. only accesible to staff, the owners, ...)",
|
||||||
"fr": "Non accessible au public (par exemple réservé au personnel, au propriétaire, ...)"
|
"fr": "Non accessible au public (par exemple réservé au personnel, au propriétaire, ...)",
|
||||||
|
"nl": "Niet toegankelijk voor het publiek (bv. enkel voor personneel, de eigenaar, ...)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"question": {
|
|
||||||
"en": "Is this defibrillator freely accessible?",
|
|
||||||
"fr": "Ce défibrillateur est-il librement accessible ?"
|
|
||||||
},
|
|
||||||
"type": "text",
|
|
||||||
"key": "access",
|
|
||||||
"condition": "indoor=yes"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "level",
|
|
||||||
"mappings": [],
|
|
||||||
"question": {
|
"question": {
|
||||||
"en": "On which floor is this defibrillator located?",
|
"en": "On which floor is this defibrillator located?",
|
||||||
"fr": "À quel étage est situé ce défibrillateur ?"
|
"fr": "À quel étage est situé ce défibrillateur?",
|
||||||
|
"nl": "Op welke verdieping bevindt deze defibrillator zich?"
|
||||||
|
},
|
||||||
|
"condition": {
|
||||||
|
"and": [
|
||||||
|
"indoor=yes",
|
||||||
|
"access!~private"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "level",
|
||||||
|
"type": "int"
|
||||||
},
|
},
|
||||||
"type": "int",
|
|
||||||
"render": {
|
"render": {
|
||||||
"en": "This defibrallator is on floor {level}",
|
"en": "This defibrallator is on floor {level}",
|
||||||
"fr": "Ce défibrillateur est à l'étage {level}"
|
"fr": "Ce défibrillateur est à l'étage {level}",
|
||||||
},
|
"nl": "De defibrillator bevindt zicht op verdieping {level}"
|
||||||
"condition": "indoor=yes&access!=private"
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "defibrillator:location",
|
"render": "{defibrillator:location}",
|
||||||
"mappings": [],
|
|
||||||
"question": {
|
"question": {
|
||||||
"en": "Please give some explanation on where the defibrillator can be found",
|
"en": "Please give some explanation on where the defibrillator can be found",
|
||||||
"fr": "Veuillez indiquez plus précisément où se situe le défibrillateur"
|
"fr": "Veuillez indiquez plus précisément où se situe le défibrillateur",
|
||||||
|
"nl": "Gelieve meer informatie te geven over de exacte locatie van de defibrillator"
|
||||||
},
|
},
|
||||||
"type": "text",
|
"freeform": {
|
||||||
"render": "{defibrillator:location}"
|
"type": "text",
|
||||||
|
"key": "defibrillator:location"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"overpassTags": "emergency=defibrillator"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Open AED Map",
|
|
||||||
"icon": "./assets/themes/aed/aed.svg",
|
|
||||||
"name": "aed",
|
|
||||||
"description": {
|
|
||||||
"en": "On this map, one can find and mark nearby defibrillators",
|
|
||||||
"fr": "Sur cette carte, vous pouvez trouver et améliorer les informations sur les défibrillateurs"
|
|
||||||
}
|
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue