Refacotring: move themeConfig into models

This commit is contained in:
pietervdvn 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,22 +30,22 @@ 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

@ -42,14 +42,14 @@ export interface DeleteConfigJson {
* In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).
* To still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'
* It is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!
*
* Example (note that "amenity=" erases the 'amenity'-key alltogether):
*
* Example (note that "amenity=" erases the 'amenity'-key alltogether):
* ```
* {
* "and": ["disussed:amenity=public_bookcase", "amenity="]
* }
* ```
*
*
* or (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):
* ```
* {

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
@ -222,7 +222,7 @@ export interface LayerConfigJson {
/**
* If set, the user will prompted to confirm the location before actually adding the data.
* This will be with a 'drag crosshair'-method.
*
*
* If 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category.
*/
preciseInput?: true | {

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.
@ -15,7 +15,7 @@ import UnitConfigJson from "./UnitConfigJson";
* General remark: a type (string | any) indicates either a fixed or a translatable string.
*/
export interface LayoutConfigJson {
/**
* The id of this layout.
*
@ -207,7 +207,7 @@ export interface LayoutConfigJson {
* ```
*
* It's also possible to load multiple layers at once, for example, if you would like for both drinking water and benches to start at the zoomlevel at 12, you would use the following:
*
*
* ```
* "layer": {
* "builtin": ["benches", "drinking_water"],
@ -226,9 +226,9 @@ export interface LayoutConfigJson {
*
* Not only do we want to write consistent data to OSM, we also want to present this consistently to the user.
* This is handled by defining units.
*
*
* # Rendering
*
*
* To render a value with long (human) denomination, use {canonical(key)}
*
* # Usage
@ -336,7 +336,7 @@ export interface LayoutConfigJson {
enableGeolocation?: boolean;
enableBackgroundLayerSelection?: boolean;
enableShowAllQuestions?: boolean;
enableDownload?: boolean;
enableDownload?: boolean;
enablePdfDownload?: boolean;
}
}

View file

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

View file

@ -8,7 +8,7 @@ export interface TagRenderingConfigJson {
/**
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.
*
*
* Note that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`
*/
render?: string | any,
@ -30,7 +30,7 @@ export interface TagRenderingConfigJson {
* Allow freeform text input from the user
*/
freeform?: {
/**
* If this key is present, then 'render' is used to display the value.
* If this is undefined, the rendering is _always_ shown
@ -55,7 +55,7 @@ export interface TagRenderingConfigJson {
/**
* When set, influences the way a question is asked.
* Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
*
*
* This combines badly with special input elements, as it'll distort the layout.
*/
inline?: boolean
@ -88,12 +88,12 @@ export interface TagRenderingConfigJson {
then: string | any,
/**
* In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).
*
*
* In the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.
* In this case, one of the mappings can be hiden by setting this flag.
*
*
* To demonstrate an example making a default assumption:
*
*
* mappings: [
* {
* if: "access=", -- no access tag present, we assume accessible
@ -109,8 +109,8 @@ export interface TagRenderingConfigJson {
* then: "Not accessible to the public"
* }
* ]
*
*
*
*
* For example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.
* Then, we would add two mappings:
* {
@ -122,13 +122,13 @@ export interface TagRenderingConfigJson {
* then: "Maintained by Agentschap Natuur en Bos"
* hideInAnswer: true
* }
*
*
* Hide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.
* Keep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch
*
*
* e.g., for toilets: if "wheelchair=no", we know there is no wheelchair dedicated room.
* For the location of the changing table, the option "in the wheelchair accessible toilet is weird", so we write:
*
*
* {
* "question": "Where is the changing table located?"
* "mappings": [
@ -138,7 +138,7 @@ export interface TagRenderingConfigJson {
*
* ]
* }
*
*
* Also have a look for the meta-tags
* {
* if: "operator=Agentschap Natuur en Bos",
@ -156,7 +156,7 @@ export interface TagRenderingConfigJson {
* If this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`
*/
ifnot?: AndOrTagConfigJson | string
}[]
/**
@ -164,4 +164,4 @@ export interface TagRenderingConfigJson {
* However, it will _only_ be shown if it matches the overpass-tags of the layer it was originally defined in.
*/
roaming?: boolean
}
}

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"
);
@ -144,9 +143,9 @@ export default class LayerConfig {
this.minzoomVisible = json.minzoomVisible ?? this.minzoom;
this.wayHandling = json.wayHandling ?? 0;
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
@ -158,8 +157,8 @@ export default class LayerConfig {
} else {
snapToLayers = pr.preciseInput.snapToLayer
}
let preferredBackground : string[]
let preferredBackground: string[]
if (typeof pr.preciseInput.preferredBackground === "string") {
preferredBackground = [pr.preciseInput.preferredBackground]
} else {
@ -171,10 +170,10 @@ export default class LayerConfig {
maxSnapDistance: pr.preciseInput.maxSnapDistance ?? 10
}
}
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;
@ -591,4 +590,4 @@ export default class LayerConfig {
return allIcons;
}
}
}

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 {