More work on refactoring the changes handling

This commit is contained in:
Pieter Vander Vennet 2021-07-18 14:52:09 +02:00
parent 42391b4ff1
commit b55f9a25c6
19 changed files with 181 additions and 105 deletions

View file

@ -7,7 +7,7 @@ export default class ChangeToElementsActor {
for (const change of changes) {
const id = change.type + "/" + change.id;
if (!allElements.has(id)) {
continue; // Will be picked up later on
continue; // Ignored as the geometryFixer will introduce this
}
const src = allElements.getEventSourceById(id)

View file

@ -13,7 +13,7 @@ export default class SelectedFeatureHandler {
private readonly _hash: UIEventSource<string>;
private readonly _selectedFeature: UIEventSource<any>;
private static readonly _no_trigger_on = ["welcome","copyright","layers"]
private static readonly _no_trigger_on = ["welcome","copyright","layers","new"]
private readonly _osmApiSource: OsmApiFeatureSource;
constructor(hash: UIEventSource<string>,
@ -60,7 +60,9 @@ export default class SelectedFeatureHandler {
if(hash === undefined || SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){
return; // No valid feature selected
}
// We should have a valid osm-ID and zoom to it
// We should have a valid osm-ID and zoom to it... But we wrap it in try-catch to be sure
try{
OsmObject.DownloadObject(hash).addCallbackAndRunD(element => {
const centerpoint = element.centerpoint();
console.log("Zooming to location for select point: ", centerpoint)
@ -68,6 +70,9 @@ export default class SelectedFeatureHandler {
location.data.lon = centerpoint[1]
location.ping();
})
}catch(e){
console.error("Could not download OSM-object with id", hash, " - probably a weird hash")
}
}
private downloadFeature(hash: string){

View file

@ -5,6 +5,7 @@ import {ChangeDescription} from "../Osm/Actions/ChangeDescription";
import {Utils} from "../../Utils";
import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject";
/**
* Applies changes from 'Changes' onto a featureSource
*/
@ -16,25 +17,41 @@ export default class ChangeApplicator implements FeatureSource {
this.name = "ChangesApplied(" + source.name + ")"
this.features = source.features
const seenChanges = new Set<ChangeDescription>();
const self = this;
let runningUpdate = false;
source.features.addCallbackAndRunD(features => {
if(runningUpdate){
return; // No need to ping again
}
ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data)
seenChanges.clear()
})
changes.pendingChanges.addCallbackAndRunD(changes => {
ChangeApplicator.ApplyChanges(source.features.data, changes)
runningUpdate = true;
changes = changes.filter(ch => !seenChanges.has(ch))
changes.forEach(c => seenChanges.add(c))
console.log("Called back", changes)
ChangeApplicator.ApplyChanges(self.features.data, changes)
source.features.ping()
runningUpdate = false;
})
}
private static ApplyChanges(features: { feature: any, freshness: Date }[], cs: ChangeDescription[]) {
if (cs.length === 0 || features === undefined) {
return features;
/**
* Returns true if the geometry is changed and the source should be pinged
*/
private static ApplyChanges(features: {feature: any, freshness: Date}[], cs: ChangeDescription[]): boolean {
if (cs.length === 0 || features === undefined ) {
return ;
}
console.log("Applying changes ", this.name, cs)
let geometryChanged = false;
const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>()
for (const c of cs) {
const id = c.type + "/" + c.id
@ -52,6 +69,8 @@ export default class ChangeApplicator implements FeatureSource {
feature: feature,
freshness: now
})
console.log("Added a new feature: ", feature)
geometryChanged = true;
}
// First, create the new features - they have a negative ID
@ -61,7 +80,11 @@ export default class ChangeApplicator implements FeatureSource {
if (change.id >= 0) {
return; // Nothing to do here, already created
}
if(change.changes === undefined){
// An update to the object - not the actual created
return;
}
try {
@ -93,8 +116,8 @@ export default class ChangeApplicator implements FeatureSource {
for (const feature of features) {
const id = feature.feature.properties.id;
const f = feature.feature;
const id = f.properties.id;
if (!changesPerId.has(id)) {
continue;
}
@ -118,11 +141,12 @@ export default class ChangeApplicator implements FeatureSource {
// Apply other changes to the object
if (change.changes !== undefined) {
geometryChanged = true;
switch (change.type) {
case "node":
// @ts-ignore
const coor: { lat, lon } = change.changes;
f.geometry.coordinates = [[coor.lon, coor.lat]]
f.geometry.coordinates = [coor.lon, coor.lat]
break;
case "way":
f.geometry.coordinates = change.changes["locations"]
@ -134,5 +158,6 @@ export default class ChangeApplicator implements FeatureSource {
}
}
}
return geometryChanged
}
}

View file

@ -15,15 +15,19 @@ export default class OsmApiFeatureSource implements FeatureSource {
public load(id: string) {
if(id.indexOf("-") >= 0){
if (id.indexOf("-") >= 0) {
// Newly added point - not yet in OSM
return;
}
console.debug("Downloading", id, "from the OSM-API")
OsmObject.DownloadObject(id).addCallbackAndRunD(element => {
const geojson = element.asGeoJson();
geojson.id = geojson.properties.id;
this.features.setData([{feature: geojson, freshness: element.timestamp}])
try {
const geojson = element.asGeoJson();
geojson.id = geojson.properties.id;
this.features.setData([{feature: geojson, freshness: element.timestamp}])
} catch (e) {
console.error(e)
}
})
}
@ -58,7 +62,7 @@ export default class OsmApiFeatureSource implements FeatureSource {
const bounds = Utils.tile_bounds(z, x, y);
console.log("Loading OSM data tile", z, x, y, " with bounds", bounds)
OsmObject.LoadArea(bounds, objects => {
const keptGeoJson: {feature:any, freshness: Date}[] = []
const keptGeoJson: { feature: any, freshness: Date }[] = []
// Which layer does the object match?
for (const object of objects) {
@ -69,7 +73,7 @@ export default class OsmApiFeatureSource implements FeatureSource {
if (doesMatch) {
const geoJson = object.asGeoJson();
geoJson._matching_layer_id = layer.id
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
break;
}

View file

@ -37,7 +37,7 @@ export default class ChangeTagAction extends OsmChangeAction {
return {k: key.trim(), v: value.trim()};
}
Perform(changes: Changes): ChangeDescription [] {
CreateChangeDescriptions(changes: Changes): ChangeDescription [] {
const changedTags: { k: string, v: string }[] = this._tagsFilter.asChange(this._currentTags).map(ChangeTagAction.checkChange)
const typeId = this._elementId.split("/")
const type = typeId[0]

View file

@ -4,23 +4,27 @@ import {Changes} from "../Changes";
import {ChangeDescription} from "./ChangeDescription";
import {And} from "../../Tags/And";
export default class CreateNewNodeAction implements OsmChangeAction {
export default class CreateNewNodeAction extends OsmChangeAction {
private readonly _basicTags: Tag[];
private readonly _lat: number;
private readonly _lon: number;
public newElementId : string = undefined
constructor(basicTags: Tag[], lat: number, lon: number) {
super()
this._basicTags = basicTags;
this._lat = lat;
this._lon = lon;
}
Perform(changes: Changes): ChangeDescription[] {
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
const id = changes.getNewID()
const properties = {
id: "node/" + id
}
this.newElementId = "node/"+id
for (const kv of this._basicTags) {
if (typeof kv.value !== "string") {
throw "Invalid value: don't use a regex in a preset"

View file

@ -7,10 +7,17 @@ import {ChangeDescription} from "./ChangeDescription";
export default abstract class OsmChangeAction {
private isUsed = false
public Perform(changes: Changes) {
if (this.isUsed) {
throw "This ChangeAction is already used: " + this.constructor.name
}
this.isUsed = true;
return this.CreateChangeDescriptions(changes)
}
protected abstract CreateChangeDescriptions(changes: Changes): ChangeDescription[]
public abstract Perform(changes: Changes): ChangeDescription[]
}

View file

@ -12,7 +12,7 @@ export default class RelationSplitlHandler extends OsmChangeAction{
super()
}
Perform(changes: Changes): ChangeDescription[] {
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
return [];
}

View file

@ -42,7 +42,7 @@ export default class SplitAction extends OsmChangeAction {
return wayParts.filter(wp => wp.length > 0)
}
Perform(changes: Changes): ChangeDescription[] {
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
const splitPoints = this._splitPoints
// We mark the new split points with a new id
console.log(splitPoints)
@ -72,7 +72,6 @@ export default class SplitAction extends OsmChangeAction {
// Next up is creating actual parts from this
const wayParts: SplitInfo[][] = SplitAction.SegmentSplitInfo(splitInfo);
console.log("WayParts", wayParts, "by", splitInfo)
// Allright! At this point, we have our new ways!
// Which one is the longest of them (and can keep the id)?
@ -144,7 +143,7 @@ console.log("WayParts", wayParts, "by", splitInfo)
// At last, we still have to check that we aren't part of a relation...
// At least, the order of the ways is identical, so we can keep the same roles
changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).Perform(changes))
changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).CreateChangeDescriptions(changes))
// And we have our objects!
// Time to upload

View file

@ -4,7 +4,6 @@ import {UIEventSource} from "../UIEventSource";
import Constants from "../../Models/Constants";
import OsmChangeAction from "./Actions/OsmChangeAction";
import {ChangeDescription} from "./Actions/ChangeDescription";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import {Utils} from "../../Utils";
/**
@ -23,25 +22,23 @@ export class Changes {
public readonly pendingChanges = new UIEventSource<ChangeDescription[]>([]) // LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
private readonly isUploading = new UIEventSource(false);
private readonly previouslyCreated : OsmObject[] = []
constructor() {
this.isUploading.addCallbackAndRun(u => {
if (u) {
console.trace("Uploading set!")
}
})
}
public static createChangesetFor(csId: string,
private static createChangesetFor(csId: string,
allChanges: {
modifiedObjects?: OsmObject[],
newElements?: OsmObject[],
deletedElements?: OsmObject[]
modifiedObjects: OsmObject[],
newObjects: OsmObject[],
deletedObjects: OsmObject[]
}): string {
const changedElements = allChanges.modifiedObjects ?? []
const newElements = allChanges.newElements ?? []
const deletedElements = allChanges.deletedElements ?? []
const newElements = allChanges.newObjects ?? []
const deletedElements = allChanges.deletedObjects ?? []
let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`;
if (newElements.length > 0) {
@ -73,7 +70,7 @@ export class Changes {
.map(c => c.type + "/" + c.id))
}
private static CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): {
private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): {
newObjects: OsmObject[],
modifiedObjects: OsmObject[]
deletedObjects: OsmObject[]
@ -87,12 +84,21 @@ export class Changes {
states.set(o.type + "/" + o.id, "unchanged")
}
for (const o of this.previouslyCreated) {
objects.set(o.type + "/" + o.id, o)
states.set(o.type + "/" + o.id, "unchanged")
}
let changed = false;
for (const change of changes) {
const id = change.type + "/" + change.id
if (!objects.has(id)) {
if(change.id >= 0){
throw "Did not get an object that should be known: "+id
}
// This is a new object that should be created
states.set(id, "created")
console.log("Creating object for changeDescription", change)
let osmObj: OsmObject = undefined;
switch (change.type) {
case "node":
@ -116,6 +122,7 @@ export class Changes {
throw "Hmm? This is a bug"
}
objects.set(id, osmObj)
this.previouslyCreated.push(osmObj)
}
const state = states.get(id)
@ -195,8 +202,8 @@ export class Changes {
newObjects: [],
modifiedObjects: [],
deletedObjects: []
}
objects.forEach((v, id) => {
const state = states.get(id)
@ -228,20 +235,18 @@ export class Changes {
*/
public flushChanges(flushreason: string = undefined) {
if (this.pendingChanges.data.length === 0) {
console.log("No pending changes")
return;
}
if (flushreason !== undefined) {
console.log(flushreason)
}
if (this.isUploading.data) {
console.log("Is uploading... Abort")
console.log("Is already uploading... Abort")
return;
}
this.isUploading.setData(true)
console.log("Beginning upload...");
console.log("Beginning upload... "+flushreason ?? "");
// At last, we build the changeset and upload
const self = this;
const pending = self.pendingChanges.data;
@ -249,8 +254,12 @@ export class Changes {
console.log("Needed ids", neededIds)
OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => {
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
const changes = Changes.CreateChangesetObjects(pending, osmObjects)
console.log("Changes", changes)
const changes: {
newObjects: OsmObject[],
modifiedObjects: OsmObject[]
deletedObjects: OsmObject[]
} = self.CreateChangesetObjects(pending, osmObjects)
if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) {
console.log("No changes to be made")
this.pendingChanges.setData([])
@ -262,11 +271,8 @@ export class Changes {
State.state.osmConnection.UploadChangeset(
State.state.layoutToUse.data,
State.state.allElements,
(csId) => {
return Changes.createChangesetFor(csId, changes);
},
(csId) => Changes.createChangesetFor(csId, changes),
() => {
// When done
console.log("Upload successfull!")
self.pendingChanges.setData([]);
self.isUploading.setData(false)

View file

@ -23,7 +23,7 @@ export abstract class OsmObject {
this.id = id;
this.type = type;
this.tags = {
id: id
id: `${this.type}/${id}`
}
}
@ -52,6 +52,9 @@ export abstract class OsmObject {
const splitted = id.split("/");
const type = splitted[0];
const idN = Number(splitted[1]);
if(idN <0){
return;
}
OsmObject.objectCache.set(id, src);
const newContinuation = (element: OsmObject) => {
@ -69,7 +72,7 @@ export abstract class OsmObject {
new OsmRelation(idN).Download(newContinuation);
break;
default:
throw "Invalid road type:" + type;
throw "Invalid object type:" + type + id;
}
return src;
@ -105,7 +108,7 @@ export abstract class OsmObject {
if (OsmObject.referencingRelationsCache.has(id)) {
return OsmObject.referencingRelationsCache.get(id);
}
const relsSrc = new UIEventSource<OsmRelation[]>([])
const relsSrc = new UIEventSource<OsmRelation[]>(undefined)
OsmObject.referencingRelationsCache.set(id, relsSrc);
Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`)
.then(data => {

View file

@ -6,7 +6,7 @@ export class UIEventSource<T> {
public data: T;
public trace: boolean;
private readonly tag: string;
private _callbacks = [];
private _callbacks: ((t: T) => (boolean | void | any)) [] = [];
constructor(data: T, tag: string = "") {
this.tag = tag;
@ -31,7 +31,7 @@ export class UIEventSource<T> {
}
return [];
}
public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> {
const sink = new UIEventSource<X>(source.data?.data);
@ -63,7 +63,13 @@ export class UIEventSource<T> {
}
public addCallback(callback: ((latestData: T) => void)): UIEventSource<T> {
/**
* Adds a callback
*
* If the result of the callback is 'true', the callback is considered finished and will be removed again
* @param callback
*/
public addCallback(callback: ((latestData: T) => (boolean | void | any))): UIEventSource<T> {
if (callback === console.log) {
// This ^^^ actually works!
throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead."
@ -90,8 +96,21 @@ export class UIEventSource<T> {
}
public ping(): void {
let toDelete = undefined
for (const callback of this._callbacks) {
callback(this.data);
if (callback(this.data) === true) {
// This callback wants to be deleted
if (toDelete === undefined) {
toDelete = [callback]
} else {
toDelete.push(callback)
}
}
}
if (toDelete !== undefined) {
for (const toDeleteElement of toDelete) {
this._callbacks.splice(this._callbacks.indexOf(toDeleteElement), 1)
}
}
}

View file

@ -20,7 +20,7 @@ import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader";
import {Relation} from "./Logic/Osm/ExtractRelations";
import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource";
import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
import FeatureSource from "./Logic/FeatureSource/FeatureSource";
/**
* Contains the global state: a bunch of UI-event sources
@ -32,7 +32,7 @@ export default class State {
public static state: State;
public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined);
public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined, "layoutToUse");
/**
The mapping from id -> UIEventSource<properties>
@ -45,7 +45,7 @@ export default class State {
/**
The leaflet instance of the big basemap
*/
public leafletMap = new UIEventSource<L.Map>(undefined);
public leafletMap = new UIEventSource<L.Map>(undefined, "leafletmap");
/**
* Background layer id
*/
@ -70,7 +70,7 @@ export default class State {
}[]> = new UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]>([])
}[]>([], "filteredLayers")
/**
@ -101,13 +101,13 @@ export default class State {
public readonly featureSwitchFakeUser: UIEventSource<boolean>;
public readonly featurePipeline: FeaturePipeline;
public featurePipeline: FeatureSource;
/**
* The map location: currently centered lat, lon and zoom
*/
public readonly locationControl = new UIEventSource<Loc>(undefined);
public readonly locationControl = new UIEventSource<Loc>(undefined, "locationControl");
public backgroundLayer;
public readonly backgroundLayerId: UIEventSource<string>;
@ -149,11 +149,13 @@ export default class State {
.syncWith(LocalStorageSource.Get("lon")));
this.locationControl = new UIEventSource<Loc>({
this.locationControl.setData({
zoom: Utils.asFloat(zoom.data),
lat: Utils.asFloat(lat.data),
lon: Utils.asFloat(lon.data),
}).addCallback((latlonz) => {
})
this.locationControl.addCallback((latlonz) => {
// Sync th location controls
zoom.setData(latlonz.zoom);
lat.setData(latlonz.lat);
lon.setData(latlonz.lon);

View file

@ -21,6 +21,7 @@ import {InputElement} from "../Input/InputElement";
import Loc from "../../Models/Loc";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
import Hash from "../../Logic/Web/Hash";
/*
* The SimpleAddUI is a single panel, which can have multiple states:
@ -71,10 +72,11 @@ export default class SimpleAddUI extends Toggle {
}
return SimpleAddUI.CreateConfirmButton(preset,
(tags, location) => {
let changes =
State.state.changes.applyAction(new CreateNewNodeAction(tags, location.lat, location.lon))
State.state.selectedElement.setData(changes.newFeatures[0]);
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon)
State.state.changes.applyAction(newElementAction)
selectedPreset.setData(undefined)
isShown.setData(false)
Hash.hash.setData(newElementAction.newElementId)
}, () => {
selectedPreset.setData(undefined)
})
@ -119,16 +121,16 @@ export default class SimpleAddUI extends Toggle {
lon: location.data.lon,
zoom: 19
});
let backgroundLayer = undefined;
if(preset.preciseInput.preferredBackground){
backgroundLayer= AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground))
if (preset.preciseInput.preferredBackground) {
backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground))
}
preciseInput = new LocationInput({
mapBackground: backgroundLayer,
centerLocation:locationSrc
centerLocation: locationSrc
})
preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;")
}
@ -143,7 +145,7 @@ export default class SimpleAddUI extends Toggle {
.onClick(() => {
confirm(preset.tags, (preciseInput?.GetValue() ?? location).data);
});
if (preciseInput !== undefined) {
confirmButton = new Combine([preciseInput, confirmButton])
}
@ -239,7 +241,7 @@ export default class SimpleAddUI extends Toggle {
for (const preset of presets) {
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
let icon:() => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html
let icon: () => BaseUIElement = () => layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html
.SetClass("w-12 h-12 block relative");
const presetInfo: PresetInfo = {
tags: preset.tags,

View file

@ -92,18 +92,24 @@ export default class SplitRoadWizard extends Toggle {
// Save button
const saveButton = new Button(t.split.Clone(), () => {
hasBeenSplit.setData(true)
OsmObject.DownloadObject(id).addCallbackAndRunD(way => {
OsmObject.DownloadReferencingRelations(id).addCallbackAndRunD(
partOf => {
const splitAction = new SplitAction(
<OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature)
)
State.state.changes.applyAction(splitAction)
}
)
const way = OsmObject.DownloadObject(id)
const partOfSrc = OsmObject.DownloadReferencingRelations(id);
let hasRun = false
way.map(way => {
const partOf = partOfSrc.data
if(way === undefined || partOf === undefined){
return;
}
)
if(hasRun){
return
}
hasRun = true
const splitAction = new SplitAction(
<OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature)
)
State.state.changes.applyAction(splitAction)
}, [partOfSrc])
});
@ -123,7 +129,7 @@ export default class SplitRoadWizard extends Toggle {
const splitTitle = new Title(t.splitTitle);
const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle])]);
const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle]).SetClass("flex flex-row")]);
mapView.SetClass("question")
const confirm = new Toggle(mapView, splitToggle, splitClicked);
super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit)

View file

@ -61,7 +61,6 @@ export default class ShowDataLayer {
}
const allFeats = features.data.map(ff => ff.feature);
console.log("Rendering ",allFeats, "features at layer ", name)
geoLayer = self.CreateGeojsonLayer();
for (const feat of allFeats) {
if (feat === undefined) {
@ -87,6 +86,7 @@ export default class ShowDataLayer {
console.error(e)
}
}
State.state.selectedElement.ping()
}
features.addCallback(() => update());

View file

@ -19,7 +19,6 @@ export class SubstitutedTranslation extends VariableUiElement {
const extraMappings: SpecialVisualization[] = [];
mapping?.forEach((value, key) => {
console.log("KV:", key, value)
extraMappings.push(
{
funcName: key,
@ -73,11 +72,6 @@ export class SubstitutedTranslation extends VariableUiElement {
}
}[] {
if (extraMappings.length > 0) {
console.log("Extra mappings are", extraMappings)
}
for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'

View file

@ -109,9 +109,9 @@ export class Translation extends BaseUIElement {
// @ts-ignore
const date: Date = el;
rtext = date.toLocaleString();
} else if (el.ConstructElement() === undefined) {
console.error("InnerREnder is not defined", el);
throw "Hmmm, el.InnerRender is not defined?"
} else if (el.ConstructElement === undefined) {
console.error("ConstructElement is not defined", el);
throw "ConstructElement is not defined, you are working with a "+(typeof el)+":"+(el.constructor.name)
} else {
Translation.forcedLanguage = lang; // This is a very dirty hack - it'll bite me one day
rtext = el.ConstructElement().innerHTML;

View file

@ -33,7 +33,7 @@
"startZoom": 14,
"startLon": 3.2228,
"maintainer": "MapComplete",
"widenfactor": 0.05,
"widenfactor": 0.01,
"roamingRenderings": [
{
"question": {