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...
|
// Must be below the list...
|
||||||
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
|
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
|
||||||
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
|
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
|
||||||
|
|
||||||
|
public static sharedUnits: any[] = []
|
||||||
|
|
||||||
private static getSharedLayers(): Map<string, LayerConfig> {
|
private static getSharedLayers(): Map<string, LayerConfig> {
|
||||||
const sharedLayers = new Map<string, LayerConfig>();
|
const sharedLayers = new Map<string, LayerConfig>();
|
||||||
for (const layer of known_layers.layers) {
|
for (const layer of known_layers.layers) {
|
||||||
try {
|
try {
|
||||||
const parsed = new LayerConfig(layer, "shared_layers")
|
const parsed = new LayerConfig(layer, AllKnownLayers.sharedUnits,"shared_layers")
|
||||||
sharedLayers.set(layer.id, parsed);
|
sharedLayers.set(layer.id, parsed);
|
||||||
sharedLayers[layer.id] = parsed;
|
sharedLayers[layer.id] = parsed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -33,7 +35,7 @@ export default class AllKnownLayers {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
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.set(layer.id, parsed);
|
||||||
sharedLayers[layer.id] = parsed;
|
sharedLayers[layer.id] = parsed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,13 +1,59 @@
|
||||||
import {Translation} from "../../UI/i18n/Translation";
|
import {Translation} from "../../UI/i18n/Translation";
|
||||||
import UnitConfigJson from "./UnitConfigJson";
|
import UnitConfigJson from "./UnitConfigJson";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
|
import BaseUIElement from "../../UI/BaseUIElement";
|
||||||
|
import Combine from "../../UI/Base/Combine";
|
||||||
|
|
||||||
export class Unit {
|
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 alternativeDenominations: string [];
|
||||||
private readonly canonical: string;
|
public readonly canonical: string;
|
||||||
private readonly default: boolean;
|
readonly default: boolean;
|
||||||
private readonly prefix: boolean;
|
readonly prefix: boolean;
|
||||||
|
|
||||||
constructor(json: UnitConfigJson, context: string) {
|
constructor(json: UnitConfigJson, context: string) {
|
||||||
context = `${context}.unit(${json.canonicalDenomination})`
|
context = `${context}.unit(${json.canonicalDenomination})`
|
||||||
|
@ -26,15 +72,22 @@ export class Unit {
|
||||||
|
|
||||||
this.default = json.default ?? false;
|
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;
|
this.prefix = json.prefix ?? false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get human() : Translation {
|
||||||
|
return this._human.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
public canonicalValue(value: string) {
|
public canonicalValue(value: string, actAsDefault?: boolean) {
|
||||||
const stripped = this.StrippedValue(value)
|
if(value === undefined){
|
||||||
if(stripped === null){
|
return undefined;
|
||||||
|
}
|
||||||
|
const stripped = this.StrippedValue(value, actAsDefault)
|
||||||
|
if (stripped === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return stripped + this.canonical
|
return stripped + this.canonical
|
||||||
|
@ -46,11 +99,13 @@ export class Unit {
|
||||||
* - the value is a Number (without unit) and default is set
|
* - the value is a Number (without unit) and default is set
|
||||||
*
|
*
|
||||||
* Returns null if it doesn't match this unit
|
* 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 (this.prefix) {
|
||||||
if (value.startsWith(this.canonical)) {
|
if (value.startsWith(this.canonical)) {
|
||||||
return value.substring(this.canonical.length).trim();
|
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())
|
const parsed = Number(value.trim())
|
||||||
if (!isNaN(parsed)) {
|
if (!isNaN(parsed)) {
|
||||||
return value.trim();
|
return value.trim();
|
|
@ -17,6 +17,7 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
|
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
|
||||||
import BaseUIElement from "../../UI/BaseUIElement";
|
import BaseUIElement from "../../UI/BaseUIElement";
|
||||||
|
import {Denomination, Unit} from "./Denomination";
|
||||||
|
|
||||||
export default class LayerConfig {
|
export default class LayerConfig {
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ export default class LayerConfig {
|
||||||
width: TagRenderingConfig;
|
width: TagRenderingConfig;
|
||||||
dashArray: TagRenderingConfig;
|
dashArray: TagRenderingConfig;
|
||||||
wayHandling: number;
|
wayHandling: number;
|
||||||
|
public readonly units: Unit[];
|
||||||
|
|
||||||
presets: {
|
presets: {
|
||||||
title: Translation,
|
title: Translation,
|
||||||
|
@ -56,8 +58,10 @@ export default class LayerConfig {
|
||||||
tagRenderings: TagRenderingConfig [];
|
tagRenderings: TagRenderingConfig [];
|
||||||
|
|
||||||
constructor(json: LayerConfigJson,
|
constructor(json: LayerConfigJson,
|
||||||
|
units:Unit[],
|
||||||
context?: string,
|
context?: string,
|
||||||
official: boolean = true,) {
|
official: boolean = true,) {
|
||||||
|
this.units = units;
|
||||||
context = context + "." + json.id;
|
context = context + "." + json.id;
|
||||||
const self = this;
|
const self = this;
|
||||||
this.id = json.id;
|
this.id = json.id;
|
||||||
|
|
|
@ -109,7 +109,8 @@ export interface LayerConfigJson {
|
||||||
/**
|
/**
|
||||||
* Small icons shown next to the title.
|
* Small icons shown next to the title.
|
||||||
* If not specified, the OsmLink and wikipedia links will be used by default.
|
* 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)[];
|
titleIcons?: (string | TagRenderingConfigJson)[];
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {LayoutConfigJson} from "./LayoutConfigJson";
|
||||||
import AllKnownLayers from "../AllKnownLayers";
|
import AllKnownLayers from "../AllKnownLayers";
|
||||||
import SharedTagRenderings from "../SharedTagRenderings";
|
import SharedTagRenderings from "../SharedTagRenderings";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {Unit} from "./Unit";
|
import {Denomination, Unit} from "./Denomination";
|
||||||
|
|
||||||
export default class LayoutConfig {
|
export default class LayoutConfig {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
|
@ -47,7 +47,7 @@ export default class LayoutConfig {
|
||||||
How long is the cache valid, in seconds?
|
How long is the cache valid, in seconds?
|
||||||
*/
|
*/
|
||||||
public readonly cacheTimeout?: number;
|
public readonly cacheTimeout?: number;
|
||||||
public readonly units: { appliesToKeys: Set<string>, applicableUnits: Unit[] }[] = []
|
public readonly units: Unit[] = []
|
||||||
private readonly _official: boolean;
|
private readonly _official: boolean;
|
||||||
|
|
||||||
constructor(json: LayoutConfigJson, official = true, context?: string) {
|
constructor(json: LayoutConfigJson, official = true, context?: string) {
|
||||||
|
@ -73,6 +73,7 @@ export default class LayoutConfig {
|
||||||
if (json.description === undefined) {
|
if (json.description === undefined) {
|
||||||
throw "Description not defined in " + this.id;
|
throw "Description not defined in " + this.id;
|
||||||
}
|
}
|
||||||
|
this.units = LayoutConfig.ExtractUnits(json, context);
|
||||||
this.title = new Translation(json.title, context + ".title");
|
this.title = new Translation(json.title, context + ".title");
|
||||||
this.description = new Translation(json.description, context + ".description");
|
this.description = new Translation(json.description, context + ".description");
|
||||||
this.shortDescription = json.shortDescription === undefined ? this.description.FirstSentence() : new Translation(json.shortDescription, context + ".shortdescription");
|
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 (AllKnownLayers.sharedLayersJson[layer] !== undefined) {
|
||||||
if (json.overrideAll !== undefined) {
|
if (json.overrideAll !== undefined) {
|
||||||
let lyr = JSON.parse(JSON.stringify(AllKnownLayers.sharedLayersJson[layer]));
|
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 {
|
} else {
|
||||||
return AllKnownLayers.sharedLayers[layer]
|
return AllKnownLayers.sharedLayers[layer]
|
||||||
}
|
}
|
||||||
|
@ -124,7 +125,7 @@ export default class LayoutConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @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!
|
// 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)
|
this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExtractUnits(json: LayoutConfigJson, context: string) : Unit[]{
|
||||||
|
const result: Unit[] = []
|
||||||
if ((json.units ?? []).length !== 0) {
|
if ((json.units ?? []).length !== 0) {
|
||||||
for (let i1 = 0; i1 < json.units.length; i1++) {
|
for (let i1 = 0; i1 < json.units.length; i1++) {
|
||||||
let unit = json.units[i1];
|
let unit = json.units[i1];
|
||||||
|
@ -206,30 +211,31 @@ export default class LayoutConfig {
|
||||||
|
|
||||||
const defaultSet = unit.applicableUnits.filter(u => u.default === true)
|
const defaultSet = unit.applicableUnits.filter(u => u.default === true)
|
||||||
// No default is defined - we pick the first as default
|
// No default is defined - we pick the first as default
|
||||||
if(defaultSet.length === 0){
|
if (defaultSet.length === 0) {
|
||||||
unit.applicableUnits[0].default = true
|
unit.applicableUnits[0].default = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that there are not multiple defaults
|
// Check that there are not multiple defaults
|
||||||
if (defaultSet.length > 1) {
|
if (defaultSet.length > 1) {
|
||||||
throw `Multiple units are set as default: they have canonical values of ${defaultSet.map(u => u.canonicalDenomination).join(", ")}`
|
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}]`))
|
const applicable = unit.applicableUnits.map((u, i) => new Denomination(u, `${context}.units[${i}]`))
|
||||||
this.units.push({
|
result.push(new Unit( appliesTo, applicable));
|
||||||
appliesToKeys: new Set(appliesTo),
|
|
||||||
applicableUnits: applicable
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const seenKeys = new Set<string>()
|
const seenKeys = new Set<string>()
|
||||||
for (const unit of this.units) {
|
for (const unit of result) {
|
||||||
const alreadySeen = Array.from(unit.appliesToKeys).filter(key => seenKeys.has(key));
|
const alreadySeen = Array.from(unit.appliesToKeys).filter((key: string) => seenKeys.has(key));
|
||||||
if (alreadySeen.length > 0) {
|
if (alreadySeen.length > 0) {
|
||||||
throw `${context}.units: multiple units define the same keys. The key(s) ${alreadySeen.join(",")} occur multiple times`
|
throw `${context}.units: multiple units define the same keys. The key(s) ${alreadySeen.join(",")} occur multiple times`
|
||||||
}
|
}
|
||||||
unit.appliesToKeys.forEach(key => seenKeys.add(key))
|
unit.appliesToKeys.forEach(key => seenKeys.add(key))
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomCodeSnippets(): string[] {
|
public CustomCodeSnippets(): string[] {
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class SimpleMetaTagger {
|
||||||
(feature => {
|
(feature => {
|
||||||
const units = State.state.layoutToUse.data.units ?? [];
|
const units = State.state.layoutToUse.data.units ?? [];
|
||||||
for (const key in feature.properties) {
|
for (const key in feature.properties) {
|
||||||
if(!feature.properties.hasOwnProperty(key)){
|
if (!feature.properties.hasOwnProperty(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const unit of units) {
|
for (const unit of units) {
|
||||||
|
@ -93,15 +93,10 @@ export default class SimpleMetaTagger {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const value = feature.properties[key]
|
const value = feature.properties[key]
|
||||||
|
const [, denomination] = unit.findDenomination(value)
|
||||||
for (const applicableUnit of unit.applicableUnits) {
|
const canonical = denomination.canonicalValue(value)
|
||||||
const canonical = applicableUnit.canonicalValue(value)
|
console.log("Rewritten ", key, " from", value, "into", canonical)
|
||||||
if (canonical == null) {
|
feature.properties[key] = canonical;
|
||||||
continue
|
|
||||||
}
|
|
||||||
console.log("Rewritten ", key, " from", value, "into", canonical)
|
|
||||||
feature.properties[key] = canonical;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Utils } from "../Utils";
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
|
|
||||||
public static vNumber = "0.8.0a";
|
public static vNumber = "0.8.1";
|
||||||
|
|
||||||
// The user journey states thresholds when a new feature gets unlocked
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
public static userJourney = {
|
public static userJourney = {
|
||||||
|
|
|
@ -3,30 +3,48 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class CombinedInputElement<T> extends InputElement<T> {
|
export default class CombinedInputElement<T, J, X> extends InputElement<X> {
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._combined.ConstructElement();
|
|
||||||
}
|
|
||||||
private readonly _a: InputElement<T>;
|
|
||||||
private readonly _b: BaseUIElement;
|
|
||||||
private readonly _combined: BaseUIElement;
|
|
||||||
public readonly IsSelected: UIEventSource<boolean>;
|
public readonly IsSelected: UIEventSource<boolean>;
|
||||||
constructor(a: InputElement<T>, b: InputElement<T>) {
|
private readonly _a: InputElement<T>;
|
||||||
|
private readonly _b: InputElement<J>;
|
||||||
|
private readonly _combined: BaseUIElement;
|
||||||
|
private readonly _value: UIEventSource<X>
|
||||||
|
private readonly _split: (x: X) => [T, J];
|
||||||
|
|
||||||
|
constructor(a: InputElement<T>, b: InputElement<J>,
|
||||||
|
combine: (t: T, j: J) => X,
|
||||||
|
split: (x: X) => [T, J]) {
|
||||||
super();
|
super();
|
||||||
this._a = a;
|
this._a = a;
|
||||||
this._b = b;
|
this._b = b;
|
||||||
|
this._split = split;
|
||||||
this.IsSelected = this._a.IsSelected.map((isSelected) => {
|
this.IsSelected = this._a.IsSelected.map((isSelected) => {
|
||||||
return isSelected || b.IsSelected.data
|
return isSelected || b.IsSelected.data
|
||||||
}, [b.IsSelected])
|
}, [b.IsSelected])
|
||||||
this._combined = new Combine([this._a, this._b]);
|
this._combined = new Combine([this._a, this._b]);
|
||||||
|
this._value = this._a.GetValue().map(
|
||||||
|
t => combine(t, this._b.GetValue().data),
|
||||||
|
[this._b.GetValue()],
|
||||||
|
)
|
||||||
|
.addCallback(x => {
|
||||||
|
const [t, j] = split(x)
|
||||||
|
this._a.GetValue().setData(t)
|
||||||
|
this._b.GetValue().setData(j)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<T> {
|
GetValue(): UIEventSource<X> {
|
||||||
return this._a.GetValue();
|
return this._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsValid(t: T): boolean {
|
IsValid(x: X): boolean {
|
||||||
return this._a.IsValid(t);
|
const [t, j] = this._split(x)
|
||||||
|
return this._a.IsValid(t) && this._b.IsValid(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this._combined.ConstructElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -270,7 +270,10 @@ export default class ValidatedTextField {
|
||||||
if (tp.inputHelper) {
|
if (tp.inputHelper) {
|
||||||
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(), {
|
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(), {
|
||||||
location: options.location
|
location: options.location
|
||||||
}));
|
}),
|
||||||
|
(a, b) => a, // We can ignore b, as they are linked earlier
|
||||||
|
a => [a, a]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@ import State from "../../State";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {Unit} from "../../Customizations/JSON/Denomination";
|
||||||
|
|
||||||
export default class EditableTagRendering extends Toggle {
|
export default class EditableTagRendering extends Toggle {
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>,
|
constructor(tags: UIEventSource<any>,
|
||||||
configuration: TagRenderingConfig,
|
configuration: TagRenderingConfig,
|
||||||
|
units: Unit [],
|
||||||
editMode = new UIEventSource<boolean>(false)
|
editMode = new UIEventSource<boolean>(false)
|
||||||
) {
|
) {
|
||||||
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration)
|
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration)
|
||||||
|
@ -41,7 +43,7 @@ export default class EditableTagRendering extends Toggle {
|
||||||
editMode.setData(false)
|
editMode.setData(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
const question = new TagRenderingQuestion(tags, configuration,
|
const question = new TagRenderingQuestion(tags, configuration,units,
|
||||||
() => {
|
() => {
|
||||||
editMode.setData(false)
|
editMode.setData(false)
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
tags: UIEventSource<any>,
|
tags: UIEventSource<any>,
|
||||||
layerConfig: LayerConfig
|
layerConfig: LayerConfig,
|
||||||
) {
|
) {
|
||||||
super(() => FeatureInfoBox.GenerateTitleBar(tags, layerConfig),
|
super(() => FeatureInfoBox.GenerateTitleBar(tags, layerConfig),
|
||||||
() => FeatureInfoBox.GenerateContent(tags, layerConfig),
|
() => FeatureInfoBox.GenerateContent(tags, layerConfig),
|
||||||
|
@ -35,7 +35,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
.SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2");
|
.SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2");
|
||||||
const titleIcons = new Combine(
|
const titleIcons = new Combine(
|
||||||
layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon,
|
layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon,
|
||||||
"block w-8 h-8 align-baseline box-content sm:p-0.5", "width: 2rem !important;")
|
"block w-8 h-8 align-baseline box-content sm:p-0.5")
|
||||||
))
|
))
|
||||||
.SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2")
|
.SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2")
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
let questionBox: UIElement = undefined;
|
let questionBox: UIElement = undefined;
|
||||||
|
|
||||||
if (State.state.featureSwitchUserbadge.data) {
|
if (State.state.featureSwitchUserbadge.data) {
|
||||||
questionBox = new QuestionBox(tags, layerConfig.tagRenderings);
|
questionBox = new QuestionBox(tags, layerConfig.tagRenderings, layerConfig.units);
|
||||||
}
|
}
|
||||||
|
|
||||||
let questionBoxIsUsed = false;
|
let questionBoxIsUsed = false;
|
||||||
|
@ -59,7 +59,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
questionBoxIsUsed = true;
|
questionBoxIsUsed = true;
|
||||||
return questionBox;
|
return questionBox;
|
||||||
}
|
}
|
||||||
return new EditableTagRendering(tags, tr);
|
return new EditableTagRendering(tags, tr, layerConfig.units);
|
||||||
});
|
});
|
||||||
if (!questionBoxIsUsed) {
|
if (!questionBoxIsUsed) {
|
||||||
renderings.push(questionBox);
|
renderings.push(questionBox);
|
||||||
|
|
|
@ -5,6 +5,8 @@ import TagRenderingQuestion from "./TagRenderingQuestion";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {Unit} from "../../Customizations/JSON/Denomination";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,12 +16,12 @@ export default class QuestionBox extends UIElement {
|
||||||
private readonly _tags: UIEventSource<any>;
|
private readonly _tags: UIEventSource<any>;
|
||||||
|
|
||||||
private readonly _tagRenderings: TagRenderingConfig[];
|
private readonly _tagRenderings: TagRenderingConfig[];
|
||||||
private _tagRenderingQuestions: UIElement[];
|
private _tagRenderingQuestions: BaseUIElement[];
|
||||||
|
|
||||||
private _skippedQuestions: UIEventSource<number[]> = new UIEventSource<number[]>([])
|
private _skippedQuestions: UIEventSource<number[]> = new UIEventSource<number[]>([])
|
||||||
private _skippedQuestionsButton: UIElement;
|
private _skippedQuestionsButton: BaseUIElement;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>, tagRenderings: TagRenderingConfig[]) {
|
constructor(tags: UIEventSource<any>, tagRenderings: TagRenderingConfig[], units: Unit[]) {
|
||||||
super(tags);
|
super(tags);
|
||||||
this.ListenTo(this._skippedQuestions);
|
this.ListenTo(this._skippedQuestions);
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
|
@ -28,7 +30,7 @@ export default class QuestionBox extends UIElement {
|
||||||
.filter(tr => tr.question !== undefined)
|
.filter(tr => tr.question !== undefined)
|
||||||
.filter(tr => tr.question !== null);
|
.filter(tr => tr.question !== null);
|
||||||
this._tagRenderingQuestions = this._tagRenderings
|
this._tagRenderingQuestions = this._tagRenderings
|
||||||
.map((tagRendering, i) => new TagRenderingQuestion(this._tags, tagRendering,
|
.map((tagRendering, i) => new TagRenderingQuestion(this._tags, tagRendering,units,
|
||||||
() => {
|
() => {
|
||||||
// We save
|
// We save
|
||||||
self._skippedQuestions.ping();
|
self._skippedQuestions.ping();
|
||||||
|
@ -49,7 +51,7 @@ export default class QuestionBox extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender() {
|
InnerRender() {
|
||||||
const allQuestions : UIElement[] = []
|
const allQuestions : BaseUIElement[] = []
|
||||||
for (let i = 0; i < this._tagRenderingQuestions.length; i++) {
|
for (let i = 0; i < this._tagRenderingQuestions.length; i++) {
|
||||||
let tagRendering = this._tagRenderings[i];
|
let tagRendering = this._tagRenderings[i];
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ import {And} from "../../Logic/Tags/And";
|
||||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
import {DropDown} from "../Input/DropDown";
|
import {DropDown} from "../Input/DropDown";
|
||||||
|
import {Unit} from "../../Customizations/JSON/Denomination";
|
||||||
|
import CombinedInputElement from "../Input/CombinedInputElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the question element.
|
* Shows the question element.
|
||||||
|
@ -38,14 +40,17 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
private _inputElement: InputElement<TagsFilter>;
|
private _inputElement: InputElement<TagsFilter>;
|
||||||
private _cancelButton: BaseUIElement;
|
private _cancelButton: BaseUIElement;
|
||||||
private _appliedTags: BaseUIElement;
|
private _appliedTags: BaseUIElement;
|
||||||
|
private readonly _applicableUnit: Unit;
|
||||||
private _question: BaseUIElement;
|
private _question: BaseUIElement;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>,
|
constructor(tags: UIEventSource<any>,
|
||||||
configuration: TagRenderingConfig,
|
configuration: TagRenderingConfig,
|
||||||
|
units: Unit[],
|
||||||
afterSave?: () => void,
|
afterSave?: () => void,
|
||||||
cancelButton?: BaseUIElement
|
cancelButton?: BaseUIElement
|
||||||
) {
|
) {
|
||||||
super(tags);
|
super(tags);
|
||||||
|
this._applicableUnit = units.filter(unit => unit.isApplicableToKey(configuration.freeform?.key))[0];
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
this._configuration = configuration;
|
this._configuration = configuration;
|
||||||
this._cancelButton = cancelButton;
|
this._cancelButton = cancelButton;
|
||||||
|
@ -114,9 +119,9 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
const self = this;
|
const self = this;
|
||||||
let inputEls: InputElement<TagsFilter>[];
|
let inputEls: InputElement<TagsFilter>[];
|
||||||
|
|
||||||
const mappings = (this._configuration.mappings??[])
|
const mappings = (this._configuration.mappings ?? [])
|
||||||
.filter( mapping => {
|
.filter(mapping => {
|
||||||
if(mapping.hideInAnswer === true){
|
if (mapping.hideInAnswer === true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (typeof (mapping.hideInAnswer) !== "boolean" && mapping.hideInAnswer.matchesProperties(this._tags.data)) {
|
if (typeof (mapping.hideInAnswer) !== "boolean" && mapping.hideInAnswer.matchesProperties(this._tags.data)) {
|
||||||
|
@ -124,9 +129,9 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
let allIfNots: TagsFilter[] = Utils.NoNull(this._configuration.mappings?.map(m => m.ifnot) ?? [] );
|
let allIfNots: TagsFilter[] = Utils.NoNull(this._configuration.mappings?.map(m => m.ifnot) ?? []);
|
||||||
const ff = this.GenerateFreeform();
|
const ff = this.GenerateFreeform();
|
||||||
const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
|
const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
|
||||||
|
|
||||||
|
@ -272,7 +277,7 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
then: Translation,
|
then: Translation,
|
||||||
hideInAnswer: boolean | TagsFilter
|
hideInAnswer: boolean | TagsFilter
|
||||||
}, ifNot?: TagsFilter[]): InputElement<TagsFilter> {
|
}, ifNot?: TagsFilter[]): InputElement<TagsFilter> {
|
||||||
|
|
||||||
let tagging = mapping.if;
|
let tagging = mapping.if;
|
||||||
if (ifNot.length > 0) {
|
if (ifNot.length > 0) {
|
||||||
tagging = new And([tagging, ...ifNot])
|
tagging = new And([tagging, ...ifNot])
|
||||||
|
@ -323,16 +328,41 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, {
|
let input: InputElement<string> = ValidatedTextField.InputForType(this._configuration.freeform.type, {
|
||||||
isValid: (str) => (str.length <= 255),
|
isValid: (str) => (str.length <= 255),
|
||||||
country: () => this._tags.data._country,
|
country: () => this._tags.data._country,
|
||||||
location: [this._tags.data._lat, this._tags.data._lon]
|
location: [this._tags.data._lat, this._tags.data._lon]
|
||||||
});
|
});
|
||||||
|
|
||||||
textField.GetValue().setData(this._tags.data[this._configuration.freeform.key]);
|
if (this._applicableUnit) {
|
||||||
|
// We need to apply a unit.
|
||||||
|
// This implies:
|
||||||
|
// We have to create a dropdown with applicable denominations, and fuse those values
|
||||||
|
const unit = this._applicableUnit
|
||||||
|
const unitDropDown = new DropDown("",
|
||||||
|
unit.denominations.map(denom => {
|
||||||
|
return {
|
||||||
|
shown: denom.human,
|
||||||
|
value: denom
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
unitDropDown.GetValue().setData(this._applicableUnit.defaultDenom)
|
||||||
|
unitDropDown.SetStyle("width: min-content")
|
||||||
|
|
||||||
|
input = new CombinedInputElement(
|
||||||
|
input,
|
||||||
|
unitDropDown,
|
||||||
|
(text, denom) => denom?.canonicalValue(text, true) ?? text,
|
||||||
|
(valueWithDenom: string) => unit.findDenomination(valueWithDenom)
|
||||||
|
).SetClass("flex")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input.GetValue().setData(this._tags.data[this._configuration.freeform.key]);
|
||||||
|
|
||||||
return new InputElementMap(
|
return new InputElementMap(
|
||||||
textField, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
|
input, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
|
||||||
pickString, toString
|
pickString, toString
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -33,23 +33,24 @@ export default class SpecialVisualizations {
|
||||||
args: { name: string, defaultValue?: string, doc: string }[]
|
args: { name: string, defaultValue?: string, doc: string }[]
|
||||||
}[] =
|
}[] =
|
||||||
|
|
||||||
[{
|
[
|
||||||
funcName: "all_tags",
|
{
|
||||||
docs: "Prints all key-value pairs of the object - used for debugging",
|
funcName: "all_tags",
|
||||||
args: [],
|
docs: "Prints all key-value pairs of the object - used for debugging",
|
||||||
constr: ((state: State, tags: UIEventSource<any>) => {
|
args: [],
|
||||||
return new VariableUiElement(tags.map(tags => {
|
constr: ((state: State, tags: UIEventSource<any>) => {
|
||||||
const parts = [];
|
return new VariableUiElement(tags.map(tags => {
|
||||||
for (const key in tags) {
|
const parts = [];
|
||||||
if (!tags.hasOwnProperty(key)) {
|
for (const key in tags) {
|
||||||
continue;
|
if (!tags.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parts.push(key + "=" + tags[key]);
|
||||||
}
|
}
|
||||||
parts.push(key + "=" + tags[key]);
|
return parts.join("<br/>")
|
||||||
}
|
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
|
||||||
return parts.join("<br/>")
|
})
|
||||||
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
|
},
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "image_carousel",
|
funcName: "image_carousel",
|
||||||
|
@ -252,13 +253,40 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ShareButton(Svg.share_ui(), generateShareData)
|
return new ShareButton(Svg.share_svg().SetClass("w-8 h-8"), generateShareData)
|
||||||
} else {
|
} else {
|
||||||
return new FixedUiElement("")
|
return new FixedUiElement("")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{funcName: "canonical",
|
||||||
|
docs: "Converts a short, canonical value into the long, translated text",
|
||||||
|
example: "{canonical(length)} will give 42 metre (in french)",
|
||||||
|
args:[{
|
||||||
|
name:"key",
|
||||||
|
doc: "The key of the tag to give the canonical text for"
|
||||||
|
}],
|
||||||
|
constr: (state, tagSource, args) => {
|
||||||
|
const key = args [0]
|
||||||
|
return new VariableUiElement(
|
||||||
|
tagSource.map(tags => tags[key]).map(value => {
|
||||||
|
if(value === undefined){
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const unit = state.layoutToUse.data.units.filter(unit => unit.isApplicableToKey(key))[0]
|
||||||
|
if(unit === undefined){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unit.asHumanLongValue(value);
|
||||||
|
|
||||||
|
},
|
||||||
|
[ state.layoutToUse])
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
]
|
]
|
||||||
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
|
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
|
||||||
|
|
|
@ -323,6 +323,7 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"tagRenderings": [
|
"tagRenderings": [
|
||||||
"images",
|
"images",
|
||||||
"questions",
|
"questions",
|
||||||
|
@ -371,11 +372,11 @@
|
||||||
"nl": "Hoe lang is deze klimroute (in meters)?"
|
"nl": "Hoe lang is deze klimroute (in meters)?"
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"de": "Diese Route ist {climbing:length} Meter lang",
|
"de": "Diese Route ist {canonical(climbing:length)} lang",
|
||||||
"en": "This route is {climbing:length} meter long",
|
"en": "This route is {canonical(climbing:length)} long",
|
||||||
"nl": "Deze klimroute is {climbing:length} meter lang",
|
"nl": "Deze klimroute is {canonical(climbing:length)} lang",
|
||||||
"ja": "このルート長は、 {climbing:length} メーターです",
|
"ja": "このルート長は、 {canonical(climbing:length)} メーターです",
|
||||||
"nb_NO": "Denne ruten er {climbing:length} meter lang"
|
"nb_NO": "Denne ruten er {canonical(climbing:length)} lang"
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "climbing:length",
|
"key": "climbing:length",
|
||||||
|
@ -827,10 +828,17 @@
|
||||||
"canonicalDenomination": "m",
|
"canonicalDenomination": "m",
|
||||||
"alternativeDenomination": ["meter","meters"],
|
"alternativeDenomination": ["meter","meters"],
|
||||||
"human": {
|
"human": {
|
||||||
"en": "meter",
|
"en": " meter",
|
||||||
"nl": "meter"
|
"nl": " meter"
|
||||||
},
|
},
|
||||||
"default": true
|
"default": true
|
||||||
|
},{
|
||||||
|
"canonicalDenomination": "ft",
|
||||||
|
"alternativeDenomination": ["feet","voet"],
|
||||||
|
"human": {
|
||||||
|
"en": " feet",
|
||||||
|
"nl": " voet"
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -955,10 +963,10 @@
|
||||||
{
|
{
|
||||||
"#": "Avg length?",
|
"#": "Avg length?",
|
||||||
"render": {
|
"render": {
|
||||||
"de": "Die Routen sind durchschnittlich <b>{climbing:length}m</b> lang",
|
"de": "Die Routen sind durchschnittlich <b>{canonical(climbing:length)}</b> lang",
|
||||||
"en": "The routes are <b>{climbing:length}m</b> long on average",
|
"en": "The routes are <b>{canonical(climbing:length)}</b> long on average",
|
||||||
"nl": "De klimroutes zijn gemiddeld <b>{climbing:length}m</b> lang",
|
"nl": "De klimroutes zijn gemiddeld <b>{canonical(climbing:length)}</b> lang",
|
||||||
"ja": "ルートの長さは平均で<b>{climbing:length} m</b>です"
|
"ja": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です"
|
||||||
},
|
},
|
||||||
"condition": {
|
"condition": {
|
||||||
"and": [
|
"and": [
|
||||||
|
@ -1321,12 +1329,28 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"overrideAll": {
|
"overrideAll": {
|
||||||
|
"titleIcons": [
|
||||||
|
{
|
||||||
|
"render": "<div style='display:block ruby;' class='m-1 '><img src='./assets/themes/climbing/height.svg' style='width:2rem; height:2rem'/>{climbing:length}</div>",
|
||||||
|
"freeform": {
|
||||||
|
"key": "climbing:length"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"render": "<div style='display:block ruby;' class='m-1 '><img src='./assets/themes/climbing/carabiner.svg' style='width:2rem; height:2rem'/>{climbing:bolted}</div>",
|
||||||
|
"freeform": {
|
||||||
|
"key": "climbing:bolted"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"defaults"],
|
||||||
"+calculatedTags": [
|
"+calculatedTags": [
|
||||||
"_embedding_feature_properties=feat.overlapWith('climbing').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
|
"_embedding_feature_properties=feat.overlapWith('climbing').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
|
||||||
"_embedding_features_with_access=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.access !== undefined)[0]",
|
"_embedding_features_with_access=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.access !== undefined)[0]",
|
||||||
"_embedding_feature_with_rock=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.rock !== undefined)[0]",
|
"_embedding_feature_with_rock=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.rock !== undefined)[0] ?? '{}'",
|
||||||
"_embedding_features_with_rock:rock=JSON.parse(_embedding_feature_with_rock).rock",
|
"_embedding_features_with_rock:rock=JSON.parse(_embedding_feature_with_rock)?.rock",
|
||||||
"_embedding_features_with_rock:id=JSON.parse(_embedding_feature_with_rock).id",
|
"_embedding_features_with_rock:id=JSON.parse(_embedding_feature_with_rock)?.id",
|
||||||
"_embedding_feature:access=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').access",
|
"_embedding_feature:access=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').access",
|
||||||
"_embedding_feature:access:description=JSON.parse(feat.properties._embedding_features_with_access ?? '{}')['access:description']",
|
"_embedding_feature:access:description=JSON.parse(feat.properties._embedding_features_with_access ?? '{}')['access:description']",
|
||||||
"_embedding_feature:id=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').id"
|
"_embedding_feature:id=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').id"
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Matthew Dera"
|
||||||
|
],
|
||||||
|
"path": "carabiner.svg",
|
||||||
|
"license": "CC-BY-SA 4.0",
|
||||||
|
"sources": [
|
||||||
|
"https://thenounproject.com/term/carabiner/30076/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Pieter Vander Vennet"
|
||||||
|
],
|
||||||
|
"path": "height.svg",
|
||||||
|
"license": "CC0",
|
||||||
|
"sources": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"authors": [
|
"authors": [
|
||||||
"Polarbear w",
|
"Polarbear w",
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
|
||||||
import {Layer} from "leaflet";
|
import {Layer} from "leaflet";
|
||||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
||||||
import SmallLicense from "../Models/smallLicense";
|
import SmallLicense from "../Models/smallLicense";
|
||||||
|
import AllKnownLayers from "../Customizations/AllKnownLayers";
|
||||||
|
|
||||||
if(process.argv.length == 2){
|
if(process.argv.length == 2){
|
||||||
console.log("USAGE: ts-node scripts/fixTheme <path to theme>")
|
console.log("USAGE: ts-node scripts/fixTheme <path to theme>")
|
||||||
|
@ -37,7 +38,7 @@ for (const layerConfigJson of themeConfigJson.layers) {
|
||||||
layerConfigJson["source"] = { osmTags : tags}
|
layerConfigJson["source"] = { osmTags : tags}
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const layerConfig = new LayerConfig(layerConfigJson, true)
|
const layerConfig = new LayerConfig(layerConfigJson, AllKnownLayers.sharedUnits, "fix theme",true)
|
||||||
const images : string[] = Array.from(layerConfig.ExtractImages())
|
const images : string[] = Array.from(layerConfig.ExtractImages())
|
||||||
const remoteImages = images.filter(img => img.startsWith("http"))
|
const remoteImages = images.filter(img => img.startsWith("http"))
|
||||||
for (const remoteImage of remoteImages) {
|
for (const remoteImage of remoteImages) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import LayoutConfig from "../Customizations/JSON/LayoutConfig";
|
||||||
import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
|
import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
|
||||||
import {Translation} from "../UI/i18n/Translation";
|
import {Translation} from "../UI/i18n/Translation";
|
||||||
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
|
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
|
||||||
|
import AllKnownLayers from "../Customizations/AllKnownLayers";
|
||||||
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
|
// 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
|
// It spits out an overview of those to be used to load them
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class LayerOverviewUtils {
|
||||||
errorCount.push("Layer " + layerJson.id + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)")
|
errorCount.push("Layer " + layerJson.id + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)")
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const layer = new LayerConfig(layerJson, "test", true)
|
const layer = new LayerConfig(layerJson, AllKnownLayers.sharedUnits,"test", true)
|
||||||
const images = Array.from(layer.ExtractImages())
|
const images = Array.from(layer.ExtractImages())
|
||||||
const remoteImages = images.filter(img => img.indexOf("http") == 0)
|
const remoteImages = images.filter(img => img.indexOf("http") == 0)
|
||||||
for (const remoteImage of remoteImages) {
|
for (const remoteImage of remoteImages) {
|
||||||
|
@ -153,7 +154,7 @@ class LayerOverviewUtils {
|
||||||
for (const layerFile of layerFiles) {
|
for (const layerFile of layerFiles) {
|
||||||
|
|
||||||
layerErrorCount.push(...this.validateLayer(layerFile.parsed, layerFile.path, knownPaths))
|
layerErrorCount.push(...this.validateLayer(layerFile.parsed, layerFile.path, knownPaths))
|
||||||
knownLayerIds.set(layerFile.parsed.id, new LayerConfig(layerFile.parsed))
|
knownLayerIds.set(layerFile.parsed.id, new LayerConfig(layerFile.parsed,AllKnownLayers.sharedUnits))
|
||||||
}
|
}
|
||||||
|
|
||||||
let themeErrorCount = []
|
let themeErrorCount = []
|
||||||
|
|
3
test.ts
3
test.ts
|
@ -50,7 +50,8 @@ function TestTagRendering(){
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
}, undefined, "test")
|
}, undefined, "test"),
|
||||||
|
[]
|
||||||
).AttachTo("maindiv")
|
).AttachTo("maindiv")
|
||||||
new VariableUiElement(tagsSource.map(tags => tags["valves"])).SetClass("alert").AttachTo("extradiv")
|
new VariableUiElement(tagsSource.map(tags => tags["valves"])).SetClass("alert").AttachTo("extradiv")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import T from "./TestHelper";
|
import T from "./TestHelper";
|
||||||
import {Unit} from "../Customizations/JSON/Unit";
|
import {Denomination} from "../Customizations/JSON/Denomination";
|
||||||
import {equal} from "assert";
|
import {equal} from "assert";
|
||||||
|
|
||||||
export default class UnitsSpec extends T {
|
export default class UnitsSpec extends T {
|
||||||
|
@ -8,7 +8,7 @@ export default class UnitsSpec extends T {
|
||||||
super("Units", [
|
super("Units", [
|
||||||
["Simple canonicalize", () => {
|
["Simple canonicalize", () => {
|
||||||
|
|
||||||
const unit = new Unit({
|
const unit = new Denomination({
|
||||||
canonicalDenomination: "m",
|
canonicalDenomination: "m",
|
||||||
alternativeDenomination: ["meter"],
|
alternativeDenomination: ["meter"],
|
||||||
'default': true,
|
'default': true,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue