Refacotring: move themeConfig into models

This commit is contained in:
Pieter Vander Vennet 2021-08-07 23:11:34 +02:00
parent 0a01561d37
commit 647100bee5
79 changed files with 603 additions and 629 deletions

View file

@ -1,6 +1,6 @@
import LayerConfig from "./JSON/LayerConfig";
import * as known_layers from "../assets/generated/known_layers_and_themes.json"
import {Utils} from "../Utils";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export default class AllKnownLayers {

View file

@ -1,6 +1,6 @@
import LayoutConfig from "./JSON/LayoutConfig";
import AllKnownLayers from "./AllKnownLayers";
import * as known_themes from "../assets/generated/known_layers_and_themes.json"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
export class AllKnownLayouts {

View file

@ -1,208 +0,0 @@
import {Translation} from "../../UI/i18n/Translation";
import UnitConfigJson from "./UnitConfigJson";
import Translations from "../../UI/i18n/Translations";
import BaseUIElement from "../../UI/BaseUIElement";
import Combine from "../../UI/Base/Combine";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
export class Unit {
public readonly appliesToKeys: Set<string>;
public readonly denominations: Denomination[];
public readonly denominationsSorted: Denomination[];
public readonly defaultDenom: Denomination;
public readonly eraseInvalid: boolean;
private readonly possiblePostFixes: string[] = []
constructor(appliesToKeys: string[], applicableUnits: Denomination[], eraseInvalid: boolean) {
this.appliesToKeys = new Set(appliesToKeys);
this.denominations = applicableUnits;
this.defaultDenom = applicableUnits.filter(denom => denom.default)[0]
this.eraseInvalid = eraseInvalid
const seenUnitExtensions = new Set<string>();
for (const denomination of this.denominations) {
if(seenUnitExtensions.has(denomination.canonical)){
throw "This canonical unit is already defined in another denomination: "+denomination.canonical
}
const duplicate = denomination.alternativeDenominations.filter(denom => seenUnitExtensions.has(denom))
if(duplicate.length > 0){
throw "A denomination is used multiple times: "+duplicate.join(", ")
}
seenUnitExtensions.add(denomination.canonical)
denomination.alternativeDenominations.forEach(d => seenUnitExtensions.add(d))
}
this.denominationsSorted = [...this.denominations]
this.denominationsSorted.sort((a, b) => b.canonical.length - a.canonical.length)
const possiblePostFixes = new Set<string>()
function addPostfixesOf(str){
str = str.toLowerCase()
for (let i = 0; i < str.length + 1; i++) {
const substr = str.substring(0,i)
possiblePostFixes.add(substr)
}
}
for (const denomination of this.denominations) {
addPostfixesOf(denomination.canonical)
denomination.alternativeDenominations.forEach(addPostfixesOf)
}
this.possiblePostFixes = Array.from(possiblePostFixes)
this.possiblePostFixes.sort((a, b) => b.length - a .length)
}
isApplicableToKey(key: string | undefined): boolean {
if (key === undefined) {
return false;
}
return this.appliesToKeys.has(key);
}
/**
* Finds which denomination is applicable and gives the stripped value back
*/
findDenomination(valueWithDenom: string): [string, Denomination] {
if(valueWithDenom === undefined){
return undefined;
}
for (const denomination of this.denominationsSorted) {
const bare = denomination.StrippedValue(valueWithDenom)
if (bare !== null) {
return [bare, denomination]
}
}
return [undefined, undefined]
}
asHumanLongValue(value: string): BaseUIElement {
if (value === undefined) {
return undefined;
}
const [stripped, denom] = this.findDenomination(value)
const human = denom?.human
if(human === undefined){
return new FixedUiElement(stripped ?? value);
}
const elems = denom.prefix ? [human, stripped] : [stripped, human];
return new Combine(elems)
}
/**
* Returns the value without any (sub)parts of any denomination - usefull as preprocessing step for validating inputs.
* E.g.
* if 'megawatt' is a possible denomination, then '5 Meg' will be rewritten to '5' (which can then be validated as a valid pnat)
*
* Returns the original string if nothign matches
*/
stripUnitParts(str: string) {
if(str === undefined){
return undefined;
}
for (const denominationPart of this.possiblePostFixes) {
if(str.endsWith(denominationPart)){
return str.substring(0, str.length - denominationPart.length).trim()
}
}
return str;
}
}
export class Denomination {
public readonly canonical: string;
readonly default: boolean;
readonly prefix: boolean;
private readonly _human: Translation;
public readonly alternativeDenominations: string [];
constructor(json: UnitConfigJson, context: string) {
context = `${context}.unit(${json.canonicalDenomination})`
this.canonical = json.canonicalDenomination.trim()
if (this.canonical === undefined) {
throw `${context}: this unit has no decent canonical value defined`
}
json.alternativeDenomination.forEach((v, i) => {
if (((v?.trim() ?? "") === "")) {
throw `${context}.alternativeDenomination.${i}: invalid alternative denomination: undefined, null or only whitespace`
}
})
this.alternativeDenominations = json.alternativeDenomination?.map(v => v.trim()) ?? []
this.default = json.default ?? false;
this._human = Translations.T(json.human, context + "human")
this.prefix = json.prefix ?? false;
}
get human(): Translation {
return this._human.Clone()
}
public canonicalValue(value: string, actAsDefault?: boolean) {
if (value === undefined) {
return undefined;
}
const stripped = this.StrippedValue(value, actAsDefault)
if (stripped === null) {
return null;
}
return (stripped + " " + this.canonical.trim()).trim();
}
/**
* Returns the core value (without unit) if:
* - the value ends with the canonical or an alternative value (or begins with if prefix is set)
* - the value is a Number (without unit) and default is set
*
* Returns null if it doesn't match this unit
*/
public StrippedValue(value: string, actAsDefault?: boolean): string {
if (value === undefined) {
return undefined;
}
value = value.toLowerCase()
if (this.prefix) {
if (value.startsWith(this.canonical) && this.canonical !== "") {
return value.substring(this.canonical.length).trim();
}
for (const alternativeValue of this.alternativeDenominations) {
if (value.startsWith(alternativeValue)) {
return value.substring(alternativeValue.length).trim();
}
}
} else {
if (value.endsWith(this.canonical.toLowerCase()) && this.canonical !== "") {
return value.substring(0, value.length - this.canonical.length).trim();
}
for (const alternativeValue of this.alternativeDenominations) {
if (value.endsWith(alternativeValue.toLowerCase())) {
return value.substring(0, value.length - alternativeValue.length).trim();
}
}
}
if (this.default || actAsDefault) {
const parsed = Number(value.trim())
if (!isNaN(parsed)) {
return value.trim();
}
}
return null;
}
}

View file

@ -1,27 +0,0 @@
import { TagsFilter } from "../../Logic/Tags/TagsFilter";
import { Translation } from "../../UI/i18n/Translation";
import Translations from "../../UI/i18n/Translations";
import FilterConfigJson from "./FilterConfigJson";
import { FromJSON } from "./FromJSON";
export default class FilterConfig {
readonly options: {
question: Translation;
osmTags: TagsFilter;
}[];
constructor(json: FilterConfigJson, context: string) {
this.options = json.options.map((option, i) => {
const question = Translations.T(
option.question,
context + ".options-[" + i + "].question"
);
const osmTags = FromJSON.Tag(
option.osmTags ?? {and:[]},
`${context}.options-[${i}].osmTags`
);
return { question: question, osmTags: osmTags };
});
}
}

View file

@ -1,11 +0,0 @@
import { AndOrTagConfigJson } from "./TagConfigJson";
export default interface FilterConfigJson {
/**
* The options for a filter
* If there are multiple options these will be a list of radio buttons
* If there is only one option this will be a checkbox
* Filtering is done based on the given osmTags that are compared to the objects in that layer.
*/
options: { question: string | any; osmTags?: AndOrTagConfigJson | string }[];
}

View file

@ -1,144 +0,0 @@
import {AndOrTagConfigJson} from "./TagConfigJson";
import {Utils} from "../../Utils";
import {RegexTag} from "../../Logic/Tags/RegexTag";
import {Or} from "../../Logic/Tags/Or";
import {And} from "../../Logic/Tags/And";
import {Tag} from "../../Logic/Tags/Tag";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
import ComparingTag from "../../Logic/Tags/ComparingTag";
export class FromJSON {
public static SimpleTag(json: string, context?: string): Tag {
const tag = Utils.SplitFirst(json, "=");
if (tag.length !== 2) {
throw `Invalid tag: no (or too much) '=' found (in ${context ?? "unkown context"})`
}
return new Tag(tag[0], tag[1]);
}
public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
try {
return this.TagUnsafe(json, context);
} catch (e) {
console.error("Could not parse tag", json, "in context", context, "due to ", e)
throw e;
}
}
private static comparators
: [string, (a: number, b: number) => boolean][]
= [
["<=", (a, b) => a <= b],
[">=", (a, b) => a >= b],
["<", (a, b) => a < b],
[">", (a, b) => a > b],
]
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
if (json === undefined) {
throw `Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression`
}
if (typeof (json) == "string") {
const tag = json as string;
for (const [operator, comparator] of FromJSON.comparators) {
if (tag.indexOf(operator) >= 0) {
const split = Utils.SplitFirst(tag, operator);
const val = Number(split[1].trim())
if (isNaN(val)) {
throw `Error: not a valid value for a comparison: ${split[1]}, make sure it is a number and nothing more (at ${context})`
}
const f = (value: string | undefined) => {
const b = Number(value?.replace(/[^\d.]/g,''))
if (isNaN(b)) {
return false;
}
return comparator(b, val)
}
return new ComparingTag(split[0], f, operator + val)
}
}
if (tag.indexOf("!~") >= 0) {
const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") {
throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})`
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
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) {
const split = Utils.SplitFirst(tag, ":=");
return new SubstitutingTag(split[0], split[1]);
}
if (tag.indexOf("!=") >= 0) {
const split = Utils.SplitFirst(tag, "!=");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
true
);
}
if (tag.indexOf("!~") >= 0) {
const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
true
);
}
if (tag.indexOf("~") >= 0) {
const split = Utils.SplitFirst(tag, "~");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$")
);
}
if (tag.indexOf("=") >= 0) {
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])
}
throw `Error while parsing tag '${tag}' in ${context}: no key part and value part were found`
}
if (json.and !== undefined) {
return new And(json.and.map(t => FromJSON.Tag(t, context)));
}
if (json.or !== undefined) {
return new Or(json.or.map(t => FromJSON.Tag(t, context)));
}
}
}

View file

@ -1,7 +1,7 @@
import TagRenderingConfig from "./JSON/TagRenderingConfig";
import * as questions from "../assets/tagRenderings/questions.json";
import * as icons from "../assets/tagRenderings/icons.json";
import {Utils} from "../Utils";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
export default class SharedTagRenderings {

View file

@ -15,7 +15,6 @@ import {Utils} from "./Utils";
import Svg from "./Svg";
import Link from "./UI/Base/Link";
import * as personal from "./assets/themes/personal/personal.json";
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import * as L from "leaflet";
import Img from "./UI/Base/Img";
import UserDetails from "./Logic/Osm/OsmConnection";
@ -30,14 +29,15 @@ import Translations from "./UI/i18n/Translations";
import MapControlButton from "./UI/MapControlButton";
import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler";
import LZString from "lz-string";
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
import FeatureSource from "./Logic/FeatureSource/FeatureSource";
import AllKnownLayers from "./Customizations/AllKnownLayers";
import LayerConfig from "./Customizations/JSON/LayerConfig";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import {TagsFilter} from "./Logic/Tags/TagsFilter";
import LeftControls from "./UI/BigComponents/LeftControls";
import RightControls from "./UI/BigComponents/RightControls";
import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
import LayerConfig from "./Models/ThemeConfig/LayerConfig";
export class InitUiElements {
static InitAll(

View file

@ -3,9 +3,9 @@ import {UIEventSource} from "../UIEventSource";
import Svg from "../../Svg";
import Img from "../../UI/Base/Img";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
import BaseUIElement from "../../UI/BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class GeoLocationHandler extends VariableUiElement {
/**

View file

@ -1,8 +1,8 @@
import {UIEventSource} from "../UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {OsmConnection} from "../Osm/OsmConnection";
import {Utils} from "../../Utils";
import LZString from "lz-string";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class InstalledThemes {
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;

View file

@ -1,13 +1,13 @@
import {UIEventSource} from "../UIEventSource";
import Loc from "../../Models/Loc";
import {Or} from "../Tags/Or";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {Overpass} from "../Osm/Overpass";
import Bounds from "../../Models/Bounds";
import FeatureSource from "../FeatureSource/FeatureSource";
import {Utils} from "../../Utils";
import {TagsFilter} from "../Tags/TagsFilter";
import SimpleMetaTagger from "../SimpleMetaTagger";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class OverpassFeatureSource implements FeatureSource {

View file

@ -1,10 +1,10 @@
import {UIEventSource} from "../UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Translations from "../../UI/i18n/Translations";
import Locale from "../../UI/i18n/Locale";
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
import {ElementStorage} from "../ElementStorage";
import Combine from "../../UI/Base/Combine";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
class TitleElement extends UIEventSource<string> {

View file

@ -7,7 +7,6 @@ import FeatureSource from "../FeatureSource/FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LocalStorageSaver from "./LocalStorageSaver";
import LocalStorageSource from "./LocalStorageSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Loc from "../../Models/Loc";
import GeoJsonSource from "./GeoJsonSource";
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
@ -15,6 +14,7 @@ import RegisteringFeatureSource from "./RegisteringFeatureSource";
import FilteredLayer from "../../Models/FilteredLayer";
import {Changes} from "../Osm/Changes";
import ChangeApplicator from "./ChangeApplicator";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class FeaturePipeline implements FeatureSource {

View file

@ -1,9 +1,9 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import Loc from "../../Models/Loc";
import Hash from "../Web/Hash";
import {TagsFilter} from "../Tags/TagsFilter";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
export default class FilteringFeatureSource implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> =

View file

@ -3,7 +3,7 @@ import {UIEventSource} from "../UIEventSource";
import Loc from "../../Models/Loc";
import State from "../../State";
import {Utils} from "../../Utils";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
/**

View file

@ -5,7 +5,7 @@
*/
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class LocalStorageSaver implements FeatureSource {
public static readonly storageKey: string = "cached-features";

View file

@ -1,7 +1,7 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LocalStorageSaver from "./LocalStorageSaver";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class LocalStorageSource implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]>;

View file

@ -1,7 +1,7 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import {GeoOperations} from "../GeoOperations";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
/**
* This is the part of the pipeline which introduces extra points at the center of an area (but only if this is demanded by the wayhandling)

View file

@ -1,8 +1,8 @@
import LayerConfig from "../Customizations/JSON/LayerConfig";
import SimpleMetaTagger from "./SimpleMetaTagger";
import {ExtraFunction} from "./ExtraFunction";
import {Relation} from "./Osm/ExtractRelations";
import {UIEventSource} from "./UIEventSource";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
interface Params {

View file

@ -5,9 +5,9 @@ import {UIEventSource} from "../UIEventSource";
import {ElementStorage} from "../ElementStorage";
import State from "../../State";
import Locale from "../../UI/i18n/Locale";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Constants from "../../Models/Constants";
import {OsmObject} from "./OsmObject";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export class ChangesetHandler {

View file

@ -5,10 +5,10 @@ import {OsmPreferences} from "./OsmPreferences";
import {ChangesetHandler} from "./ChangesetHandler";
import {ElementStorage} from "../ElementStorage";
import Svg from "../../Svg";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Img from "../../UI/Base/Img";
import {Utils} from "../../Utils";
import {OsmObject} from "./OsmObject";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class UserDetails {

View file

@ -2,6 +2,11 @@ import {Tag} from "./Tag";
import {TagsFilter} from "./TagsFilter";
import {And} from "./And";
import {Utils} from "../../Utils";
import ComparingTag from "./ComparingTag";
import {RegexTag} from "./RegexTag";
import SubstitutingTag from "./SubstitutingTag";
import {Or} from "./Or";
import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
export class TagUtils {
static ApplyTemplate(template: string, tags: any): string {
@ -118,4 +123,136 @@ export class TagUtils {
}
return true;
}
public static SimpleTag(json: string, context?: string): Tag {
const tag = Utils.SplitFirst(json, "=");
if (tag.length !== 2) {
throw `Invalid tag: no (or too much) '=' found (in ${context ?? "unkown context"})`
}
return new Tag(tag[0], tag[1]);
}
public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
try {
return this.TagUnsafe(json, context);
} catch (e) {
console.error("Could not parse tag", json, "in context", context, "due to ", e)
throw e;
}
}
private static comparators
: [string, (a: number, b: number) => boolean][]
= [
["<=", (a, b) => a <= b],
[">=", (a, b) => a >= b],
["<", (a, b) => a < b],
[">", (a, b) => a > b],
]
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
if (json === undefined) {
throw `Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression`
}
if (typeof (json) == "string") {
const tag = json as string;
for (const [operator, comparator] of TagUtils.comparators) {
if (tag.indexOf(operator) >= 0) {
const split = Utils.SplitFirst(tag, operator);
const val = Number(split[1].trim())
if (isNaN(val)) {
throw `Error: not a valid value for a comparison: ${split[1]}, make sure it is a number and nothing more (at ${context})`
}
const f = (value: string | undefined) => {
const b = Number(value?.replace(/[^\d.]/g, ''))
if (isNaN(b)) {
return false;
}
return comparator(b, val)
}
return new ComparingTag(split[0], f, operator + val)
}
}
if (tag.indexOf("!~") >= 0) {
const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") {
throw `Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${tag} while parsing ${context})`
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
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) {
const split = Utils.SplitFirst(tag, ":=");
return new SubstitutingTag(split[0], split[1]);
}
if (tag.indexOf("!=") >= 0) {
const split = Utils.SplitFirst(tag, "!=");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
true
);
}
if (tag.indexOf("!~") >= 0) {
const split = Utils.SplitFirst(tag, "!~");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$"),
true
);
}
if (tag.indexOf("~") >= 0) {
const split = Utils.SplitFirst(tag, "~");
if (split[1] === "*") {
split[1] = "..*"
}
return new RegexTag(
split[0],
new RegExp("^" + split[1] + "$")
);
}
if (tag.indexOf("=") >= 0) {
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])
}
throw `Error while parsing tag '${tag}' in ${context}: no key part and value part were found`
}
if (json.and !== undefined) {
return new And(json.and.map(t => TagUtils.Tag(t, context)));
}
if (json.or !== undefined) {
return new Or(json.or.map(t => TagUtils.Tag(t, context)));
}
}
}

View file

@ -2,7 +2,7 @@ import { Utils } from "../Utils";
export default class Constants {
public static vNumber = "0.9.0-rc2";
public static vNumber = "0.9.0-rc3";
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {

96
Models/Denomination.ts Normal file
View file

@ -0,0 +1,96 @@
import {Translation} from "../UI/i18n/Translation";
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson";
import Translations from "../UI/i18n/Translations";
export class Denomination {
public readonly canonical: string;
readonly default: boolean;
readonly prefix: boolean;
private readonly _human: Translation;
public readonly alternativeDenominations: string [];
constructor(json: UnitConfigJson, context: string) {
context = `${context}.unit(${json.canonicalDenomination})`
this.canonical = json.canonicalDenomination.trim()
if (this.canonical === undefined) {
throw `${context}: this unit has no decent canonical value defined`
}
json.alternativeDenomination.forEach((v, i) => {
if (((v?.trim() ?? "") === "")) {
throw `${context}.alternativeDenomination.${i}: invalid alternative denomination: undefined, null or only whitespace`
}
})
this.alternativeDenominations = json.alternativeDenomination?.map(v => v.trim()) ?? []
this.default = json.default ?? false;
this._human = Translations.T(json.human, context + "human")
this.prefix = json.prefix ?? false;
}
get human(): Translation {
return this._human.Clone()
}
public canonicalValue(value: string, actAsDefault?: boolean) {
if (value === undefined) {
return undefined;
}
const stripped = this.StrippedValue(value, actAsDefault)
if (stripped === null) {
return null;
}
return (stripped + " " + this.canonical.trim()).trim();
}
/**
* Returns the core value (without unit) if:
* - the value ends with the canonical or an alternative value (or begins with if prefix is set)
* - the value is a Number (without unit) and default is set
*
* Returns null if it doesn't match this unit
*/
public StrippedValue(value: string, actAsDefault?: boolean): string {
if (value === undefined) {
return undefined;
}
value = value.toLowerCase()
if (this.prefix) {
if (value.startsWith(this.canonical) && this.canonical !== "") {
return value.substring(this.canonical.length).trim();
}
for (const alternativeValue of this.alternativeDenominations) {
if (value.startsWith(alternativeValue)) {
return value.substring(alternativeValue.length).trim();
}
}
} else {
if (value.endsWith(this.canonical.toLowerCase()) && this.canonical !== "") {
return value.substring(0, value.length - this.canonical.length).trim();
}
for (const alternativeValue of this.alternativeDenominations) {
if (value.endsWith(alternativeValue.toLowerCase())) {
return value.substring(0, value.length - alternativeValue.length).trim();
}
}
}
if (this.default || actAsDefault) {
const parsed = Number(value.trim())
if (!isNaN(parsed)) {
return value.trim();
}
}
return null;
}
}

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "../Logic/UIEventSource";
import {TagsFilter} from "../Logic/Tags/TagsFilter";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import LayerConfig from "./ThemeConfig/LayerConfig";
export default interface FilteredLayer {
readonly isDisplayed: UIEventSource<boolean>;

View file

@ -1,19 +1,19 @@
import {DeleteConfigJson} from "./DeleteConfigJson";
import {Translation} from "../../UI/i18n/Translation";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {DeleteConfigJson} from "./Json/DeleteConfigJson";
import Translations from "../../UI/i18n/Translations";
import {FromJSON} from "./FromJSON";
import {TagUtils} from "../../Logic/Tags/TagUtils";
export default class DeleteConfig {
public readonly extraDeleteReasons?: {
public readonly extraDeleteReasons?: {
explanation: Translation,
changesetMessage: string
}[]
public readonly nonDeleteMappings?: { if: TagsFilter, then: Translation }[]
public readonly nonDeleteMappings?: { if: TagsFilter, then: Translation }[]
public readonly softDeletionTags?: TagsFilter
public readonly neededChangesets?: number
public readonly softDeletionTags?: TagsFilter
public readonly neededChangesets?: number
constructor(json: DeleteConfigJson, context: string) {
@ -30,18 +30,18 @@ export default class DeleteConfig {
this.nonDeleteMappings = json.nonDeleteMappings?.map((nonDelete, i) => {
const ctx = `${context}.extraDeleteReasons[${i}]`
return {
if: FromJSON.Tag(nonDelete.if, ctx + ".if"),
if: TagUtils.Tag(nonDelete.if, ctx + ".if"),
then: Translations.T(nonDelete.then, ctx + ".then")
}
})
this.softDeletionTags = undefined;
if(json.softDeletionTags !== undefined){
this.softDeletionTags = FromJSON.Tag(json.softDeletionTags,`${context}.softDeletionTags`)
if (json.softDeletionTags !== undefined) {
this.softDeletionTags = TagUtils.Tag(json.softDeletionTags, `${context}.softDeletionTags`)
}
if(json["hardDeletionTags"] !== undefined){
if (json["hardDeletionTags"] !== undefined) {
throw `You probably meant 'softDeletionTags' instead of 'hardDeletionTags' (at ${context})`
}
this.neededChangesets = json.neededChangesets

View file

@ -0,0 +1,27 @@
import {Translation} from "../../UI/i18n/Translation";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import FilterConfigJson from "./Json/FilterConfigJson";
import Translations from "../../UI/i18n/Translations";
import {TagUtils} from "../../Logic/Tags/TagUtils";
export default class FilterConfig {
readonly options: {
question: Translation;
osmTags: TagsFilter;
}[];
constructor(json: FilterConfigJson, context: string) {
this.options = json.options.map((option, i) => {
const question = Translations.T(
option.question,
context + ".options-[" + i + "].question"
);
const osmTags = TagUtils.Tag(
option.osmTags ?? {and: []},
`${context}.options-[${i}].osmTags`
);
return {question: question, osmTags: osmTags};
});
}
}

View file

@ -0,0 +1,11 @@
import {AndOrTagConfigJson} from "./TagConfigJson";
export default interface FilterConfigJson {
/**
* The options for a filter
* If there are multiple options these will be a list of radio buttons
* If there is only one option this will be a checkbox
* Filtering is done based on the given osmTags that are compared to the objects in that layer.
*/
options: { question: string | any; osmTags?: AndOrTagConfigJson | string }[];
}

View file

@ -1,7 +1,7 @@
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import {AndOrTagConfigJson} from "./TagConfigJson";
import {DeleteConfigJson} from "./DeleteConfigJson";
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import FilterConfigJson from "./FilterConfigJson";
import {DeleteConfigJson} from "./DeleteConfigJson";
/**
* Configuration for a single layer

View file

@ -1,6 +1,6 @@
import {LayerConfigJson} from "./LayerConfigJson";
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import UnitConfigJson from "./UnitConfigJson";
import {LayerConfigJson} from "./LayerConfigJson";
/**
* Defines the entire theme.

View file

@ -1,4 +1,3 @@
export interface AndOrTagConfigJson {
and?: (string | AndOrTagConfigJson)[]
or?: (string | AndOrTagConfigJson)[]

View file

@ -1,4 +1,4 @@
export default interface UnitConfigJson{
export default interface UnitConfigJson {
/**
* The canonical value which will be added to the text.
@ -19,7 +19,7 @@ export default interface UnitConfigJson{
* "fr": "metre"
* }
*/
human?:string | any
human?: string | any
/**
* If set, then the canonical value will be prefixed instead, e.g. for '€'

View file

@ -1,24 +1,23 @@
import Translations from "../../UI/i18n/Translations";
import TagRenderingConfig from "./TagRenderingConfig";
import {LayerConfigJson} from "./LayerConfigJson";
import {FromJSON} from "./FromJSON";
import SharedTagRenderings from "../SharedTagRenderings";
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import {Translation} from "../../UI/i18n/Translation";
import Svg from "../../Svg";
import SourceConfig from "./SourceConfig";
import TagRenderingConfig from "./TagRenderingConfig";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import PresetConfig from "./PresetConfig";
import {LayerConfigJson} from "./Json/LayerConfigJson";
import Translations from "../../UI/i18n/Translations";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
import {Utils} from "../../Utils";
import Svg from "../../Svg";
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../../UI/BaseUIElement";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import Combine from "../../UI/Base/Combine";
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import SourceConfig from "./SourceConfig";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import BaseUIElement from "../../UI/BaseUIElement";
import {Unit} from "./Denomination";
import DeleteConfig from "./DeleteConfig";
import FilterConfig from "./FilterConfig";
import PresetConfig from "./PresetConfig";
import {Unit} from "../Unit";
import DeleteConfig from "./DeleteConfig";
export default class LayerConfig {
static WAYHANDLING_DEFAULT = 0;
@ -83,7 +82,7 @@ export default class LayerConfig {
let legacy = undefined;
if (json["overpassTags"] !== undefined) {
// @ts-ignore
legacy = FromJSON.Tag(json["overpassTags"], context + ".overpasstags");
legacy = TagUtils.Tag(json["overpassTags"], context + ".overpasstags");
}
if (json.source !== undefined) {
if (legacy !== undefined) {
@ -95,7 +94,7 @@ export default class LayerConfig {
let osmTags: TagsFilter = legacy;
if (json.source["osmTags"]) {
osmTags = FromJSON.Tag(
osmTags = TagUtils.Tag(
json.source["osmTags"],
context + "source.osmTags"
);
@ -146,7 +145,7 @@ export default class LayerConfig {
this.presets = (json.presets ?? []).map((pr, i) => {
let preciseInput = undefined;
if(pr.preciseInput !== undefined){
if (pr.preciseInput !== undefined) {
if (pr.preciseInput === true) {
pr.preciseInput = {
preferredBackground: undefined
@ -159,7 +158,7 @@ export default class LayerConfig {
snapToLayers = pr.preciseInput.snapToLayer
}
let preferredBackground : string[]
let preferredBackground: string[]
if (typeof pr.preciseInput.preferredBackground === "string") {
preferredBackground = [pr.preciseInput.preferredBackground]
} else {
@ -172,9 +171,9 @@ export default class LayerConfig {
}
}
const config : PresetConfig= {
const config: PresetConfig = {
title: Translations.T(pr.title, `${context}.presets[${i}].title`),
tags: pr.tags.map((t) => FromJSON.SimpleTag(t)),
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
description: Translations.T(pr.description, `${context}.presets[${i}].description`),
preciseInput: preciseInput,
}
@ -301,7 +300,7 @@ export default class LayerConfig {
tr = SharedTagRenderings.SharedIcons.get(overlay.then);
}
return {
if: FromJSON.Tag(overlay.if),
if: TagUtils.Tag(overlay.if),
then: tr,
badge: overlay.badge ?? false,
};
@ -426,7 +425,7 @@ export default class LayerConfig {
}
function render(tr: TagRenderingConfig, deflt?: string) {
if(tags === undefined){
if (tags === undefined) {
return deflt
}
const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt;

View file

@ -1,11 +1,12 @@
import {Translation} from "../../UI/i18n/Translation";
import TagRenderingConfig from "./TagRenderingConfig";
import LayerConfig from "./LayerConfig";
import {LayoutConfigJson} from "./LayoutConfigJson";
import AllKnownLayers from "../AllKnownLayers";
import SharedTagRenderings from "../SharedTagRenderings";
import {LayoutConfigJson} from "./Json/LayoutConfigJson";
import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
import AllKnownLayers from "../../Customizations/AllKnownLayers";
import {Utils} from "../../Utils";
import {Denomination, Unit} from "./Denomination";
import LayerConfig from "./LayerConfig";
import {Unit} from "../Unit";
import {Denomination} from "../Denomination";
export default class LayoutConfig {
public readonly id: string;

View file

@ -29,7 +29,7 @@ export default class SourceConfig {
if (defined == 0) {
throw `Source: nothing correct defined in the source (in ${context}) (the params are ${JSON.stringify(params)})`
}
if(params.isOsmCache && params.geojsonSource == undefined){
if (params.isOsmCache && params.geojsonSource == undefined) {
console.error(params)
throw `Source said it is a OSM-cached layer, but didn't define the actual source of the cache (in context ${context})`
}

View file

@ -1,15 +1,13 @@
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import Translations from "../../UI/i18n/Translations";
import {FromJSON} from "./FromJSON";
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
import {Translation} from "../../UI/i18n/Translation";
import {Utils} from "../../Utils";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
import Translations from "../../UI/i18n/Translations";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import {And} from "../../Logic/Tags/And";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
import {Utils} from "../../Utils";
import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
/***
* The parsed version of TagRenderingConfigJSON
* Identical data, but with some methods and validation
@ -62,7 +60,7 @@ export default class TagRenderingConfig {
this.render = Translations.T(json.render, context + ".render");
this.question = Translations.T(json.question, context + ".question");
this.roaming = json.roaming ?? false;
const condition = FromJSON.Tag(json.condition ?? {"and": []}, `${context}.condition`);
const condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`);
if (this.roaming && conditionIfRoaming !== undefined) {
this.condition = new And([condition, conditionIfRoaming]);
} else {
@ -75,7 +73,7 @@ export default class TagRenderingConfig {
key: json.freeform.key,
type: json.freeform.type ?? "string",
addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
TagUtils.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
inline: json.freeform.inline ?? false,
default: json.freeform.default,
helperArgs: json.freeform.helperArgs
@ -87,7 +85,7 @@ export default class TagRenderingConfig {
if (this.freeform.key === undefined || this.freeform.key === "") {
throw `Freeform.key is undefined or the empty string - this is not allowed; either fill out something or remove the freeform block alltogether. Error in ${context}`
}
if(json.freeform["args"] !== undefined){
if (json.freeform["args"] !== undefined) {
throw `Freeform.args is defined. This should probably be 'freeform.helperArgs' (at ${context})`
}
@ -134,12 +132,12 @@ export default class TagRenderingConfig {
if (typeof mapping.hideInAnswer === "boolean") {
hideInAnswer = mapping.hideInAnswer;
} else if (mapping.hideInAnswer !== undefined) {
hideInAnswer = FromJSON.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
hideInAnswer = TagUtils.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
}
const mappingContext = `${context}.mapping[${i}]`
const mp = {
if: FromJSON.Tag(mapping.if, `${mappingContext}.if`),
ifnot: (mapping.ifnot !== undefined ? FromJSON.Tag(mapping.ifnot, `${mappingContext}.ifnot`) : undefined),
if: TagUtils.Tag(mapping.if, `${mappingContext}.if`),
ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${mappingContext}.ifnot`) : undefined),
then: Translations.T(mapping.then, `{mappingContext}.then`),
hideInAnswer: hideInAnswer
};
@ -336,25 +334,5 @@ export default class TagRenderingConfig {
return usedIcons;
}
/**
* Returns true if this tag rendering has a minimap in some language.
* Note: this might be hidden by conditions
*/
public hasMinimap(): boolean {
const translations: Translation[] = Utils.NoNull([this.render, ...(this.mappings ?? []).map(m => m.then)]);
for (const translation of translations) {
for (const key in translation.translations) {
if (!translation.translations.hasOwnProperty(key)) {
continue
}
const template = translation.translations[key]
const parts = SubstitutedTranslation.ExtractSpecialComponents(template)
const hasMiniMap = parts.filter(part => part.special !== undefined).some(special => special.special.func.funcName === "minimap")
if (hasMiniMap) {
return true;
}
}
}
return false;
}
}

114
Models/Unit.ts Normal file
View file

@ -0,0 +1,114 @@
import BaseUIElement from "../UI/BaseUIElement";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Combine from "../UI/Base/Combine";
import {Denomination} from "./Denomination";
export class Unit {
public readonly appliesToKeys: Set<string>;
public readonly denominations: Denomination[];
public readonly denominationsSorted: Denomination[];
public readonly defaultDenom: Denomination;
public readonly eraseInvalid: boolean;
private readonly possiblePostFixes: string[] = []
constructor(appliesToKeys: string[], applicableUnits: Denomination[], eraseInvalid: boolean) {
this.appliesToKeys = new Set(appliesToKeys);
this.denominations = applicableUnits;
this.defaultDenom = applicableUnits.filter(denom => denom.default)[0]
this.eraseInvalid = eraseInvalid
const seenUnitExtensions = new Set<string>();
for (const denomination of this.denominations) {
if (seenUnitExtensions.has(denomination.canonical)) {
throw "This canonical unit is already defined in another denomination: " + denomination.canonical
}
const duplicate = denomination.alternativeDenominations.filter(denom => seenUnitExtensions.has(denom))
if (duplicate.length > 0) {
throw "A denomination is used multiple times: " + duplicate.join(", ")
}
seenUnitExtensions.add(denomination.canonical)
denomination.alternativeDenominations.forEach(d => seenUnitExtensions.add(d))
}
this.denominationsSorted = [...this.denominations]
this.denominationsSorted.sort((a, b) => b.canonical.length - a.canonical.length)
const possiblePostFixes = new Set<string>()
function addPostfixesOf(str) {
str = str.toLowerCase()
for (let i = 0; i < str.length + 1; i++) {
const substr = str.substring(0, i)
possiblePostFixes.add(substr)
}
}
for (const denomination of this.denominations) {
addPostfixesOf(denomination.canonical)
denomination.alternativeDenominations.forEach(addPostfixesOf)
}
this.possiblePostFixes = Array.from(possiblePostFixes)
this.possiblePostFixes.sort((a, b) => b.length - a.length)
}
isApplicableToKey(key: string | undefined): boolean {
if (key === undefined) {
return false;
}
return this.appliesToKeys.has(key);
}
/**
* Finds which denomination is applicable and gives the stripped value back
*/
findDenomination(valueWithDenom: string): [string, Denomination] {
if (valueWithDenom === undefined) {
return undefined;
}
for (const denomination of this.denominationsSorted) {
const bare = denomination.StrippedValue(valueWithDenom)
if (bare !== null) {
return [bare, denomination]
}
}
return [undefined, undefined]
}
asHumanLongValue(value: string): BaseUIElement {
if (value === undefined) {
return undefined;
}
const [stripped, denom] = this.findDenomination(value)
const human = denom?.human
if (human === undefined) {
return new FixedUiElement(stripped ?? value);
}
const elems = denom.prefix ? [human, stripped] : [stripped, human];
return new Combine(elems)
}
/**
* Returns the value without any (sub)parts of any denomination - usefull as preprocessing step for validating inputs.
* E.g.
* if 'megawatt' is a possible denomination, then '5 Meg' will be rewritten to '5' (which can then be validated as a valid pnat)
*
* Returns the original string if nothign matches
*/
stripUnitParts(str: string) {
if (str === undefined) {
return undefined;
}
for (const denominationPart of this.possiblePostFixes) {
if (str.endsWith(denominationPart)) {
return str.substring(0, str.length - denominationPart.length).trim()
}
}
return str;
}
}

View file

@ -6,7 +6,6 @@ import Locale from "./UI/i18n/Locale";
import {UIEventSource} from "./Logic/UIEventSource";
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
import {QueryParameters} from "./Logic/Web/QueryParameters";
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import {MangroveIdentity} from "./Logic/Web/MangroveReviews";
import InstalledThemes from "./Logic/Actors/InstalledThemes";
import BaseLayer from "./Models/BaseLayer";
@ -21,6 +20,7 @@ import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
import FilteredLayer from "./Models/FilteredLayer";
import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
/**
* Contains the global state: a bunch of UI-event sources

View file

@ -4,10 +4,10 @@ import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import UserDetails from "../../Logic/Osm/OsmConnection";
import Constants from "../../Models/Constants";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Loc from "../../Models/Loc";
import * as L from "leaflet"
import {VariableUiElement} from "../Base/VariableUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
/**
* The bottom right attribution panel in the leaflet map

View file

@ -3,7 +3,6 @@ import Translations from "../i18n/Translations";
import Attribution from "./Attribution";
import State from "../../State";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {FixedUiElement} from "../Base/FixedUiElement";
import * as licenses from "../../assets/generated/license_info.json"
import SmallLicense from "../../Models/smallLicense";
@ -12,6 +11,7 @@ import Link from "../Base/Link";
import {VariableUiElement} from "../Base/VariableUIElement";
import * as contributors from "../../assets/contributors.json"
import BaseUIElement from "../BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
/**
* The attribution panel shown on mobile

View file

@ -5,10 +5,8 @@ import {VariableUiElement} from "../Base/VariableUIElement";
import Toggle from "../Input/Toggle";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import {Translation} from "../i18n/Translation";
import Svg from "../../Svg";
import FilterConfig from "../../Customizations/JSON/FilterConfig";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {And} from "../../Logic/Tags/And";
import {UIEventSource} from "../../Logic/UIEventSource";
@ -16,6 +14,8 @@ import BaseUIElement from "../BaseUIElement";
import State from "../../State";
import FilteredLayer from "../../Models/FilteredLayer";
import BackgroundSelector from "./BackgroundSelector";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
/**

View file

@ -10,11 +10,11 @@ import Constants from "../../Models/Constants";
import Combine from "../Base/Combine";
import {TabbedComponent} from "../Base/TabbedComponent";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import UserDetails from "../../Logic/Osm/OsmConnection";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import BaseUIElement from "../BaseUIElement";
import Toggle from "../Input/Toggle";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {

View file

@ -1,5 +1,4 @@
import {VariableUiElement} from "../Base/VariableUIElement";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts";
import Svg from "../../Svg";
import State from "../../State";
@ -11,6 +10,7 @@ import Constants from "../../Models/Constants";
import LanguagePicker from "../LanguagePicker";
import IndexText from "./IndexText";
import BaseUIElement from "../BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class MoreScreen extends Combine {

View file

@ -7,9 +7,9 @@ import {SubtleButton} from "../Base/SubtleButton";
import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import Img from "../Base/Img";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
export default class PersonalLayersPanel extends VariableUiElement {

View file

@ -1,6 +1,5 @@
import {VariableUiElement} from "../Base/VariableUIElement";
import {Translation} from "../i18n/Translation";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Svg from "../../Svg";
import Combine from "../Base/Combine";
import {SubtleButton} from "../Base/SubtleButton";
@ -11,8 +10,9 @@ import Toggle from "../Input/Toggle";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import Constants from "../../Models/Constants";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import BaseUIElement from "../BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
export default class ShareScreen extends Combine {

View file

@ -8,7 +8,6 @@ import State from "../../State";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import Constants from "../../Models/Constants";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
@ -17,8 +16,9 @@ import UserDetails from "../../Logic/Osm/OsmConnection";
import LocationInput from "../Input/LocationInput";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
import PresetConfig from "../../Customizations/JSON/PresetConfig";
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
import PresetConfig from "../../Models/ThemeConfig/PresetConfig";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
/*
* The SimpleAddUI is a single panel, which can have multiple states:

View file

@ -17,11 +17,11 @@ import Loc from "../Models/Loc";
import {BBox} from "../Logic/GeoOperations";
import ShowDataLayer from "./ShowDataLayer";
import BaseLayer from "../Models/BaseLayer";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {FixedUiElement} from "./Base/FixedUiElement";
import Translations from "./i18n/Translations";
import State from "../State";
import Constants from "../Models/Constants";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
export default class ExportPDF {
// dimensions of the map in milimeter

View file

@ -10,8 +10,8 @@ import Toggle from "../Input/Toggle";
import FileSelectorButton from "../Input/FileSelectorButton";
import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader";
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
export class ImageUploadFlow extends Toggle {

View file

@ -8,8 +8,8 @@ import Svg from "../../Svg";
import State from "../../State";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import {GeoOperations} from "../../Logic/GeoOperations";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import ShowDataLayer from "../ShowDataLayer";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class LocationInput extends InputElement<Loc> {

View file

@ -11,10 +11,10 @@ import DirectionInput from "./DirectionInput";
import ColorPicker from "./ColorPicker";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
import {Unit} from "../../Customizations/JSON/Denomination";
import BaseUIElement from "../BaseUIElement";
import LengthInput from "./LengthInput";
import {GeoOperations} from "../../Logic/GeoOperations";
import {Unit} from "../../Models/Unit";
interface TextFieldDef {
name: string,

View file

@ -8,18 +8,18 @@ import {Tag} from "../../Logic/Tags/Tag";
import {UIEventSource} from "../../Logic/UIEventSource";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import TagRenderingQuestion from "./TagRenderingQuestion";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import Combine from "../Base/Combine";
import {SubtleButton} from "../Base/SubtleButton";
import {FixedUiElement} from "../Base/FixedUiElement";
import {Translation} from "../i18n/Translation";
import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson";
import BaseUIElement from "../BaseUIElement";
import {Changes} from "../../Logic/Osm/Changes";
import {And} from "../../Logic/Tags/And";
import Constants from "../../Models/Constants";
import DeleteConfig from "../../Customizations/JSON/DeleteConfig";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
import DeleteConfig from "../../Models/ThemeConfig/DeleteConfig";
export default class DeleteWizard extends Toggle {
/**

View file

@ -1,5 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import TagRenderingQuestion from "./TagRenderingQuestion";
import Translations from "../i18n/Translations";
import Combine from "../Base/Combine";
@ -8,7 +7,8 @@ import State from "../../State";
import Svg from "../../Svg";
import Toggle from "../Input/Toggle";
import BaseUIElement from "../BaseUIElement";
import {Unit} from "../../Customizations/JSON/Denomination";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import {Unit} from "../../Models/Unit";
export default class EditableTagRendering extends Toggle {

View file

@ -1,11 +1,9 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import EditableTagRendering from "./EditableTagRendering";
import QuestionBox from "./QuestionBox";
import Combine from "../Base/Combine";
import TagRenderingAnswer from "./TagRenderingAnswer";
import State from "../../State";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import {Tag} from "../../Logic/Tags/Tag";
import Constants from "../../Models/Constants";
@ -14,6 +12,11 @@ import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import DeleteWizard from "./DeleteWizard";
import SplitRoadWizard from "./SplitRoadWizard";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {Translation} from "../i18n/Translation";
import {Utils} from "../../Utils";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
export default class FeatureInfoBox extends ScrollableFullScreen {
@ -88,7 +91,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
}
const hasMinimap = layerConfig.tagRenderings.some(tr => tr.hasMinimap())
const hasMinimap = layerConfig.tagRenderings.some(tr => FeatureInfoBox.hasMinimap(tr))
if (!hasMinimap) {
renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap")))
}
@ -136,4 +139,26 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
}
/**
* Returns true if this tag rendering has a minimap in some language.
* Note: this might be hidden by conditions
*/
private static hasMinimap(renderingConfig: TagRenderingConfig): boolean {
const translations: Translation[] = Utils.NoNull([renderingConfig.render, ...(renderingConfig.mappings ?? []).map(m => m.then)]);
for (const translation of translations) {
for (const key in translation.translations) {
if (!translation.translations.hasOwnProperty(key)) {
continue
}
const template = translation.translations[key]
const parts = SubstitutedTranslation.ExtractSpecialComponents(template)
const hasMiniMap = parts.filter(part => part.special !== undefined).some(special => special.special.func.funcName === "minimap")
if (hasMiniMap) {
return true;
}
}
}
return false;
}
}

View file

@ -1,12 +1,12 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import TagRenderingQuestion from "./TagRenderingQuestion";
import Translations from "../i18n/Translations";
import State from "../../State";
import Combine from "../Base/Combine";
import BaseUIElement from "../BaseUIElement";
import {Unit} from "../../Customizations/JSON/Denomination";
import {VariableUiElement} from "../Base/VariableUIElement";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import {Unit} from "../../Models/Unit";
/**

View file

@ -10,10 +10,10 @@ import {LeafletMouseEvent} from "leaflet";
import Combine from "../Base/Combine";
import {Button} from "../Base/Button";
import Translations from "../i18n/Translations";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import SplitAction from "../../Logic/Osm/Actions/SplitAction";
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
import Title from "../Base/Title";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class SplitRoadWizard extends Toggle {
private static splitLayout = new UIEventSource(SplitRoadWizard.GetSplitLayout())

View file

@ -1,10 +1,10 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import {Utils} from "../../Utils";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import List from "../Base/List";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
/***
* Displays the correct value for a known tagrendering

View file

@ -1,6 +1,5 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import {InputElement} from "../Input/InputElement";
import ValidatedTextField from "../Input/ValidatedTextField";
import {FixedInputElement} from "../Input/FixedInputElement";
@ -23,9 +22,10 @@ import {And} from "../../Logic/Tags/And";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import BaseUIElement from "../BaseUIElement";
import {DropDown} from "../Input/DropDown";
import {Unit} from "../../Customizations/JSON/Denomination";
import InputElementWrapper from "../Input/InputElementWrapper";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import {Unit} from "../../Models/Unit";
/**
* Shows the question element.

View file

@ -4,10 +4,10 @@
import {UIEventSource} from "../Logic/UIEventSource";
import * as L from "leaflet"
import "leaflet.markercluster"
import LayerConfig from "../Customizations/JSON/LayerConfig";
import State from "../State";
import FeatureInfoBox from "./Popup/FeatureInfoBox";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export default class ShowDataLayer {

View file

@ -17,13 +17,13 @@ import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
import State from "../State";
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
import BaseUIElement from "./BaseUIElement";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import Title from "./Base/Title";
import Table from "./Base/Table";
import Histogram from "./BigComponents/Histogram";
import Loc from "../Models/Loc";
import {Utils} from "../Utils";
import BaseLayer from "../Models/BaseLayer";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export interface SpecialVisualization {
funcName: string,

View file

@ -4,7 +4,6 @@ import {InitUiElements} from "./InitUiElements";
import {QueryParameters} from "./Logic/Web/QueryParameters";
import {UIEventSource} from "./Logic/UIEventSource";
import * as $ from "jquery";
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import MoreScreen from "./UI/BigComponents/MoreScreen";
import State from "./State";
import Combine from "./UI/Base/Combine";
@ -21,6 +20,7 @@ import ShowDataLayer from "./UI/ShowDataLayer";
import * as L from "leaflet";
import ValidatedTextField from "./UI/Input/ValidatedTextField";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts
SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");

View file

@ -7,9 +7,9 @@ import {UIEventSource} from "./Logic/UIEventSource";
import {Utils} from "./Utils";
import {SubtleButton} from "./UI/Base/SubtleButton";
import LZString from "lz-string";
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
import BaseUIElement from "./UI/BaseUIElement";
import Table from "./UI/Base/Table";
import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson";
const connection = new OsmConnection(false, false, new UIEventSource<string>(undefined), "");

View file

@ -2,9 +2,9 @@ import {lstatSync, readdirSync, readFileSync} from "fs";
import {Utils} from "../Utils";
Utils.runningFromConsole = true
import * as https from "https";
import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import * as fs from "fs";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
export default class ScriptUtils {

View file

@ -5,12 +5,12 @@ import {Utils} from "../Utils"
Utils.runningFromConsole = true;
import {readFileSync, writeFileSync} from "fs";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import SmallLicense from "../Models/smallLicense";
import AllKnownLayers from "../Customizations/AllKnownLayers";
import ScriptUtils from "./ScriptUtils";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
ScriptUtils.fixUtils()

View file

@ -8,17 +8,17 @@ import {Overpass} from "../Logic/Osm/Overpass";
import {existsSync, readFileSync, writeFileSync} from "fs";
import {TagsFilter} from "../Logic/Tags/TagsFilter";
import {Or} from "../Logic/Tags/Or";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import ScriptUtils from "./ScriptUtils";
import ExtractRelations from "../Logic/Osm/ExtractRelations";
import * as OsmToGeoJson from "osmtogeojson";
import MetaTagging from "../Logic/MetaTagging";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import {GeoOperations} from "../Logic/GeoOperations";
import {UIEventSource} from "../Logic/UIEventSource";
import * as fs from "fs";
import {TileRange} from "../Models/TileRange";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
function createOverpassObject(theme: LayoutConfig) {

View file

@ -8,9 +8,9 @@ import ValidatedTextField from "../UI/Input/ValidatedTextField";
import BaseUIElement from "../UI/BaseUIElement";
import Translations from "../UI/i18n/Translations";
import {writeFileSync} from "fs";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import State from "../State";
import {QueryParameters} from "../Logic/Web/QueryParameters";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";

View file

@ -1,11 +1,11 @@
import ScriptUtils from "./ScriptUtils";
import {writeFileSync} from "fs";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import * as licenses from "../assets/generated/license_info.json"
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import AllKnownLayers from "../Customizations/AllKnownLayers";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
// It spits out an overview of those to be used to load them

View file

@ -2,14 +2,14 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import {Translation} from "../UI/i18n/Translation";
import Constants from "../Models/Constants";
import * as all_known_layouts from "../assets/generated/known_layers_and_themes.json"
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
const sharp = require('sharp');

View file

@ -2,11 +2,11 @@ import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import Locale from "../UI/i18n/Locale";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import {Translation} from "../UI/i18n/Translation";
import {readFileSync, writeFileSync} from "fs";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
/**
* Generates all the files in "Docs/TagInfo". These are picked up by the taginfo project, showing a link to the mapcomplete theme if the key is used

View file

@ -1,9 +1,9 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import {writeFile} from "fs";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import Translations from "../UI/i18n/Translations";
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
function generateWikiEntry(layout: LayoutConfig) {

View file

@ -1,11 +1,11 @@
import {UIEventSource} from "./Logic/UIEventSource";
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import State from "./State";
import LocationInput from "./UI/Input/LocationInput";
import Loc from "./Models/Loc";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig";
const layout = new UIEventSource<LayoutConfig>(AllKnownLayouts.allKnownLayouts.get("cycle_infra"))
State.state = new State(layout.data)

View file

@ -1,22 +1,9 @@
import {Utils} from "../Utils";
import * as Assert from "assert";
import T from "./TestHelper";
import {GeoOperations} from "../Logic/GeoOperations";
Utils.runningFromConsole = true;
import {equal} from "assert";
import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
import {Translation} from "../UI/i18n/Translation";
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And";
import * as Assert from "assert";
import {GeoOperations} from "../Logic/GeoOperations";
export default class GeoOperationsSpec extends T {

View file

@ -5,8 +5,8 @@ import {equal} from "assert";
import T from "./TestHelper";
import {Translation} from "../UI/i18n/Translation";
import AllKnownLayers from "../Customizations/AllKnownLayers";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import * as bike_repair_station from "../assets/layers/bike_repair_station/bike_repair_station.json"
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
export default class ImageAttributionSpec extends T {
constructor() {

View file

@ -1,21 +1,10 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import {equal} from "assert";
import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "../UI/Popup/EditableTagRendering";
import {Translation} from "../UI/i18n/Translation";
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And";
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
Utils.runningFromConsole = true;
export default class ImageSearcherSpec extends T {
constructor() {

View file

@ -1,14 +1,14 @@
import {Utils} from "../Utils";
import {equal} from "assert";
import T from "./TestHelper";
import {FromJSON} from "../Customizations/JSON/FromJSON";
import Locale from "../UI/i18n/Locale";
import Translations from "../UI/i18n/Translations";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import {Translation} from "../UI/i18n/Translation";
import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours";
import {Tag} from "../Logic/Tags/Tag";
import {And} from "../Logic/Tags/And";
import {TagUtils} from "../Logic/Tags/TagUtils";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
Utils.runningFromConsole = true;
@ -25,7 +25,7 @@ export default class TagSpec extends T {
}],
["Parse tag config", (() => {
const tag = FromJSON.Tag("key=value") as Tag;
const tag = TagUtils.Tag("key=value") as Tag;
equal(tag.key, "key");
equal(tag.value, "value");
equal(tag.matchesProperties({"key": "value"}), true)
@ -34,13 +34,13 @@ export default class TagSpec extends T {
equal(tag.matchesProperties({"other_key": ""}), false)
equal(tag.matchesProperties({"other_key": "value"}), false)
const isEmpty = FromJSON.Tag("key=") as Tag;
const isEmpty = TagUtils.Tag("key=") as Tag;
equal(isEmpty.matchesProperties({"key": "value"}), false)
equal(isEmpty.matchesProperties({"key": ""}), true)
equal(isEmpty.matchesProperties({"other_key": ""}), true)
equal(isEmpty.matchesProperties({"other_key": "value"}), true)
const isNotEmpty = FromJSON.Tag("key!=");
const isNotEmpty = TagUtils.Tag("key!=");
equal(isNotEmpty.matchesProperties({"key": "value"}), true)
equal(isNotEmpty.matchesProperties({"key": "other_value"}), true)
equal(isNotEmpty.matchesProperties({"key": ""}), false)
@ -48,68 +48,68 @@ export default class TagSpec extends T {
equal(isNotEmpty.matchesProperties({"other_key": "value"}), false)
const and = FromJSON.Tag({"and": ["key=value", "x=y"]}) as And;
const and = TagUtils.Tag({"and": ["key=value", "x=y"]}) as And;
equal((and.and[0] as Tag).key, "key");
equal((and.and[1] as Tag).value, "y");
const notReg = FromJSON.Tag("x!~y") as And;
const notReg = TagUtils.Tag("x!~y") as And;
equal(notReg.matchesProperties({"x": "y"}), false)
equal(notReg.matchesProperties({"x": "z"}), true)
equal(notReg.matchesProperties({"x": ""}), true)
equal(notReg.matchesProperties({}), true)
const noMatch = FromJSON.Tag("key!=value") as Tag;
const noMatch = TagUtils.Tag("key!=value") as Tag;
equal(noMatch.matchesProperties({"key": "value"}), false)
equal(noMatch.matchesProperties({"key": "otherValue"}), true)
equal(noMatch.matchesProperties({"key": ""}), true)
equal(noMatch.matchesProperties({"otherKey": ""}), true)
const multiMatch = FromJSON.Tag("vending~.*bicycle_tube.*") as Tag;
const multiMatch = TagUtils.Tag("vending~.*bicycle_tube.*") as Tag;
equal(multiMatch.matchesProperties({"vending": "bicycle_tube"}), true)
equal(multiMatch.matchesProperties({"vending": "something;bicycle_tube"}), true)
equal(multiMatch.matchesProperties({"vending": "bicycle_tube;something"}), true)
equal(multiMatch.matchesProperties({"vending": "xyz;bicycle_tube;something"}), true)
const nameStartsWith = FromJSON.Tag("name~[sS]peelbos.*")
const nameStartsWith = TagUtils.Tag("name~[sS]peelbos.*")
equal(nameStartsWith.matchesProperties({"name": "Speelbos Sint-Anna"}), true)
equal(nameStartsWith.matchesProperties({"name": "speelbos Sint-Anna"}), true)
equal(nameStartsWith.matchesProperties({"name": "Sint-Anna"}), false)
equal(nameStartsWith.matchesProperties({"name": ""}), false)
const assign = FromJSON.Tag("survey:date:={_date:now}")
const assign = TagUtils.Tag("survey:date:={_date:now}")
equal(assign.matchesProperties({"survey:date": "2021-03-29", "_date:now": "2021-03-29"}), true);
equal(assign.matchesProperties({"survey:date": "2021-03-29", "_date:now": "2021-01-01"}), false);
equal(assign.matchesProperties({"survey:date": "2021-03-29"}), false);
equal(assign.matchesProperties({"_date:now": "2021-03-29"}), false);
equal(assign.matchesProperties({"some_key": "2021-03-29"}), false);
const notEmptyList = FromJSON.Tag("xyz!~\\[\\]")
const notEmptyList = TagUtils.Tag("xyz!~\\[\\]")
equal(notEmptyList.matchesProperties({"xyz": undefined}), true);
equal(notEmptyList.matchesProperties({"xyz": "[]"}), false);
equal(notEmptyList.matchesProperties({"xyz": "[\"abc\"]"}), true);
let compare = FromJSON.Tag("key<=5")
let compare = TagUtils.Tag("key<=5")
equal(compare.matchesProperties({"key": undefined}), false);
equal(compare.matchesProperties({"key": "6"}), false);
equal(compare.matchesProperties({"key": "5"}), true);
equal(compare.matchesProperties({"key": "4"}), true);
compare = FromJSON.Tag("key<5")
compare = TagUtils.Tag("key<5")
equal(compare.matchesProperties({"key": undefined}), false);
equal(compare.matchesProperties({"key": "6"}), false);
equal(compare.matchesProperties({"key": "5"}), false);
equal(compare.matchesProperties({"key": "4.2"}), true);
compare = FromJSON.Tag("key>5")
compare = TagUtils.Tag("key>5")
equal(compare.matchesProperties({"key": undefined}), false);
equal(compare.matchesProperties({"key": "6"}), true);
equal(compare.matchesProperties({"key": "5"}), false);
equal(compare.matchesProperties({"key": "4.2"}), false);
compare = FromJSON.Tag("key>=5")
compare = TagUtils.Tag("key>=5")
equal(compare.matchesProperties({"key": undefined}), false);
equal(compare.matchesProperties({"key": "6"}), true);
equal(compare.matchesProperties({"key": "5"}), true);
@ -190,7 +190,7 @@ export default class TagSpec extends T {
"protect_class!=98"
]
}
const filter = FromJSON.Tag(t)
const filter = TagUtils.Tag(t)
const overpass = filter.asOverpass();
console.log(overpass)
equal(overpass[0], "[\"boundary\"=\"protected_area\"][\"protect_class\"!~\"^98$\"]")
@ -201,7 +201,7 @@ export default class TagSpec extends T {
t
]
}
const overpassOr = FromJSON.Tag(or).asOverpass()
const overpassOr = TagUtils.Tag(or).asOverpass()
equal(2, overpassOr.length)
equal(overpassOr[1], "[\"boundary\"=\"protected_area\"][\"protect_class\"!~\"^98$\"]")
@ -209,7 +209,7 @@ export default class TagSpec extends T {
"amenity=drinking_water",
or
]}
const overpassOrInor = FromJSON.Tag(orInOr).asOverpass()
const overpassOrInor = TagUtils.Tag(orInOr).asOverpass()
equal(3, overpassOrInor.length)
}
], [

View file

@ -4,10 +4,10 @@ import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import * as assert from "assert";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
export default class ThemeSpec extends T{
constructor() {

View file

@ -1,6 +1,7 @@
import T from "./TestHelper";
import {Denomination, Unit} from "../Customizations/JSON/Denomination";
import {equal} from "assert";
import {Unit} from "../Models/Unit";
import {Denomination} from "../Models/Denomination";
export default class UnitsSpec extends T {