Various small fixes, add indication of which tags are added for power users
This commit is contained in:
parent
a55767c1e9
commit
47d755e59f
9 changed files with 147 additions and 59 deletions
|
@ -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],
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
2
State.ts
2
State.ts
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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: "",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
21
test/TestHelper.ts
Normal 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(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue