From 47d755e59f9c11fbaa81ba98b8ff87f1af46fe56 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 22 Aug 2020 17:33:08 +0200 Subject: [PATCH] Various small fixes, add indication of which tags are added for power users --- Customizations/JSON/CustomLayoutFromJSON.ts | 14 +++++- Customizations/TagRendering.ts | 56 ++++++++++++++------- Logic/TagsFilter.ts | 22 ++++++++ State.ts | 2 +- UI/CustomThemeGenerator/ThemeGenerator.ts | 31 +++++++++--- UI/i18n/Translation.ts | 8 ++- index.css | 5 +- test/Tag.spec.ts | 47 +++++++---------- test/TestHelper.ts | 21 ++++++++ 9 files changed, 147 insertions(+), 59 deletions(-) create mode 100644 test/TestHelper.ts diff --git a/Customizations/JSON/CustomLayoutFromJSON.ts b/Customizations/JSON/CustomLayoutFromJSON.ts index 828681d..2d4b391 100644 --- a/Customizations/JSON/CustomLayoutFromJSON.ts +++ b/Customizations/JSON/CustomLayoutFromJSON.ts @@ -38,6 +38,7 @@ export interface LayerConfigJson { description: string; minzoom: number, color: TagRenderingConfigJson; + width: TagRenderingConfigJson; overpassTags: string | string[] | { k: string, v: string }[]; wayHandling: number, presets: [ @@ -80,6 +81,10 @@ export class CustomLayoutFromJSON { public static TagRenderingFromJson(json: any): TagDependantUIElementConstructor { + if(json === undefined){ + return undefined; + } + if (typeof (json) === "string") { return new FixedText(json); } @@ -89,7 +94,6 @@ export class CustomLayoutFromJSON { 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 + "}", `{${json.key}}` @@ -152,13 +156,19 @@ export class CustomLayoutFromJSON { }) { 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); + let weight = parseInt(thickness?.GetContent(tags)) ?? 10; + if(isNaN(weight)){ + weight = 10; + } return { color: stroke, - weight: 10, + weight: weight, icon: { iconUrl: iconUrl, iconSize: [40, 40], diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index e167045..bb1f2ed 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -23,15 +23,15 @@ export class TagRendering extends UIElement implements TagDependantUIElement { private _priority: number; - private _question: Translation; - private _mapping: { k: TagsFilter, txt: string | Translation, priority?: number }[]; + private _question: string | Translation; + private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[]; private _tagsPreprocessor?: ((tags: any) => any); private _freeform: { key: string, - template: string | Translation, + template: string | UIElement, renderTemplate: string | Translation, - placeholder?: string | Translation, + placeholder?: string | UIElement, extraTags?: TagsFilter }; @@ -42,12 +42,15 @@ export class TagRendering extends UIElement implements TagDependantUIElement { private readonly _skipButton: UIElement; private readonly _editButton: UIElement; + private readonly _appliedTags: UIElement; + private readonly _questionSkipped: UIEventSource = new UIEventSource(false); private readonly _editMode: UIEventSource = new UIEventSource(false); - private static injected = TagRendering.injectFunction(); + private static injected = TagRendering.injectFunction(); + static injectFunction() { // This is a workaround as not to import tagrendering into TagREnderingOptions TagRenderingOptions.tagRendering = (tags, options) => new TagRendering(tags, options); @@ -91,7 +94,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement { }; if (options.question !== undefined) { - this._question = this.ApplyTemplate(options.question); + this._question = options.question; } this._mapping = []; @@ -109,7 +112,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement { const newTags = this._tagsPreprocessor(this._source.data); choiceSubbed = { k: choice.k.substituteValues(newTags), - txt: this.ApplyTemplate(choice.txt), + txt: choice.txt, priority: choice.priority } } @@ -133,6 +136,21 @@ export class TagRendering extends UIElement implements TagDependantUIElement { 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 = () => { self._questionSkipped.setData(true); self._editMode.setData(false); @@ -201,7 +219,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement { 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); } if (elements.length == 1) { @@ -213,8 +230,8 @@ export class TagRendering extends UIElement implements TagDependantUIElement { } - private InputElementForMapping(mapping: { k: TagsFilter, txt: string | UIElement }) { - return new FixedInputElement(mapping.txt, mapping.k); + private InputElementForMapping(mapping: { k: TagsFilter, txt: string | Translation }) { + return new FixedInputElement(this.ApplyTemplate(mapping.txt), mapping.k); } @@ -372,10 +389,11 @@ export class TagRendering extends UIElement implements TagDependantUIElement { return "
" + "" + question + "" + - (this._question.IsEmpty() ? "" : "
") + + (question !== "" ? "" : "
") + "
" + this._questionElement.Render() + "
" + this._skipButton.Render() + this._saveButton.Render() + + this._appliedTags.Render() + "
" } @@ -406,18 +424,20 @@ export class TagRendering extends UIElement implements TagDependantUIElement { return this._priority; } - private ApplyTemplate(template: string | Translation): Translation { + private ApplyTemplate(template: string | Translation): UIElement { if (template === undefined || template === null) { 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") { - const tags = this._tagsPreprocessor(this._source.data); - return new Translation ({en:TagUtils.ApplyTemplate(template, tags)}); + transl = 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())); } diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index a3306bc..fdff5e0 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -7,6 +7,8 @@ export abstract class TagsFilter { matchesProperties(properties: any) : boolean{ return this.matches(TagUtils.proprtiesToKV(properties)); } + + abstract asHumanString(); } @@ -51,6 +53,10 @@ export class Regex extends TagsFilter { substituteValues(tags: any) : TagsFilter{ 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)); } + + asHumanString() { + return this.key+"="+this.value; + } } @@ -190,6 +200,10 @@ export class Or extends TagsFilter { } 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); } + + asHumanString() { + return this.and.map(t => t.asHumanString()).join("&"); + } } @@ -269,6 +287,10 @@ export class Not extends TagsFilter{ substituteValues(tags: any): TagsFilter { return new Not(this.not.substituteValues(tags)); } + + asHumanString() { + return "!"+this.not.asHumanString(); + } } diff --git a/State.ts b/State.ts index 6a6f565..ea60c12 100644 --- a/State.ts +++ b/State.ts @@ -24,7 +24,7 @@ export class State { // The singleton of the global state public static state: State; - public static vNumber = "0.0.5c"; + public static vNumber = "0.0.5d"; public static runningFromConsole: boolean = false; diff --git a/UI/CustomThemeGenerator/ThemeGenerator.ts b/UI/CustomThemeGenerator/ThemeGenerator.ts index 620a959..5277f07 100644 --- a/UI/CustomThemeGenerator/ThemeGenerator.ts +++ b/UI/CustomThemeGenerator/ThemeGenerator.ts @@ -296,7 +296,7 @@ class LayerGenerator extends UIElement { addExtraTags: "", mappings: [], question: "", - render: "Title", + render: "./assets/bug.svg", type: "text" }, { header: "Icon", @@ -304,13 +304,14 @@ class LayerGenerator extends UIElement { removable: false, hideQuestion: true }), + new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.color ?? { key: "*", addExtraTags: "", mappings: [], question: "", - render: "Title", + render: "#0000ff", type: "text" }, { header: "Colour", @@ -319,6 +320,20 @@ class LayerGenerator extends UIElement { 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, { header: "Tag rendering", @@ -406,17 +421,21 @@ class AllLayerComponent extends UIElement { config.data.layers.push({ id: "", title: { - key: "", - render: "title" + key: "*", + render: "Title" }, icon: { - key: "", + key: "*", render: "./assets/bug.svg" }, color: { - key: "", + key: "*", render: "#0000ff" }, + width: { + key:"*", + render: "10" + }, description: "", minzoom: 12, overpassTags: "", diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index 664da2c..505f0ad 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -65,8 +65,12 @@ export default class Translation extends UIElement { this.translations = translations } - public replace(a, b) { - return this.Subs({a: b}); + public replace(a: string, b: string) { + if(a.startsWith("{") && a.endsWith("}")){ + a = a.substr(1, a.length - 2); + } + const result= this.Subs({[a]: b}); + return result; } public R(): string { diff --git a/index.css b/index.css index 670d4eb..6a7e6cd 100644 --- a/index.css +++ b/index.css @@ -79,7 +79,7 @@ form { } .subtle { - color: #cccccc; + color: #999; } .thanks { @@ -959,8 +959,9 @@ form { .infoboxcontents { + margin: 0.5em; margin-top: 1em; - margin-bottom: 0.5em; + } diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 1dcff74..8b25178 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -1,37 +1,28 @@ import {equal} from "assert"; import {UIElement} from "../UI/UIElement"; + UIElement.runningFromConsole = true; import {CustomLayoutFromJSON} from "../Customizations/JSON/CustomLayoutFromJSON"; import {And} from "../Logic/TagsFilter"; +import Translation from "../UI/i18n/Translation"; +import T from "./TestHelper"; -let failures = 0; -function t(descripiton: string, f: () => void) { - - try { - f(); - } catch (e) { - failures++; - console.warn(e); +new T([ + ["Parse and match advanced tagging", () => { + const tags = CustomLayoutFromJSON.TagsFromJson("indoor=yes&access!=private"); + console.log(tags); + const m0 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "yes"}]); + equal(m0, true); + const m1 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "private"}]); + 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"); -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"); - console.log(tags); - const m0 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "yes"}]); - equal(m0, true); - const m1 = new And(tags).matches([{k: "indoor", v: "yes"}, {k: "access", v: "private"}]); - equal(m1, false); -}); - - -done(); \ No newline at end of file + }] +]); diff --git a/test/TestHelper.ts b/test/TestHelper.ts new file mode 100644 index 0000000..101cabf --- /dev/null +++ b/test/TestHelper.ts @@ -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(",")) + } + } + +}