Add more checks on parsing from JSON, fix of those issues on the builtin themes
This commit is contained in:
parent
8da0893c05
commit
97ec893479
9 changed files with 117 additions and 29 deletions
|
@ -108,6 +108,7 @@ export class FromJSON {
|
||||||
throw `Tagrendering ${propertyName} is undefined...`
|
throw `Tagrendering ${propertyName} is undefined...`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (typeof json === "string") {
|
if (typeof json === "string") {
|
||||||
|
|
||||||
switch (json) {
|
switch (json) {
|
||||||
|
@ -137,6 +138,8 @@ export class FromJSON {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// It's the question that drives us, neo
|
||||||
|
const question = FromJSON.Translation(json.question);
|
||||||
|
|
||||||
let template = FromJSON.Translation(json.render);
|
let template = FromJSON.Translation(json.render);
|
||||||
|
|
||||||
|
@ -165,28 +168,35 @@ export class FromJSON {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappings = json.mappings?.map(mapping => (
|
const mappings = json.mappings?.map((mapping, i) => {
|
||||||
{
|
const k = FromJSON.Tag(mapping.if, `IN mapping #${i} of tagrendering ${propertyName}`)
|
||||||
k: FromJSON.Tag(mapping.if),
|
|
||||||
txt: FromJSON.Translation(mapping.then),
|
if (question !== undefined && !mapping.hideInAnswer && !k.isUsableAsAnswer()) {
|
||||||
hideInAnswer: mapping.hideInAnswer
|
throw `Invalid mapping in ${propertyName}: the tags use an OR-expression or regex expression but are also assignable as answer.`
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
k: k,
|
||||||
|
txt: FromJSON.Translation(mapping.then),
|
||||||
|
hideInAnswer: mapping.hideInAnswer
|
||||||
|
};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if(template === undefined && (mappings === undefined || mappings.length === 0)){
|
if (template === undefined && (mappings === undefined || mappings.length === 0)) {
|
||||||
console.error("Empty tagrendering detected: no mappings nor template given", json)
|
console.error(`Empty tagrendering detected in ${propertyName}: no mappings nor template given`, json)
|
||||||
throw `Empty tagrendering ${propertyName} detected: no mappings nor template given`
|
throw `Empty tagrendering ${propertyName} detected: no mappings nor template given`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let rendering = new TagRenderingOptions({
|
let rendering = new TagRenderingOptions({
|
||||||
question: FromJSON.Translation(json.question),
|
question: question,
|
||||||
freeform: freeform,
|
freeform: freeform,
|
||||||
mappings: mappings
|
mappings: mappings
|
||||||
});
|
});
|
||||||
|
|
||||||
if (json.condition) {
|
if (json.condition) {
|
||||||
return rendering.OnlyShowIf(FromJSON.Tag(json.condition));
|
return rendering.OnlyShowIf(FromJSON.Tag(json.condition, `In tagrendering ${propertyName}.condition`));
|
||||||
}
|
}
|
||||||
|
|
||||||
return rendering;
|
return rendering;
|
||||||
|
@ -197,7 +207,7 @@ export class FromJSON {
|
||||||
return new Tag(tag[0], tag[1]);
|
return new Tag(tag[0], tag[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tag(json: AndOrTagConfigJson | string): TagsFilter {
|
public static Tag(json: AndOrTagConfigJson | string, context: string): TagsFilter {
|
||||||
if(json === undefined){
|
if(json === undefined){
|
||||||
throw "Error while parsing a tag: nothing defined. Make sure all the tags are defined and at least one tag is present in a complex expression"
|
throw "Error while parsing a tag: nothing defined. Make sure all the tags are defined and at least one tag is present in a complex expression"
|
||||||
}
|
}
|
||||||
|
@ -206,7 +216,7 @@ export class FromJSON {
|
||||||
if (tag.indexOf("!~") >= 0) {
|
if (tag.indexOf("!~") >= 0) {
|
||||||
const split = Utils.SplitFirst(tag, "!~");
|
const split = Utils.SplitFirst(tag, "!~");
|
||||||
if (split[1] === "*") {
|
if (split[1] === "*") {
|
||||||
split[1] = "..*"
|
throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})`
|
||||||
}
|
}
|
||||||
return new RegexTag(
|
return new RegexTag(
|
||||||
split[0],
|
split[0],
|
||||||
|
@ -214,6 +224,16 @@ export class FromJSON {
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (tag.indexOf("~~") >= 0) {
|
||||||
|
const split = Utils.SplitFirst(tag, "~~");
|
||||||
|
if (split[1] === "*") {
|
||||||
|
split[1] = "..*"
|
||||||
|
}
|
||||||
|
return new RegexTag(
|
||||||
|
new RegExp("^" + split[0] + "$"),
|
||||||
|
new RegExp("^" + split[1] + "$")
|
||||||
|
);
|
||||||
|
}
|
||||||
if (tag.indexOf("!=") >= 0) {
|
if (tag.indexOf("!=") >= 0) {
|
||||||
const split = Utils.SplitFirst(tag, "!=");
|
const split = Utils.SplitFirst(tag, "!=");
|
||||||
if (split[1] === "*") {
|
if (split[1] === "*") {
|
||||||
|
@ -236,13 +256,16 @@ export class FromJSON {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const split = Utils.SplitFirst(tag, "=");
|
const split = Utils.SplitFirst(tag, "=");
|
||||||
|
if(split[1] == "*"){
|
||||||
|
throw `Error while parsing tag '${tag}' in ${context}: detected a wildcard on a normal value. Use a regex pattern instead`
|
||||||
|
}
|
||||||
return new Tag(split[0], split[1])
|
return new Tag(split[0], split[1])
|
||||||
}
|
}
|
||||||
if (json.and !== undefined) {
|
if (json.and !== undefined) {
|
||||||
return new And(json.and.map(FromJSON.Tag));
|
return new And(json.and.map(t => FromJSON.Tag(t, context)));
|
||||||
}
|
}
|
||||||
if (json.or !== undefined) {
|
if (json.or !== undefined) {
|
||||||
return new Or(json.or.map(FromJSON.Tag));
|
return new Or(json.or.map(t => FromJSON.Tag(t, context)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +288,7 @@ export class FromJSON {
|
||||||
|
|
||||||
console.log("Parsing layer", json)
|
console.log("Parsing layer", json)
|
||||||
const tr = FromJSON.Translation;
|
const tr = FromJSON.Translation;
|
||||||
const overpassTags = FromJSON.Tag(json.overpassTags);
|
const overpassTags = FromJSON.Tag(json.overpassTags, "overpasstags for layer "+json.id);
|
||||||
const icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg");
|
const icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg");
|
||||||
const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center");
|
const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center");
|
||||||
const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff");
|
const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff");
|
||||||
|
|
|
@ -7,14 +7,23 @@ import BikeCafes from "../Layers/BikeCafes";
|
||||||
|
|
||||||
|
|
||||||
export default class Cyclofix extends Layout {
|
export default class Cyclofix extends Layout {
|
||||||
|
|
||||||
|
private static RememberTheDead(): boolean {
|
||||||
|
const now = new Date();
|
||||||
|
const m = now.getMonth() + 1;
|
||||||
|
const day = new Date().getDay() + 1;
|
||||||
|
const date = day + "/" + m;
|
||||||
|
return (date === "31/10" || date === "1/11" || date === "2/11");
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
"cyclofix",
|
"cyclofix",
|
||||||
["en", "nl", "fr","gl"],
|
["en", "nl", "fr", "gl"],
|
||||||
Translations.t.cyclofix.title,
|
Translations.t.cyclofix.title,
|
||||||
["bike_repair_station", new BikeShops(), "drinking_water", "bike_parking", new BikeOtherShops(), new BikeCafes(),
|
["bike_repair_station", new BikeShops(), "drinking_water", "bike_parking", new BikeOtherShops(), new BikeCafes(),
|
||||||
// The first of november, we remember our dead
|
// The first of november, we remember our dead
|
||||||
...(new Date().getMonth() + 1 == 11 && new Date().getDay() + 1 == 1 ? ["ghost_bike"] : [])],
|
...(Cyclofix.RememberTheDead() ? ["ghost_bike"] : [])],
|
||||||
16,
|
16,
|
||||||
50.8465573,
|
50.8465573,
|
||||||
4.3516970,
|
4.3516970,
|
||||||
|
|
|
@ -4,6 +4,7 @@ export abstract class TagsFilter {
|
||||||
abstract matches(tags: { k: string, v: string }[]): boolean
|
abstract matches(tags: { k: string, v: string }[]): boolean
|
||||||
abstract asOverpass(): string[]
|
abstract asOverpass(): string[]
|
||||||
abstract substituteValues(tags: any) : TagsFilter;
|
abstract substituteValues(tags: any) : TagsFilter;
|
||||||
|
abstract isUsableAsAnswer() : boolean;
|
||||||
|
|
||||||
matchesProperties(properties: Map<string, string>): boolean {
|
matchesProperties(properties: Map<string, string>): boolean {
|
||||||
return this.matches(TagUtils.proprtiesToKV(properties));
|
return this.matches(TagUtils.proprtiesToKV(properties));
|
||||||
|
@ -43,6 +44,10 @@ export class RegexTag extends TagsFilter {
|
||||||
return r.source;
|
return r.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
matches(tags: { k: string; v: string }[]): boolean {
|
matches(tags: { k: string; v: string }[]): boolean {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (RegexTag.doesMatch(tag.k, this.key)){
|
if (RegexTag.doesMatch(tag.k, this.key)){
|
||||||
|
@ -58,7 +63,10 @@ export class RegexTag extends TagsFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString() {
|
||||||
return `${RegexTag.source(this.key)}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`;
|
if (typeof this.key === "string") {
|
||||||
|
return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`;
|
||||||
|
}
|
||||||
|
return `~${this.key.source}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +86,7 @@ export class Tag extends TagsFilter {
|
||||||
throw "Invalid value";
|
throw "Invalid value";
|
||||||
}
|
}
|
||||||
if(value === "*"){
|
if(value === "*"){
|
||||||
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}!~*`)
|
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}~* ?`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +136,10 @@ export class Tag extends TagsFilter {
|
||||||
}
|
}
|
||||||
return this.key + "=" + v;
|
return this.key + "=" + v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -171,6 +183,10 @@ export class Or extends TagsFilter {
|
||||||
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
||||||
return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|");
|
return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -231,6 +247,15 @@ export class And extends TagsFilter {
|
||||||
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
asHumanString(linkToWiki: boolean, shorten: boolean) {
|
||||||
return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&");
|
return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsableAsAnswer(): boolean {
|
||||||
|
for (const t of this.and) {
|
||||||
|
if(!t.isUsableAsAnswer()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,4 +286,5 @@ export class TagUtils {
|
||||||
}
|
}
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default class SharePanel extends UIElement {
|
||||||
const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`)
|
const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`)
|
||||||
|
|
||||||
this._panel = new Combine([
|
this._panel = new Combine([
|
||||||
"<h2>share</h2>",
|
"<h2>Share</h2>",
|
||||||
"Share the following link with friends:<br/>",
|
"Share the following link with friends:<br/>",
|
||||||
new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)),
|
new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)),
|
||||||
"<h2>Publish on OSM Wiki</h2>",
|
"<h2>Publish on OSM Wiki</h2>",
|
||||||
|
|
|
@ -14,6 +14,8 @@ import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson";
|
||||||
import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson";
|
import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson";
|
||||||
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
||||||
import {State} from "../../State";
|
import {State} from "../../State";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import {FromJSON} from "../../Customizations/JSON/FromJSON";
|
||||||
|
|
||||||
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
|
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
|
||||||
|
|
||||||
|
@ -24,6 +26,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
|
||||||
private readonly _value: UIEventSource<TagRenderingConfigJson>;
|
private readonly _value: UIEventSource<TagRenderingConfigJson>;
|
||||||
public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; };
|
public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; };
|
||||||
|
|
||||||
|
public readonly validText : UIElement;
|
||||||
|
|
||||||
constructor(languages: UIEventSource<string[]>,
|
constructor(languages: UIEventSource<string[]>,
|
||||||
currentlySelected: UIEventSource<SingleSetting<any>>,
|
currentlySelected: UIEventSource<SingleSetting<any>>,
|
||||||
userDetails: UserDetails,
|
userDetails: UserDetails,
|
||||||
|
@ -95,12 +99,24 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
|
||||||
];
|
];
|
||||||
|
|
||||||
this.settingsTable = new SettingsTable(settings, currentlySelected);
|
this.settingsTable = new SettingsTable(settings, currentlySelected);
|
||||||
|
|
||||||
|
|
||||||
|
this.validText = new VariableUiElement(value.map((json: TagRenderingConfigJson) => {
|
||||||
|
try{
|
||||||
|
FromJSON.TagRendering(json, options?.title ?? "");
|
||||||
|
return "";
|
||||||
|
}catch(e){
|
||||||
|
return "<span class='alert'>"+e+"</span>"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
return new Combine([
|
return new Combine([
|
||||||
this.intro,
|
this.intro,
|
||||||
this.settingsTable]).Render();
|
this.settingsTable,
|
||||||
|
this.validText]).Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<TagRenderingConfigJson> {
|
GetValue(): UIEventSource<TagRenderingConfigJson> {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import {DropDown} from "./DropDown";
|
||||||
import {TextField} from "./TextField";
|
import {TextField} from "./TextField";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
import {UIElement} from "../UIElement";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import {FromJSON} from "../../Customizations/JSON/FromJSON";
|
||||||
|
|
||||||
export default class SingleTagInput extends InputElement<string> {
|
export default class SingleTagInput extends InputElement<string> {
|
||||||
|
|
||||||
|
@ -13,17 +16,29 @@ export default class SingleTagInput extends InputElement<string> {
|
||||||
private key: InputElement<string>;
|
private key: InputElement<string>;
|
||||||
private value: InputElement<string>;
|
private value: InputElement<string>;
|
||||||
private operator: DropDown<string>
|
private operator: DropDown<string>
|
||||||
|
private readonly helpMesage: UIElement;
|
||||||
|
|
||||||
constructor(value: UIEventSource<string> = undefined) {
|
constructor(value: UIEventSource<string> = undefined) {
|
||||||
super(undefined);
|
super(undefined);
|
||||||
this._value = value ?? new UIEventSource<string>("");
|
this._value = value ?? new UIEventSource<string>("");
|
||||||
|
|
||||||
|
this.helpMesage = new VariableUiElement(this._value.map(tagDef => {
|
||||||
|
try {
|
||||||
|
FromJSON.Tag(tagDef, "");
|
||||||
|
return "";
|
||||||
|
} catch (e) {
|
||||||
|
return `<br/><span class='alert'>${e}</span>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
this.key = TextField.KeyInput();
|
this.key = TextField.KeyInput();
|
||||||
|
|
||||||
this.value = new TextField<string>({
|
this.value = new TextField<string>({
|
||||||
placeholder: "value - if blank, matches if key is NOT present",
|
placeholder: "value - if blank, matches if key is NOT present",
|
||||||
fromString: str => str,
|
fromString: str => str,
|
||||||
toString: str => str,
|
toString: str => str,
|
||||||
|
value: new UIEventSource<string>("")
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.operator = new DropDown<string>("", [
|
this.operator = new DropDown<string>("", [
|
||||||
|
@ -85,9 +100,9 @@ export default class SingleTagInput extends InputElement<string> {
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
return new Combine([
|
return new Combine([
|
||||||
this.key, this.operator, this.value
|
this.key, this.operator, this.value,
|
||||||
]).SetStyle("display:flex")
|
this.helpMesage
|
||||||
.Render();
|
]).Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
{
|
{
|
||||||
"if": {
|
"if": {
|
||||||
"and": [
|
"and": [
|
||||||
"operator~Natuurpunt"
|
"operator=Natuurpunt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
|
|
|
@ -252,7 +252,7 @@
|
||||||
"en": "What is the reference number of this public bookcase?",
|
"en": "What is the reference number of this public bookcase?",
|
||||||
"nl": "Wat is het referentienummer van dit boekenruilkastje?"
|
"nl": "Wat is het referentienummer van dit boekenruilkastje?"
|
||||||
},
|
},
|
||||||
"condition": "brand=*",
|
"condition": "brand~*",
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "ref"
|
"key": "ref"
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
"maintainer": "MapComlete",
|
"maintainer": "MapComlete",
|
||||||
"widenfactor": 0.05,
|
"widenfactor": 0.05,
|
||||||
"roamingRenderings": [
|
"roamingRenderings": [
|
||||||
"pictures",
|
|
||||||
{
|
{
|
||||||
"question": "Is deze straat een fietsstraat?",
|
"question": "Is deze straat een fietsstraat?",
|
||||||
"mappings": [
|
"mappings": [
|
||||||
|
|
Loading…
Reference in a new issue