forked from MapComplete/MapComplete
Add support for units to clean up tags when they enter mapcomplete; add example of this usage in the climbing theme, add climbing theme title icons with length and needed number of carabiners
This commit is contained in:
parent
89f6f606c8
commit
966fcda8d1
20 changed files with 302 additions and 111 deletions
|
@ -8,12 +8,14 @@ export default class AllKnownLayers {
|
|||
// Must be below the list...
|
||||
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
|
||||
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
|
||||
|
||||
public static sharedUnits: any[] = []
|
||||
|
||||
private static getSharedLayers(): Map<string, LayerConfig> {
|
||||
const sharedLayers = new Map<string, LayerConfig>();
|
||||
for (const layer of known_layers.layers) {
|
||||
try {
|
||||
const parsed = new LayerConfig(layer, "shared_layers")
|
||||
const parsed = new LayerConfig(layer, AllKnownLayers.sharedUnits,"shared_layers")
|
||||
sharedLayers.set(layer.id, parsed);
|
||||
sharedLayers[layer.id] = parsed;
|
||||
} catch (e) {
|
||||
|
@ -33,7 +35,7 @@ export default class AllKnownLayers {
|
|||
continue;
|
||||
}
|
||||
try {
|
||||
const parsed = new LayerConfig(layer, "shared_layer_in_theme")
|
||||
const parsed = new LayerConfig(layer, AllKnownLayers.sharedUnits ,"shared_layer_in_theme")
|
||||
sharedLayers.set(layer.id, parsed);
|
||||
sharedLayers[layer.id] = parsed;
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,13 +1,59 @@
|
|||
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";
|
||||
|
||||
export class Unit {
|
||||
public readonly human: Translation;
|
||||
public readonly appliesToKeys: Set<string>;
|
||||
public readonly denominations : Denomination[];
|
||||
public readonly defaultDenom: Denomination;
|
||||
constructor(appliesToKeys: string[], applicableUnits: Denomination[]) {
|
||||
this.appliesToKeys = new Set( appliesToKeys);
|
||||
this.denominations = applicableUnits;
|
||||
this.defaultDenom = applicableUnits.filter(denom => denom.default)[0]
|
||||
}
|
||||
|
||||
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] {
|
||||
for (const denomination of this.denominations) {
|
||||
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
|
||||
|
||||
const elems = denom.prefix ? [human, stripped] : [stripped , human];
|
||||
return new Combine(elems)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class Denomination {
|
||||
private readonly _human: Translation;
|
||||
private readonly alternativeDenominations: string [];
|
||||
private readonly canonical: string;
|
||||
private readonly default: boolean;
|
||||
private readonly prefix: boolean;
|
||||
public readonly canonical: string;
|
||||
readonly default: boolean;
|
||||
readonly prefix: boolean;
|
||||
|
||||
constructor(json: UnitConfigJson, context: string) {
|
||||
context = `${context}.unit(${json.canonicalDenomination})`
|
||||
|
@ -26,15 +72,22 @@ export class Unit {
|
|||
|
||||
this.default = json.default ?? false;
|
||||
|
||||
this.human = Translations.T(json.human, context + "human")
|
||||
this._human = Translations.T(json.human, context + "human")
|
||||
|
||||
this.prefix = json.prefix ?? false;
|
||||
|
||||
}
|
||||
|
||||
get human() : Translation {
|
||||
return this._human.Clone()
|
||||
}
|
||||
|
||||
public canonicalValue(value: string) {
|
||||
const stripped = this.StrippedValue(value)
|
||||
if(stripped === null){
|
||||
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
|
||||
|
@ -46,11 +99,13 @@ export class Unit {
|
|||
* - the value is a Number (without unit) and default is set
|
||||
*
|
||||
* Returns null if it doesn't match this unit
|
||||
* @param value
|
||||
* @constructor
|
||||
*/
|
||||
private StrippedValue(value: string): string {
|
||||
public StrippedValue(value: string, actAsDefault?: boolean): string {
|
||||
|
||||
if(value === undefined){
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.prefix) {
|
||||
if (value.startsWith(this.canonical)) {
|
||||
return value.substring(this.canonical.length).trim();
|
||||
|
@ -72,7 +127,7 @@ export class Unit {
|
|||
}
|
||||
|
||||
|
||||
if (this.default) {
|
||||
if (this.default || actAsDefault) {
|
||||
const parsed = Number(value.trim())
|
||||
if (!isNaN(parsed)) {
|
||||
return value.trim();
|
|
@ -17,6 +17,7 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
|||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
|
||||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
import {Denomination, Unit} from "./Denomination";
|
||||
|
||||
export default class LayerConfig {
|
||||
|
||||
|
@ -46,6 +47,7 @@ export default class LayerConfig {
|
|||
width: TagRenderingConfig;
|
||||
dashArray: TagRenderingConfig;
|
||||
wayHandling: number;
|
||||
public readonly units: Unit[];
|
||||
|
||||
presets: {
|
||||
title: Translation,
|
||||
|
@ -56,8 +58,10 @@ export default class LayerConfig {
|
|||
tagRenderings: TagRenderingConfig [];
|
||||
|
||||
constructor(json: LayerConfigJson,
|
||||
units:Unit[],
|
||||
context?: string,
|
||||
official: boolean = true,) {
|
||||
this.units = units;
|
||||
context = context + "." + json.id;
|
||||
const self = this;
|
||||
this.id = json.id;
|
||||
|
|
|
@ -109,7 +109,8 @@ export interface LayerConfigJson {
|
|||
/**
|
||||
* Small icons shown next to the title.
|
||||
* If not specified, the OsmLink and wikipedia links will be used by default.
|
||||
* Use an empty array to hide them
|
||||
* Use an empty array to hide them.
|
||||
* Note that "defaults" will insert all the default titleIcons
|
||||
*/
|
||||
titleIcons?: (string | TagRenderingConfigJson)[];
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {LayoutConfigJson} from "./LayoutConfigJson";
|
|||
import AllKnownLayers from "../AllKnownLayers";
|
||||
import SharedTagRenderings from "../SharedTagRenderings";
|
||||
import {Utils} from "../../Utils";
|
||||
import {Unit} from "./Unit";
|
||||
import {Denomination, Unit} from "./Denomination";
|
||||
|
||||
export default class LayoutConfig {
|
||||
public readonly id: string;
|
||||
|
@ -47,7 +47,7 @@ export default class LayoutConfig {
|
|||
How long is the cache valid, in seconds?
|
||||
*/
|
||||
public readonly cacheTimeout?: number;
|
||||
public readonly units: { appliesToKeys: Set<string>, applicableUnits: Unit[] }[] = []
|
||||
public readonly units: Unit[] = []
|
||||
private readonly _official: boolean;
|
||||
|
||||
constructor(json: LayoutConfigJson, official = true, context?: string) {
|
||||
|
@ -73,6 +73,7 @@ export default class LayoutConfig {
|
|||
if (json.description === undefined) {
|
||||
throw "Description not defined in " + this.id;
|
||||
}
|
||||
this.units = LayoutConfig.ExtractUnits(json, context);
|
||||
this.title = new Translation(json.title, context + ".title");
|
||||
this.description = new Translation(json.description, context + ".description");
|
||||
this.shortDescription = json.shortDescription === undefined ? this.description.FirstSentence() : new Translation(json.shortDescription, context + ".shortdescription");
|
||||
|
@ -98,7 +99,7 @@ export default class LayoutConfig {
|
|||
if (AllKnownLayers.sharedLayersJson[layer] !== undefined) {
|
||||
if (json.overrideAll !== undefined) {
|
||||
let lyr = JSON.parse(JSON.stringify(AllKnownLayers.sharedLayersJson[layer]));
|
||||
return new LayerConfig(Utils.Merge(json.overrideAll, lyr), `${this.id}+overrideAll.layers[${i}]`, official);
|
||||
return new LayerConfig(Utils.Merge(json.overrideAll, lyr), this.units,`${this.id}+overrideAll.layers[${i}]`, official);
|
||||
} else {
|
||||
return AllKnownLayers.sharedLayers[layer]
|
||||
}
|
||||
|
@ -124,7 +125,7 @@ export default class LayoutConfig {
|
|||
}
|
||||
|
||||
// @ts-ignore
|
||||
return new LayerConfig(layer, `${this.id}.layers[${i}]`, official)
|
||||
return new LayerConfig(layer, this.units, `${this.id}.layers[${i}]`, official)
|
||||
});
|
||||
|
||||
// ALl the layers are constructed, let them share tags in now!
|
||||
|
@ -187,6 +188,10 @@ export default class LayoutConfig {
|
|||
this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60)
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static ExtractUnits(json: LayoutConfigJson, context: string) : Unit[]{
|
||||
const result: Unit[] = []
|
||||
if ((json.units ?? []).length !== 0) {
|
||||
for (let i1 = 0; i1 < json.units.length; i1++) {
|
||||
let unit = json.units[i1];
|
||||
|
@ -206,30 +211,31 @@ export default class LayoutConfig {
|
|||
|
||||
const defaultSet = unit.applicableUnits.filter(u => u.default === true)
|
||||
// No default is defined - we pick the first as default
|
||||
if(defaultSet.length === 0){
|
||||
unit.applicableUnits[0].default = true
|
||||
if (defaultSet.length === 0) {
|
||||
unit.applicableUnits[0].default = true
|
||||
}
|
||||
|
||||
|
||||
// Check that there are not multiple defaults
|
||||
if (defaultSet.length > 1) {
|
||||
throw `Multiple units are set as default: they have canonical values of ${defaultSet.map(u => u.canonicalDenomination).join(", ")}`
|
||||
}
|
||||
const applicable = unit.applicableUnits.map((u, i) => new Unit(u, `${context}.units[${i}]`))
|
||||
this.units.push({
|
||||
appliesToKeys: new Set(appliesTo),
|
||||
applicableUnits: applicable
|
||||
})
|
||||
const applicable = unit.applicableUnits.map((u, i) => new Denomination(u, `${context}.units[${i}]`))
|
||||
result.push(new Unit( appliesTo, applicable));
|
||||
}
|
||||
|
||||
const seenKeys = new Set<string>()
|
||||
for (const unit of this.units) {
|
||||
const alreadySeen = Array.from(unit.appliesToKeys).filter(key => seenKeys.has(key));
|
||||
for (const unit of result) {
|
||||
const alreadySeen = Array.from(unit.appliesToKeys).filter((key: string) => seenKeys.has(key));
|
||||
if (alreadySeen.length > 0) {
|
||||
throw `${context}.units: multiple units define the same keys. The key(s) ${alreadySeen.join(",")} occur multiple times`
|
||||
}
|
||||
unit.appliesToKeys.forEach(key => seenKeys.add(key))
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public CustomCodeSnippets(): string[] {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue