diff --git a/Customizations/AllKnownLayers.ts b/Customizations/AllKnownLayers.ts index 97e195db1..ea61ce7fd 100644 --- a/Customizations/AllKnownLayers.ts +++ b/Customizations/AllKnownLayers.ts @@ -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 { diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 54363cc25..35cfb010c 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -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 { diff --git a/Customizations/JSON/Denomination.ts b/Customizations/JSON/Denomination.ts deleted file mode 100644 index 2b9779f94..000000000 --- a/Customizations/JSON/Denomination.ts +++ /dev/null @@ -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; - 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(); - 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() - 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; - } - - -} \ No newline at end of file diff --git a/Customizations/JSON/FilterConfig.ts b/Customizations/JSON/FilterConfig.ts deleted file mode 100644 index 946aef43c..000000000 --- a/Customizations/JSON/FilterConfig.ts +++ /dev/null @@ -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 }; - }); - } -} diff --git a/Customizations/JSON/FilterConfigJson.ts b/Customizations/JSON/FilterConfigJson.ts deleted file mode 100644 index f1a802571..000000000 --- a/Customizations/JSON/FilterConfigJson.ts +++ /dev/null @@ -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 }[]; -} diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts deleted file mode 100644 index c20d183ae..000000000 --- a/Customizations/JSON/FromJSON.ts +++ /dev/null @@ -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))); - } - } -} \ No newline at end of file diff --git a/Customizations/SharedTagRenderings.ts b/Customizations/SharedTagRenderings.ts index 0e9bc494f..6c2a44505 100644 --- a/Customizations/SharedTagRenderings.ts +++ b/Customizations/SharedTagRenderings.ts @@ -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 { diff --git a/InitUiElements.ts b/InitUiElements.ts index 483ce6f6b..9c378f76c 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -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( diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 169fbbaab..f96a2c62f 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -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 { /** diff --git a/Logic/Actors/InstalledThemes.ts b/Logic/Actors/InstalledThemes.ts index b395fbe20..77cba3a8c 100644 --- a/Logic/Actors/InstalledThemes.ts +++ b/Logic/Actors/InstalledThemes.ts @@ -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 }[]>; diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index d76ef1206..6f03e3f28 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -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 { diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 8c807cadd..48c73c03e 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -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 { diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index be4f2b8c8..5e91b4567 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -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 { diff --git a/Logic/FeatureSource/FilteringFeatureSource.ts b/Logic/FeatureSource/FilteringFeatureSource.ts index 2d06c9dca..2daf9bf67 100644 --- a/Logic/FeatureSource/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/FilteringFeatureSource.ts @@ -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 }[]> = diff --git a/Logic/FeatureSource/GeoJsonSource.ts b/Logic/FeatureSource/GeoJsonSource.ts index c2a9db3d6..c3314d40a 100644 --- a/Logic/FeatureSource/GeoJsonSource.ts +++ b/Logic/FeatureSource/GeoJsonSource.ts @@ -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"; /** diff --git a/Logic/FeatureSource/LocalStorageSaver.ts b/Logic/FeatureSource/LocalStorageSaver.ts index 9d4994945..dce44f4d7 100644 --- a/Logic/FeatureSource/LocalStorageSaver.ts +++ b/Logic/FeatureSource/LocalStorageSaver.ts @@ -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"; diff --git a/Logic/FeatureSource/LocalStorageSource.ts b/Logic/FeatureSource/LocalStorageSource.ts index 1cabd6839..2626fe267 100644 --- a/Logic/FeatureSource/LocalStorageSource.ts +++ b/Logic/FeatureSource/LocalStorageSource.ts @@ -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 }[]>; diff --git a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts index a58442edc..b2e5fba14 100644 --- a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts +++ b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts @@ -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) diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 48733d952..ea0a41e12 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -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 { diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index 019d0efb2..23e85d07b 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -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 { diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index bc81c7b05..9ceb36d5c 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -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 { diff --git a/Logic/Tags/TagUtils.ts b/Logic/Tags/TagUtils.ts index 6fb73f245..fff39e8c9 100644 --- a/Logic/Tags/TagUtils.ts +++ b/Logic/Tags/TagUtils.ts @@ -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))); + } + } } \ No newline at end of file diff --git a/Models/Constants.ts b/Models/Constants.ts index ca119915b..86a8da825 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -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 = { diff --git a/Models/Denomination.ts b/Models/Denomination.ts new file mode 100644 index 000000000..d2daba38a --- /dev/null +++ b/Models/Denomination.ts @@ -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; + } + + +} \ No newline at end of file diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 9bd72ae8d..1cf167f45 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -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; diff --git a/Customizations/JSON/DeleteConfig.ts b/Models/ThemeConfig/DeleteConfig.ts similarity index 68% rename from Customizations/JSON/DeleteConfig.ts rename to Models/ThemeConfig/DeleteConfig.ts index 909585189..abc264584 100644 --- a/Customizations/JSON/DeleteConfig.ts +++ b/Models/ThemeConfig/DeleteConfig.ts @@ -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 } - - + + } \ No newline at end of file diff --git a/Models/ThemeConfig/FilterConfig.ts b/Models/ThemeConfig/FilterConfig.ts new file mode 100644 index 000000000..977d6b1ef --- /dev/null +++ b/Models/ThemeConfig/FilterConfig.ts @@ -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}; + }); + } +} \ No newline at end of file diff --git a/Customizations/JSON/DeleteConfigJson.ts b/Models/ThemeConfig/Json/DeleteConfigJson.ts similarity index 98% rename from Customizations/JSON/DeleteConfigJson.ts rename to Models/ThemeConfig/Json/DeleteConfigJson.ts index 0a605dcdb..c6bb1e675 100644 --- a/Customizations/JSON/DeleteConfigJson.ts +++ b/Models/ThemeConfig/Json/DeleteConfigJson.ts @@ -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='): * ``` * { diff --git a/Models/ThemeConfig/Json/FilterConfigJson.ts b/Models/ThemeConfig/Json/FilterConfigJson.ts new file mode 100644 index 000000000..c49f9f3eb --- /dev/null +++ b/Models/ThemeConfig/Json/FilterConfigJson.ts @@ -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 }[]; +} \ No newline at end of file diff --git a/Customizations/JSON/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts similarity index 99% rename from Customizations/JSON/LayerConfigJson.ts rename to Models/ThemeConfig/Json/LayerConfigJson.ts index 49342b290..fcd11aa90 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -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 | { diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts similarity index 99% rename from Customizations/JSON/LayoutConfigJson.ts rename to Models/ThemeConfig/Json/LayoutConfigJson.ts index afbc0aebc..fd332e8ca 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -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; -} +} \ No newline at end of file diff --git a/Customizations/JSON/TagConfigJson.ts b/Models/ThemeConfig/Json/TagConfigJson.ts similarity index 97% rename from Customizations/JSON/TagConfigJson.ts rename to Models/ThemeConfig/Json/TagConfigJson.ts index d167ba0c7..90e833178 100644 --- a/Customizations/JSON/TagConfigJson.ts +++ b/Models/ThemeConfig/Json/TagConfigJson.ts @@ -1,5 +1,4 @@ - export interface AndOrTagConfigJson { and?: (string | AndOrTagConfigJson)[] or?: (string | AndOrTagConfigJson)[] -} +} \ No newline at end of file diff --git a/Customizations/JSON/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts similarity index 98% rename from Customizations/JSON/TagRenderingConfigJson.ts rename to Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 843889525..9152bf463 100644 --- a/Customizations/JSON/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -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. '{website}' or include images such as `This is of type A
` */ 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 -} +} \ No newline at end of file diff --git a/Customizations/JSON/UnitConfigJson.ts b/Models/ThemeConfig/Json/UnitConfigJson.ts similarity index 93% rename from Customizations/JSON/UnitConfigJson.ts rename to Models/ThemeConfig/Json/UnitConfigJson.ts index 699bf3212..c5e8573a0 100644 --- a/Customizations/JSON/UnitConfigJson.ts +++ b/Models/ThemeConfig/Json/UnitConfigJson.ts @@ -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 '€' diff --git a/Customizations/JSON/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts similarity index 96% rename from Customizations/JSON/LayerConfig.ts rename to Models/ThemeConfig/LayerConfig.ts index 04093daa7..c99f01be4 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -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; } -} +} \ No newline at end of file diff --git a/Customizations/JSON/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts similarity index 98% rename from Customizations/JSON/LayoutConfig.ts rename to Models/ThemeConfig/LayoutConfig.ts index d29e93844..9ce8105b6 100644 --- a/Customizations/JSON/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -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; diff --git a/Customizations/JSON/PresetConfig.ts b/Models/ThemeConfig/PresetConfig.ts similarity index 100% rename from Customizations/JSON/PresetConfig.ts rename to Models/ThemeConfig/PresetConfig.ts diff --git a/Customizations/JSON/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts similarity index 94% rename from Customizations/JSON/SourceConfig.ts rename to Models/ThemeConfig/SourceConfig.ts index 2ed8fc40d..db9d21f4f 100644 --- a/Customizations/JSON/SourceConfig.ts +++ b/Models/ThemeConfig/SourceConfig.ts @@ -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})` } diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts similarity index 89% rename from Customizations/JSON/TagRenderingConfig.ts rename to Models/ThemeConfig/TagRenderingConfig.ts index c3bb99273..099b830b2 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -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; - } + } \ No newline at end of file diff --git a/Models/Unit.ts b/Models/Unit.ts new file mode 100644 index 000000000..434d53389 --- /dev/null +++ b/Models/Unit.ts @@ -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; + 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(); + 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() + + 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; + } +} \ No newline at end of file diff --git a/State.ts b/State.ts index 9b7775504..ddc44e72b 100644 --- a/State.ts +++ b/State.ts @@ -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 diff --git a/UI/BigComponents/Attribution.ts b/UI/BigComponents/Attribution.ts index 0eefd107b..b2cdc4451 100644 --- a/UI/BigComponents/Attribution.ts +++ b/UI/BigComponents/Attribution.ts @@ -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 diff --git a/UI/BigComponents/AttributionPanel.ts b/UI/BigComponents/AttributionPanel.ts index cd87c2bf3..be3594f03 100644 --- a/UI/BigComponents/AttributionPanel.ts +++ b/UI/BigComponents/AttributionPanel.ts @@ -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 diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index dfa486fd1..d4ea4d61a 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -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"; /** diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index cd16929ce..1a0ae74ed 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -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 { diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index 97d9dbb96..1ba7d88a9 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -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 { diff --git a/UI/BigComponents/PersonalLayersPanel.ts b/UI/BigComponents/PersonalLayersPanel.ts index a2f9e6d2e..559f4bc18 100644 --- a/UI/BigComponents/PersonalLayersPanel.ts +++ b/UI/BigComponents/PersonalLayersPanel.ts @@ -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 { diff --git a/UI/BigComponents/ShareScreen.ts b/UI/BigComponents/ShareScreen.ts index 7396c1568..80b3f5d73 100644 --- a/UI/BigComponents/ShareScreen.ts +++ b/UI/BigComponents/ShareScreen.ts @@ -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 { diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index a850841a9..3c7137c6d 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -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: diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts index fd431eb91..42da3b90f 100644 --- a/UI/ExportPDF.ts +++ b/UI/ExportPDF.ts @@ -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 diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index d97829609..1fd6f9e5e 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -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 { diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 508df0245..423ceef31 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -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 { diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index eb65544a0..9903aa611 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -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, diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 146c404d4..18c91d033 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -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 { /** diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index 0d735fa92..0129afef8 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -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 { diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 3f663a4b9..44373fea0 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -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; + } + } diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index f2c09625e..2b7fdfd7a 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -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"; /** diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index f54126be6..28e1e62a5 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -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()) diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index c8953dd01..3567ec70d 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -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 diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 5eba4693a..bdeb11fda 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -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. diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index e6e318f00..ced30b29f 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -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 { diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index ec6c34b5c..21a587ba6 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -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, diff --git a/index.ts b/index.ts index 634ad8533..cd146653f 100644 --- a/index.ts +++ b/index.ts @@ -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/"); diff --git a/preferences.ts b/preferences.ts index a7ae07ded..70c2862d0 100644 --- a/preferences.ts +++ b/preferences.ts @@ -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(undefined), ""); diff --git a/scripts/ScriptUtils.ts b/scripts/ScriptUtils.ts index 3427071dd..e462deb68 100644 --- a/scripts/ScriptUtils.ts +++ b/scripts/ScriptUtils.ts @@ -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 { diff --git a/scripts/fixTheme.ts b/scripts/fixTheme.ts index bed224b74..33b3e5973 100644 --- a/scripts/fixTheme.ts +++ b/scripts/fixTheme.ts @@ -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() diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index d76347d25..128dfb2e1 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -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) { diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 1cfe1418a..fead1c803 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -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"; diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 44d4ac9ad..06e6b3e96 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -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 diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts index 281acbf37..7a1a2256d 100644 --- a/scripts/generateLayouts.ts +++ b/scripts/generateLayouts.ts @@ -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'); diff --git a/scripts/generateTaginfoProjectFiles.ts b/scripts/generateTaginfoProjectFiles.ts index 31ab7798a..ebd39e2af 100644 --- a/scripts/generateTaginfoProjectFiles.ts +++ b/scripts/generateTaginfoProjectFiles.ts @@ -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 diff --git a/scripts/generateWikiPage.ts b/scripts/generateWikiPage.ts index 0ff0bf3aa..7b8aa0079 100644 --- a/scripts/generateWikiPage.ts +++ b/scripts/generateWikiPage.ts @@ -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) { diff --git a/test.ts b/test.ts index 26d5dd637..591039879 100644 --- a/test.ts +++ b/test.ts @@ -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(AllKnownLayouts.allKnownLayouts.get("cycle_infra")) State.state = new State(layout.data) diff --git a/test/GeoOperations.spec.ts b/test/GeoOperations.spec.ts index 2e5528f41..b8b3c1261 100644 --- a/test/GeoOperations.spec.ts +++ b/test/GeoOperations.spec.ts @@ -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 { diff --git a/test/ImageAttribution.spec.ts b/test/ImageAttribution.spec.ts index 8a80efa00..8734c4f4d 100644 --- a/test/ImageAttribution.spec.ts +++ b/test/ImageAttribution.spec.ts @@ -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() { diff --git a/test/ImageSearcher.spec.ts b/test/ImageSearcher.spec.ts index 9262360b8..c47b3939f 100644 --- a/test/ImageSearcher.spec.ts +++ b/test/ImageSearcher.spec.ts @@ -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() { diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 5373b4630..b4f8e100b 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -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) } ], [ diff --git a/test/Theme.spec.ts b/test/Theme.spec.ts index 019e90605..e4f86c38f 100644 --- a/test/Theme.spec.ts +++ b/test/Theme.spec.ts @@ -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() { diff --git a/test/Units.spec.ts b/test/Units.spec.ts index 11ea7af33..3d316535a 100644 --- a/test/Units.spec.ts +++ b/test/Units.spec.ts @@ -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 {