Various small fixes, add indication of which tags are added for power users

This commit is contained in:
Pieter Vander Vennet 2020-08-22 17:33:08 +02:00
parent a55767c1e9
commit 47d755e59f
9 changed files with 147 additions and 59 deletions

View file

@ -38,6 +38,7 @@ export interface LayerConfigJson {
description: string; description: string;
minzoom: number, minzoom: number,
color: TagRenderingConfigJson; color: TagRenderingConfigJson;
width: TagRenderingConfigJson;
overpassTags: string | string[] | { k: string, v: string }[]; overpassTags: string | string[] | { k: string, v: string }[];
wayHandling: number, wayHandling: number,
presets: [ presets: [
@ -80,6 +81,10 @@ export class CustomLayoutFromJSON {
public static TagRenderingFromJson(json: any): TagDependantUIElementConstructor { public static TagRenderingFromJson(json: any): TagDependantUIElementConstructor {
if(json === undefined){
return undefined;
}
if (typeof (json) === "string") { if (typeof (json) === "string") {
return new FixedText(json); return new FixedText(json);
} }
@ -89,7 +94,6 @@ export class CustomLayoutFromJSON {
const type = json.type ?? "text"; const type = json.type ?? "text";
let renderTemplate = CustomLayoutFromJSON.MaybeTranslation(json.render);; let renderTemplate = CustomLayoutFromJSON.MaybeTranslation(json.render);;
const template = renderTemplate.replace("{" + json.key + "}", "$" + type + "$"); const template = renderTemplate.replace("{" + json.key + "}", "$" + type + "$");
if(type === "url"){ if(type === "url"){
renderTemplate = json.render.replace("{" + json.key + "}", renderTemplate = json.render.replace("{" + json.key + "}",
`<a href='{${json.key}}' target='_blank'>{${json.key}}</a>` `<a href='{${json.key}}' target='_blank'>{${json.key}}</a>`
@ -152,13 +156,19 @@ export class CustomLayoutFromJSON {
}) { }) {
const iconRendering: TagDependantUIElementConstructor = CustomLayoutFromJSON.TagRenderingFromJson(layout.icon); const iconRendering: TagDependantUIElementConstructor = CustomLayoutFromJSON.TagRenderingFromJson(layout.icon);
const colourRendering = CustomLayoutFromJSON.TagRenderingFromJson(layout.color); const colourRendering = CustomLayoutFromJSON.TagRenderingFromJson(layout.color);
let thickness = CustomLayoutFromJSON.TagRenderingFromJson(layout.width);
return (tags) => { return (tags) => {
const iconUrl = iconRendering.GetContent(tags); const iconUrl = iconRendering.GetContent(tags);
const stroke = colourRendering.GetContent(tags); const stroke = colourRendering.GetContent(tags);
let weight = parseInt(thickness?.GetContent(tags)) ?? 10;
if(isNaN(weight)){
weight = 10;
}
return { return {
color: stroke, color: stroke,
weight: 10, weight: weight,
icon: { icon: {
iconUrl: iconUrl, iconUrl: iconUrl,
iconSize: [40, 40], iconSize: [40, 40],

View file

@ -23,15 +23,15 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
private _priority: number; private _priority: number;
private _question: Translation; private _question: string | Translation;
private _mapping: { k: TagsFilter, txt: string | Translation, priority?: number }[]; private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[];
private _tagsPreprocessor?: ((tags: any) => any); private _tagsPreprocessor?: ((tags: any) => any);
private _freeform: { private _freeform: {
key: string, key: string,
template: string | Translation, template: string | UIElement,
renderTemplate: string | Translation, renderTemplate: string | Translation,
placeholder?: string | Translation, placeholder?: string | UIElement,
extraTags?: TagsFilter extraTags?: TagsFilter
}; };
@ -42,12 +42,15 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
private readonly _skipButton: UIElement; private readonly _skipButton: UIElement;
private readonly _editButton: UIElement; private readonly _editButton: UIElement;
private readonly _appliedTags: UIElement;
private readonly _questionSkipped: UIEventSource<boolean> = new UIEventSource<boolean>(false); private readonly _questionSkipped: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false); private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private static injected = TagRendering.injectFunction(); 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);
@ -91,7 +94,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
}; };
if (options.question !== undefined) { if (options.question !== undefined) {
this._question = this.ApplyTemplate(options.question); this._question = options.question;
} }
this._mapping = []; this._mapping = [];
@ -109,7 +112,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
const newTags = this._tagsPreprocessor(this._source.data); const newTags = this._tagsPreprocessor(this._source.data);
choiceSubbed = { choiceSubbed = {
k: choice.k.substituteValues(newTags), k: choice.k.substituteValues(newTags),
txt: this.ApplyTemplate(choice.txt), txt: choice.txt,
priority: choice.priority priority: choice.priority
} }
} }
@ -133,6 +136,21 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
self._editMode.setData(false); self._editMode.setData(false);
} }
this._appliedTags = new VariableUiElement(
self._questionElement.GetValue().map(
(tags: TagsFilter) => {
if (tags === undefined) {
return "";
}
if ((State.state?.osmConnection?.userDetails?.data?.csCount ?? 0) < 200) {
return "";
}
return tags.asHumanString()
}
)
);
this._appliedTags.clss = "subtle";
const cancel = () => { const cancel = () => {
self._questionSkipped.setData(true); self._questionSkipped.setData(true);
self._editMode.setData(false); self._editMode.setData(false);
@ -201,7 +219,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
if (elements.length == 0) { if (elements.length == 0) {
console.warn("WARNING: no tagrendering with following options:", options);
return new FixedInputElement("This should not happen: no tag renderings defined", undefined); return new FixedInputElement("This should not happen: no tag renderings defined", undefined);
} }
if (elements.length == 1) { if (elements.length == 1) {
@ -213,8 +230,8 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
} }
private InputElementForMapping(mapping: { k: TagsFilter, txt: string | UIElement }) { private InputElementForMapping(mapping: { k: TagsFilter, txt: string | Translation }) {
return new FixedInputElement(mapping.txt, mapping.k); return new FixedInputElement(this.ApplyTemplate(mapping.txt), mapping.k);
} }
@ -372,10 +389,11 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
return "<div class='question'>" + return "<div class='question'>" +
"<span class='question-text'>" + question + "</span>" + "<span class='question-text'>" + question + "</span>" +
(this._question.IsEmpty() ? "" : "<br/>") + (question !== "" ? "" : "<br/>") +
"<div>" + this._questionElement.Render() + "</div>" + "<div>" + this._questionElement.Render() + "</div>" +
this._skipButton.Render() + this._skipButton.Render() +
this._saveButton.Render() + this._saveButton.Render() +
this._appliedTags.Render() +
"</div>" "</div>"
} }
@ -406,18 +424,20 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
return this._priority; return this._priority;
} }
private ApplyTemplate(template: string | Translation): Translation { 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" throw "Trying to apply a template, but the template is null/undefined"
} }
const self = this;
const tags = this._source.map(tags => self._tagsPreprocessor(self._source.data));
let transl : Translation;
if (typeof (template) === "string") { if (typeof (template) === "string") {
const tags = this._tagsPreprocessor(this._source.data); transl = new Translation({en: TagUtils.ApplyTemplate(template, tags)});
return new Translation ({en:TagUtils.ApplyTemplate(template, tags)}); }else{
transl = template;
} }
const tags = this._tagsPreprocessor(this._source.data);
return template.Subs(tags); return new VariableUiElement(tags.map(tags => transl.Subs(tags).InnerRender()));
} }

View file

@ -7,6 +7,8 @@ export abstract class TagsFilter {
matchesProperties(properties: any) : boolean{ matchesProperties(properties: any) : boolean{
return this.matches(TagUtils.proprtiesToKV(properties)); return this.matches(TagUtils.proprtiesToKV(properties));
} }
abstract asHumanString();
} }
@ -51,6 +53,10 @@ export class Regex extends TagsFilter {
substituteValues(tags: any) : TagsFilter{ substituteValues(tags: any) : TagsFilter{
throw "Substituting values is not supported on regex tags" throw "Substituting values is not supported on regex tags"
} }
asHumanString() {
return this._k+"~="+this._r;
}
} }
@ -143,6 +149,10 @@ export class Tag extends TagsFilter {
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() {
return this.key+"="+this.value;
}
} }
@ -190,6 +200,10 @@ export class Or extends TagsFilter {
} }
return new Or(newChoices); return new Or(newChoices);
} }
asHumanString() {
return this.or.map(t => t.asHumanString()).join("|");
}
} }
@ -247,6 +261,10 @@ export class And extends TagsFilter {
} }
return new And(newChoices); return new And(newChoices);
} }
asHumanString() {
return this.and.map(t => t.asHumanString()).join("&");
}
} }
@ -269,6 +287,10 @@ export class Not extends TagsFilter{
substituteValues(tags: any): TagsFilter { substituteValues(tags: any): TagsFilter {
return new Not(this.not.substituteValues(tags)); return new Not(this.not.substituteValues(tags));
} }
asHumanString() {
return "!"+this.not.asHumanString();
}
} }

View file

@ -24,7 +24,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.5c"; public static vNumber = "0.0.5d";
public static runningFromConsole: boolean = false; public static runningFromConsole: boolean = false;

View file

@ -296,7 +296,7 @@ class LayerGenerator extends UIElement {
addExtraTags: "", addExtraTags: "",
mappings: [], mappings: [],
question: "", question: "",
render: "Title", render: "./assets/bug.svg",
type: "text" type: "text"
}, { }, {
header: "Icon", header: "Icon",
@ -305,12 +305,13 @@ class LayerGenerator extends UIElement {
hideQuestion: true hideQuestion: true
}), }),
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.color ?? { new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.color ?? {
key: "*", key: "*",
addExtraTags: "", addExtraTags: "",
mappings: [], mappings: [],
question: "", question: "",
render: "Title", render: "#0000ff",
type: "text" type: "text"
}, { }, {
header: "Colour", header: "Colour",
@ -319,6 +320,20 @@ class LayerGenerator extends UIElement {
hideQuestion: true hideQuestion: true
}), }),
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.width ?? {
key: "*",
addExtraTags: "",
mappings: [],
question: "",
render: "10",
type: "nat"
}, {
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, { ...layerConfig.tagRenderings.map(tr => new TagRenderingGenerator(fullConfig, layerConfig, tr, {
header: "Tag rendering", header: "Tag rendering",
@ -406,17 +421,21 @@ class AllLayerComponent extends UIElement {
config.data.layers.push({ config.data.layers.push({
id: "", id: "",
title: { title: {
key: "", key: "*",
render: "title" render: "Title"
}, },
icon: { icon: {
key: "", key: "*",
render: "./assets/bug.svg" render: "./assets/bug.svg"
}, },
color: { color: {
key: "", key: "*",
render: "#0000ff" render: "#0000ff"
}, },
width: {
key:"*",
render: "10"
},
description: "", description: "",
minzoom: 12, minzoom: 12,
overpassTags: "", overpassTags: "",

View file

@ -65,8 +65,12 @@ export default class Translation extends UIElement {
this.translations = translations this.translations = translations
} }
public replace(a, b) { public replace(a: string, b: string) {
return this.Subs({a: b}); if(a.startsWith("{") && a.endsWith("}")){
a = a.substr(1, a.length - 2);
}
const result= this.Subs({[a]: b});
return result;
} }
public R(): string { public R(): string {

View file

@ -79,7 +79,7 @@ form {
} }
.subtle { .subtle {
color: #cccccc; color: #999;
} }
.thanks { .thanks {
@ -959,8 +959,9 @@ form {
.infoboxcontents { .infoboxcontents {
margin: 0.5em;
margin-top: 1em; margin-top: 1em;
margin-bottom: 0.5em;
} }

View file

@ -1,37 +1,28 @@
import {equal} from "assert"; import {equal} from "assert";
import {UIElement} from "../UI/UIElement"; import {UIElement} from "../UI/UIElement";
UIElement.runningFromConsole = true; UIElement.runningFromConsole = true;
import {CustomLayoutFromJSON} from "../Customizations/JSON/CustomLayoutFromJSON"; import {CustomLayoutFromJSON} from "../Customizations/JSON/CustomLayoutFromJSON";
import {And} from "../Logic/TagsFilter"; import {And} from "../Logic/TagsFilter";
import Translation from "../UI/i18n/Translation";
import T from "./TestHelper";
let failures = 0;
function t(descripiton: string, f: () => void) { new T([
["Parse and match advanced tagging", () => {
try {
f();
} catch (e) {
failures++;
console.warn(e);
}
}
function done() {
if (failures == 0) {
console.log("All tests done!")
} else {
console.warn(failures, "tests failedd :(")
}
}
t("Parse and match advanced tagging", () => {
const tags = CustomLayoutFromJSON.TagsFromJson("indoor=yes&access!=private"); const tags = CustomLayoutFromJSON.TagsFromJson("indoor=yes&access!=private");
console.log(tags); console.log(tags);
const m0 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "yes"}]); const m0 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "yes"}]);
equal(m0, true); equal(m0, true);
const m1 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "private"}]); const m1 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "private"}]);
equal(m1, false); equal(m1, false);
}); }
],
["Tag replacement works in translation", () => {
const tr = new Translation({
"en": "Test {key} abc"
}).replace("{key}", "value");
equal(tr.txt, "Test value abc");
}]
done(); ]);

21
test/TestHelper.ts Normal file
View file

@ -0,0 +1,21 @@
export default class T {
constructor(tests: [string, () => void ][]) {
let failures : string []= [];
for (const [name, test] of tests) {
try {
test();
} catch (e) {
failures.push(name);
console.warn("Failed test: ", name, "because", e);
}
}
if (failures.length == 0) {
console.log("All tests done!")
} else {
console.warn(failures.length, "tests failedd :(")
console.log("Failed tests: ", failures.join(","))
}
}
}