Add binoculars theme, auto reformat everything

This commit is contained in:
Pieter Vander Vennet 2021-09-09 00:05:51 +02:00
parent 38dea806c5
commit 78d6482c88
586 changed files with 115573 additions and 111842 deletions

View file

@ -4,7 +4,6 @@ import Svg from "../../Svg";
import Img from "../../UI/Base/Img";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
import BaseUIElement from "../../UI/BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {QueryParameters} from "../Web/QueryParameters";
@ -161,16 +160,16 @@ export default class GeoLocationHandler extends VariableUiElement {
} else {
lastClick.setData(new Date())
}
}else{
} else {
lastClick.setData(new Date())
}
}
self.init(true, true);
});
const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
this.init(false, !latLonGiven);
isLocked.addCallbackAndRunD(isLocked => {
@ -180,7 +179,7 @@ export default class GeoLocationHandler extends VariableUiElement {
leafletMap.data?.dragging?.enable()
}
})
this._currentGPSLocation.addCallback((location) => {
self._previousLocationGrant.setData("granted");

View file

@ -6,7 +6,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class InstalledThemes {
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
constructor(osmConnection: OsmConnection) {
this.installedThemes = osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => {
const installedThemes: { layout: LayoutConfig, definition: string }[] = [];
@ -25,10 +25,10 @@ export default class InstalledThemes {
}
try {
let layoutJson;
try{
try {
layoutJson = JSON.parse(atob(customLayout.data))
}catch(e){
layoutJson = JSON.parse( Utils.UnMinify(LZString.decompressFromBase64(customLayout.data)))
} catch (e) {
layoutJson = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(customLayout.data)))
}
const layout = new LayoutConfig(layoutJson, false);
installedThemes.push({
@ -43,7 +43,7 @@ export default class InstalledThemes {
}
InstalledThemes.DeleteInvalid(osmConnection, invalidThemes);
return installedThemes;
});

View file

@ -7,13 +7,13 @@ import Loc from "../../Models/Loc";
* Sets the current background layer to a layer that is actually available
*/
export default class LayerResetter {
constructor( currentBackgroundLayer: UIEventSource<BaseLayer>,
location: UIEventSource<Loc>,
availableLayers: UIEventSource<BaseLayer[]>,
defaultLayerId: UIEventSource<string> = undefined) {
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(AvailableBaseLayers.osmCarto.id);
constructor(currentBackgroundLayer: UIEventSource<BaseLayer>,
location: UIEventSource<Loc>,
availableLayers: UIEventSource<BaseLayer[]>,
defaultLayerId: UIEventSource<string> = undefined) {
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(AvailableBaseLayers.osmCarto.id);
// Change the baselayer back to OSM if we go out of the current range of the layer
availableLayers.addCallbackAndRun(availableLayers => {
let defaultLayer = undefined;
@ -28,7 +28,7 @@ export default class LayerResetter {
if (availableLayer.min_zoom > location.data.zoom) {
break;
}
if(availableLayer.id === defaultLayerId.data){
if (availableLayer.id === defaultLayerId.data) {
defaultLayer = availableLayer;
}
return; // All good - the current layer still works!
@ -38,7 +38,7 @@ export default class LayerResetter {
console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard")
currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto);
});
}
}

View file

@ -13,7 +13,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class OverpassFeatureSource implements FeatureSource {
public readonly name = "OverpassFeatureSource"
/**
* The last loaded features of the geojson
*/
@ -147,7 +147,7 @@ export default class OverpassFeatureSource implements FeatureSource {
}
const bounds = this._leafletMap.data?.getBounds();
if(bounds === undefined){
if (bounds === undefined) {
return;
}

View file

@ -54,7 +54,7 @@ export default class PendingChangesUploader {
function onunload(e) {
if(changes.pendingChanges.data.length == 0){
if (changes.pendingChanges.data.length == 0) {
return;
}
changes.flushChanges("onbeforeunload - probably closing or something similar");

View file

@ -3,6 +3,7 @@ Actors
An **actor** is a module which converts one UIEventSource into another while performing logic.
Typically, it will only expose the constructor taking some UIEventSources (and configuration) and a few fields which are UIEVentSources.
Typically, it will only expose the constructor taking some UIEventSources (and configuration) and a few fields which are
UIEVentSources.
An actor should _never_ have a dependency on 'State' and should _never_ import it

View file

@ -9,11 +9,10 @@ import OsmApiFeatureSource from "../FeatureSource/OsmApiFeatureSource";
* Makes sure the hash shows the selected element and vice-versa.
*/
export default class SelectedFeatureHandler {
private static readonly _no_trigger_on = ["welcome", "copyright", "layers", "new"]
private readonly _featureSource: FeatureSource;
private readonly _hash: UIEventSource<string>;
private readonly _selectedFeature: UIEventSource<any>;
private static readonly _no_trigger_on = ["welcome", "copyright", "layers", "new"]
private readonly _osmApiSource: OsmApiFeatureSource;
constructor(hash: UIEventSource<string>,

View file

@ -1,7 +1,5 @@
import * as L from "leaflet";
import Svg from "../../Svg";
import {UIEventSource} from "../UIEventSource";
import Img from "../../UI/Base/Img";
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen";
import AddNewMarker from "../../UI/BigComponents/AddNewMarker";
import FilteredLayer from "../../Models/FilteredLayer";
@ -32,28 +30,28 @@ export default class StrayClickHandler {
})
lastClickLocation.addCallback(function (lastClick) {
if (self._lastMarker !== undefined) {
leafletMap.data?.removeLayer(self._lastMarker);
}
if(lastClick === undefined){
if (lastClick === undefined) {
return;
}
selectedElement.setData(undefined);
const clickCoor : [number, number] = [lastClick.lat, lastClick.lon]
const clickCoor: [number, number] = [lastClick.lat, lastClick.lon]
self._lastMarker = L.marker(clickCoor, {
icon: L.divIcon({
html: new AddNewMarker(filteredLayers).ConstructElement(),
iconSize: [50, 50],
iconAnchor: [25, 50],
popupAnchor: [0, -45]
})
})
});
const popup = L.popup({
autoPan: true,
autoPanPaddingTopLeft: [15,15],
autoPanPaddingTopLeft: [15, 15],
closeOnEscapeKey: true,
autoClose: true
}).setContent("<div id='strayclick' style='height: 65vh'></div>");
@ -61,13 +59,13 @@ export default class StrayClickHandler {
self._lastMarker.bindPopup(popup);
self._lastMarker.on("click", () => {
if(leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints){
if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) {
self._lastMarker.closePopup()
leafletMap.data.flyTo(clickCoor, Constants.userJourney.minZoomLevelToAddNewPoints)
return;
}
uiToShow.AttachTo("strayclick")
uiToShow.Activate();
});

View file

@ -7,7 +7,7 @@ import Combine from "../../UI/Base/Combine";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
class TitleElement extends UIEventSource<string> {
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
private readonly _selectedFeature: UIEventSource<any>;
private readonly _allElementsStorage: ElementStorage;
@ -16,17 +16,17 @@ class TitleElement extends UIEventSource<string> {
selectedFeature: UIEventSource<any>,
allElementsStorage: ElementStorage) {
super("MapComplete");
this._layoutToUse = layoutToUse;
this._selectedFeature = selectedFeature;
this._allElementsStorage = allElementsStorage;
this.syncWith(
this._selectedFeature.map(
selected => {
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ??"MapComplete"
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ?? "MapComplete"
if(selected === undefined){
if (selected === undefined) {
return defaultTitle
}
@ -49,10 +49,9 @@ class TitleElement extends UIEventSource<string> {
}
, [Locale.language, layoutToUse]
)
)
}
}

View file

@ -5,8 +5,8 @@ import {UIEventSource} from "./UIEventSource";
export class ElementStorage {
private _elements = new Map<string, UIEventSource<any>>();
public ContainingFeatures = new Map<string, any>();
private _elements = new Map<string, UIEventSource<any>>();
constructor() {
@ -25,16 +25,16 @@ export class ElementStorage {
addOrGetElement(feature: any): UIEventSource<any> {
const elementId = feature.properties.id;
const newProperties = feature.properties;
const es = this.addOrGetById(elementId, newProperties)
// At last, we overwrite the tag of the new feature to use the tags in the already existing event source
feature.properties = es.data
if(!this.ContainingFeatures.has(elementId)){
if (!this.ContainingFeatures.has(elementId)) {
this.ContainingFeatures.set(elementId, feature);
}
return es;
}
@ -69,7 +69,7 @@ export class ElementStorage {
const debug_msg = []
let somethingChanged = false;
for (const k in newProperties) {
if(!newProperties.hasOwnProperty(k)){
if (!newProperties.hasOwnProperty(k)) {
continue;
}
const v = newProperties[k];

View file

@ -14,13 +14,13 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
public readonly name;
constructor(layers: UIEventSource<FilteredLayer[]>, upstream: FeatureSource) {
this.name = "FeatureDuplicator of "+upstream.name;
this.name = "FeatureDuplicator of " + upstream.name;
this.features = upstream.features.map(features => {
const newFeatures: { feature: any, freshness: Date }[] = [];
if(features === undefined){
if (features === undefined) {
return newFeatures;
}
for (const f of features) {
if (f.feature._matching_layer_id) {
// Already matched previously
@ -29,7 +29,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
continue;
}
let foundALayer = false;
for (const layer of layers.data) {
if (layer.layerDef.source.osmTags.matchesProperties(f.feature.properties)) {
@ -43,7 +43,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
id: f.feature.id,
type: f.feature.type,
properties: f.feature.properties,
_matching_layer_id : layer.layerDef.id
_matching_layer_id: layer.layerDef.id
}
newFeatures.push({feature: newFeature, freshness: f.freshness});
} else {

View file

@ -24,8 +24,8 @@ export class FeatureSourceUtils {
options = Utils.setDefaults(options, defaults);
// Select all features, ignore the freshness and other data
let featureList: any[] = featurePipeline.features.data.map((feature) =>
JSON.parse(JSON.stringify((feature.feature)))); // Make a deep copy!
let featureList: any[] = featurePipeline.features.data.map((feature) =>
JSON.parse(JSON.stringify((feature.feature)))); // Make a deep copy!
if (!options.metadata) {
for (let i = 0; i < featureList.length; i++) {
@ -39,7 +39,7 @@ export class FeatureSourceUtils {
}
return {type: "FeatureCollection", features: featureList}
}

View file

@ -57,12 +57,12 @@ export default class FeatureSourceMerger implements FeatureSource {
}
}
}
if(!somethingChanged){
if (!somethingChanged) {
// We don't bother triggering an update
return;
}
const newList = [];
all.forEach((value, key) => {
newList.push(value)

View file

@ -79,8 +79,8 @@ export default class FilteringFeatureSource implements FeatureSource {
if (result !== "yes") {
return false;
}
}
}
const tagsFilter = layer.appliedFilters.data;
if (tagsFilter) {
if (!tagsFilter.matchesProperties(f.feature.properties)) {
@ -118,7 +118,7 @@ export default class FilteringFeatureSource implements FeatureSource {
if (l.zoom < layer.layerDef.minzoom) {
continue;
}
if (!layer.isDisplayed.data) {
continue;
}

View file

@ -13,10 +13,10 @@ export default class GeoJsonSource implements FeatureSource {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name;
public readonly isOsmCache: boolean
private onFail: ((errorMsg: any, url: string) => void) = undefined;
private readonly layerId: string;
private readonly seenids: Set<string> = new Set<string>()
public readonly isOsmCache: boolean
private constructor(locationControl: UIEventSource<Loc>,
flayer: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig },

View file

@ -12,23 +12,23 @@ export default class LocalStorageSaver implements FeatureSource {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name = "LocalStorageSaver";
constructor(source: FeatureSource, layout: UIEventSource<LayoutConfig>) {
this.features = source.features;
this.features.addCallbackAndRunD(features => {
const now = new Date().getTime()
features = features.filter(f => layout.data.cacheTimeout > Math.abs(now - f.freshness.getTime())/1000)
if(features.length == 0){
features = features.filter(f => layout.data.cacheTimeout > Math.abs(now - f.freshness.getTime()) / 1000)
if (features.length == 0) {
return;
}
try {
const key = LocalStorageSaver.storageKey+layout.data.id
const key = LocalStorageSaver.storageKey + layout.data.id
localStorage.setItem(key, JSON.stringify(features));
console.log("Saved ",features.length, "elements to",key)
console.log("Saved ", features.length, "elements to", key)
} catch (e) {
console.warn("Could not save the features to local storage:", e)
}

View file

@ -16,14 +16,14 @@ export default class LocalStorageSource implements FeatureSource {
if (fromStorage == null) {
return;
}
const loaded : { feature: any; freshness: Date | string }[]=
const loaded: { feature: any; freshness: Date | string }[] =
JSON.parse(fromStorage);
const parsed : { feature: any; freshness: Date }[]= loaded.map(ff => ({
const parsed: { feature: any; freshness: Date }[] = loaded.map(ff => ({
feature: ff.feature,
freshness : typeof ff.freshness == "string" ? new Date(ff.freshness) : ff.freshness
freshness: typeof ff.freshness == "string" ? new Date(ff.freshness) : ff.freshness
}))
this.features.setData(parsed);
console.log("Loaded ", loaded.length, " features from localstorage as cache")
} catch (e) {

View file

@ -19,10 +19,10 @@ export default class MetaTaggingFeatureSource implements FeatureSource {
const self = this;
this.name = "MetaTagging of " + source.name
if(allFeaturesSource === undefined){
if (allFeaturesSource === undefined) {
throw ("UIEVentSource is undefined")
}
function update() {
const featuresFreshness = source.features.data
if (featuresFreshness === undefined) {

View file

@ -8,17 +8,17 @@ export default class RememberingSource implements FeatureSource {
public readonly features: UIEventSource<{ feature: any, freshness: Date }[]>;
public readonly name;
constructor(source: FeatureSource) {
const self = this;
this.name = "RememberingSource of "+source.name;
this.name = "RememberingSource of " + source.name;
const empty = [];
this.features = source.features.map(features => {
const oldFeatures = self.features?.data ?? empty;
if (features === undefined) {
return oldFeatures;
}
// Then new ids
const ids = new Set<string>(features.map(f => f.feature.properties.id + f.feature.geometry.type + f.feature._matching_layer_id));
// the old data

View file

@ -185,6 +185,60 @@ export class GeoOperations {
return turf.length(feature) * 1000
}
/**
* Generates the closest point on a way from a given point
* @param way The road on which you want to find a point
* @param point Point defined as [lon, lat]
*/
public static nearestPoint(way, point: [number, number]) {
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
}
public static toCSV(features: any[]): string {
const headerValuesSeen = new Set<string>();
const headerValuesOrdered: string[] = []
function addH(key) {
if (!headerValuesSeen.has(key)) {
headerValuesSeen.add(key)
headerValuesOrdered.push(key)
}
}
addH("_lat")
addH("_lon")
const lines: string[] = []
for (const feature of features) {
const properties = feature.properties;
for (const key in properties) {
if (!properties.hasOwnProperty(key)) {
continue;
}
addH(key)
}
}
headerValuesOrdered.sort()
for (const feature of features) {
const properties = feature.properties;
let line = ""
for (const key of headerValuesOrdered) {
const value = properties[key]
if (value === undefined) {
line += ","
} else {
line += JSON.stringify(value) + ","
}
}
lines.push(line)
}
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
}
/**
* Calculates the intersection between two features.
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
@ -277,60 +331,6 @@ export class GeoOperations {
return undefined;
}
/**
* Generates the closest point on a way from a given point
* @param way The road on which you want to find a point
* @param point Point defined as [lon, lat]
*/
public static nearestPoint(way, point: [number, number]) {
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
}
public static toCSV(features: any[]): string {
const headerValuesSeen = new Set<string>();
const headerValuesOrdered: string[] = []
function addH(key) {
if (!headerValuesSeen.has(key)) {
headerValuesSeen.add(key)
headerValuesOrdered.push(key)
}
}
addH("_lat")
addH("_lon")
const lines: string[] = []
for (const feature of features) {
const properties = feature.properties;
for (const key in properties) {
if (!properties.hasOwnProperty(key)) {
continue;
}
addH(key)
}
}
headerValuesOrdered.sort()
for (const feature of features) {
const properties = feature.properties;
let line = ""
for (const key of headerValuesOrdered) {
const value = properties[key]
if (value === undefined) {
line += ","
} else {
line += JSON.stringify(value) + ","
}
}
lines.push(line)
}
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
}
}
@ -364,7 +364,7 @@ export class BBox {
static get(feature) {
if (feature.bbox?.overlapsWith === undefined) {
const turfBbox: number[] = turf.bbox(feature)
feature.bbox = new BBox([[turfBbox[0], turfBbox[1]],[turfBbox[2], turfBbox[3]]]);
feature.bbox = new BBox([[turfBbox[0], turfBbox[1]], [turfBbox[2], turfBbox[3]]]);
}
return feature.bbox;

View file

@ -2,8 +2,8 @@ import {Mapillary} from "./Mapillary";
import {Wikimedia} from "./Wikimedia";
import {Imgur} from "./Imgur";
export default class AllImageProviders{
export default class AllImageProviders {
public static ImageAttributionSource = [Imgur.singleton, Mapillary.singleton, Wikimedia.singleton]
}

View file

@ -18,12 +18,13 @@ export default abstract class ImageAttributionSource {
}
public abstract SourceIcon(backlinkSource?: string) : BaseUIElement;
protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>;
public abstract SourceIcon(backlinkSource?: string): BaseUIElement;
/*Converts a value to a URL. Can return null if not applicable*/
public PrepareUrl(value: string): string{
public PrepareUrl(value: string): string {
return value;
}
protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>;
}

View file

@ -6,8 +6,8 @@ import {UIEventSource} from "../UIEventSource";
import BaseUIElement from "../../UI/BaseUIElement";
export class Imgur extends ImageAttributionSource {
public static readonly singleton = new Imgur();
public static readonly singleton = new Imgur();
private constructor() {
super();

View file

@ -1,4 +1,3 @@
import $ from "jquery"
import {LicenseInfo} from "./Wikimedia";
import ImageAttributionSource from "./ImageAttributionSource";
import BaseUIElement from "../../UI/BaseUIElement";

View file

@ -77,7 +77,7 @@ export class Wikimedia extends ImageAttributionSource {
static GetWikiData(id: number, handleWikidata: ((Wikidata) => void)) {
const url = "https://www.wikidata.org/wiki/Special:EntityData/Q" + id + ".json";
Utils.downloadJson(url).then (response => {
Utils.downloadJson(url).then(response => {
const entity = response.entities["Q" + id];
const commons = entity.sitelinks.commonswiki;
const wd = new Wikidata();
@ -139,10 +139,10 @@ export class Wikimedia extends ImageAttributionSource {
"titles=" + filename +
"&format=json&origin=*";
Utils.downloadJson(url).then(
data =>{
data => {
const licenseInfo = new LicenseInfo();
const license = (data.query.pages[-1].imageinfo ?? [])[0]?.extmetadata;
if(license === undefined){
if (license === undefined) {
console.error("This file has no usable metedata or license attached... Please fix the license info file yourself!")
source.setData(null)
return;
@ -156,10 +156,10 @@ export class Wikimedia extends ImageAttributionSource {
licenseInfo.licenseShortName = license.LicenseShortName?.value;
licenseInfo.credit = license.Credit?.value;
licenseInfo.description = license.ImageDescription?.value;
source.setData(licenseInfo);
source.setData(licenseInfo);
}
)
return source;
}

View file

@ -18,6 +18,9 @@ interface Params {
export default class MetaTagging {
private static errorPrintCount = 0;
private static readonly stopErrorOutputAt = 10;
/**
* An actor which adds metatags on every feature in the given object
* The features are a list of geojson-features, with a "properties"-field and geometry
@ -86,9 +89,6 @@ export default class MetaTagging {
}
private static errorPrintCount = 0;
private static readonly stopErrorOutputAt = 10;
private static createRetaggingFunc(layer: LayerConfig):
((params: Params, feature: any) => void) {
const calculatedTags: [string, string][] = layer.calculatedTags;
@ -111,7 +111,7 @@ export default class MetaTagging {
const f = (featuresPerLayer, feature: any) => {
try {
let result = func(feature);
if(result instanceof UIEventSource){
if (result instanceof UIEventSource) {
result.addCallbackAndRunD(d => {
if (typeof d !== "string") {
// Make sure it is a string!
@ -121,7 +121,7 @@ export default class MetaTagging {
})
result = result.data
}
if (result === undefined || result === "") {
return;
}

View file

@ -8,11 +8,10 @@ import {GeoOperations} from "../../GeoOperations";
export default class CreateNewNodeAction extends OsmChangeAction {
public newElementId: string = undefined
private readonly _basicTags: Tag[];
private readonly _lat: number;
private readonly _lon: number;
public newElementId: string = undefined
private readonly _snapOnto: OsmWay;
private readonly _reusePointDistance: number;
@ -21,7 +20,7 @@ export default class CreateNewNodeAction extends OsmChangeAction {
this._basicTags = basicTags;
this._lat = lat;
this._lon = lon;
if(lat === undefined || lon === undefined){
if (lat === undefined || lon === undefined) {
throw "Lat or lon are undefined!"
}
this._snapOnto = options?.snapOnto;
@ -82,20 +81,20 @@ export default class CreateNewNodeAction extends OsmChangeAction {
id: reusedPointId
}]
}
const locations = [...this._snapOnto.coordinates]
locations.forEach(coor => coor.reverse())
console.log("Locations are: ", locations)
const ids = [...this._snapOnto.nodes]
locations.splice(index + 1, 0, [this._lon, this._lat])
ids.splice(index + 1, 0, id)
// Allright, we have to insert a new point in the way
return [
newPointChange,
{
type:"way",
type: "way",
id: this._snapOnto.id,
changes: {
locations: locations,

View file

@ -30,7 +30,7 @@ export default class DeleteAction {
* Does actually delete the feature; returns the event source 'this.isDeleted'
* If deletion is not allowed, triggers the callback instead
*/
public DoDelete(reason: string, onNotAllowed : () => void): void {
public DoDelete(reason: string, onNotAllowed: () => void): void {
const isDeleted = this.isDeleted
const self = this;
let deletionStarted = false;
@ -40,23 +40,21 @@ export default class DeleteAction {
// Already deleted...
return;
}
if(canBeDeleted.canBeDeleted === false){
if (canBeDeleted.canBeDeleted === false) {
// We aren't allowed to delete
deletionStarted = true;
onNotAllowed();
isDeleted.setData(true);
return;
}
if (!canBeDeleted) {
// We are not allowed to delete (yet), this might change in the future though
return;
}
deletionStarted = true;
OsmObject.DownloadObject(self._id).addCallbackAndRun(obj => {
if (obj === undefined) {
@ -207,7 +205,7 @@ export default class DeleteAction {
canBeDeleted: false,
reason: t.partOfOthers
})
}else{
} else {
// alright, this point can be safely deleted!
state.setData({
canBeDeleted: true,
@ -215,7 +213,6 @@ export default class DeleteAction {
})
}
}
)

View file

@ -1,12 +1,12 @@
/**
* The logic to handle relations after a way within
* The logic to handle relations after a way within
*/
import OsmChangeAction from "./OsmChangeAction";
import {Changes} from "../Changes";
import {ChangeDescription} from "./ChangeDescription";
import {OsmRelation, OsmWay} from "../OsmObject";
import {OsmRelation} from "../OsmObject";
export default class RelationSplitlHandler extends OsmChangeAction{
export default class RelationSplitlHandler extends OsmChangeAction {
constructor(partOf: OsmRelation[], newWayIds: number[], originalNodes: number[]) {
super()

View file

@ -23,18 +23,18 @@ export class Changes {
public readonly pendingChanges = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
private readonly isUploading = new UIEventSource(false);
private readonly previouslyCreated : OsmObject[] = []
private readonly previouslyCreated: OsmObject[] = []
constructor() {
}
private static createChangesetFor(csId: string,
allChanges: {
modifiedObjects: OsmObject[],
newObjects: OsmObject[],
deletedObjects: OsmObject[]
}): string {
allChanges: {
modifiedObjects: OsmObject[],
newObjects: OsmObject[],
deletedObjects: OsmObject[]
}): string {
const changedElements = allChanges.modifiedObjects ?? []
const newElements = allChanges.newObjects ?? []
@ -70,6 +70,88 @@ export class Changes {
.map(c => c.type + "/" + c.id))
}
/**
* Returns a new ID and updates the value for the next ID
*/
public getNewID() {
return Changes._nextId--;
}
/**
* Uploads all the pending changes in one go.
* Triggered by the 'PendingChangeUploader'-actor in Actors
*/
public flushChanges(flushreason: string = undefined) {
if (this.pendingChanges.data.length === 0) {
return;
}
if (this.isUploading.data) {
console.log("Is already uploading... Abort")
return;
}
this.isUploading.setData(true)
console.log("Beginning upload... " + flushreason ?? "");
// At last, we build the changeset and upload
const self = this;
const pending = self.pendingChanges.data;
const neededIds = Changes.GetNeededIds(pending)
console.log("Needed ids", neededIds)
OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => {
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
try {
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")
self.pendingChanges.setData([])
self.isUploading.setData(false)
return true; // Unregister the callback
}
State.state.osmConnection.UploadChangeset(
State.state.layoutToUse.data,
State.state.allElements,
(csId) => Changes.createChangesetFor(csId, changes),
() => {
console.log("Upload successfull!")
self.pendingChanges.setData([]);
self.isUploading.setData(false)
},
() => {
console.log("Upload failed - trying again later")
return self.isUploading.setData(false);
} // Failed - mark to try again
)
} catch (e) {
console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e)
self.pendingChanges.setData([])
self.isUploading.setData(false)
}
return true;
});
}
public applyAction(action: OsmChangeAction) {
const changes = action.Perform(this)
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes);
this.pendingChanges.ping();
}
private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): {
newObjects: OsmObject[],
modifiedObjects: OsmObject[]
@ -85,7 +167,7 @@ export class Changes {
}
for (const o of this.previouslyCreated) {
objects.set(o.type + "/" + o.id, o)
objects.set(o.type + "/" + o.id, o)
states.set(o.type + "/" + o.id, "unchanged")
}
@ -93,8 +175,8 @@ export class Changes {
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
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")
@ -189,7 +271,7 @@ export class Changes {
break;
}
}
if (changed && state === "unchanged") {
@ -203,7 +285,7 @@ export class Changes {
modifiedObjects: [],
deletedObjects: []
}
objects.forEach((v, id) => {
const state = states.get(id)
@ -221,86 +303,4 @@ export class Changes {
return result
}
/**
* Returns a new ID and updates the value for the next ID
*/
public getNewID() {
return Changes._nextId--;
}
/**
* Uploads all the pending changes in one go.
* Triggered by the 'PendingChangeUploader'-actor in Actors
*/
public flushChanges(flushreason: string = undefined) {
if (this.pendingChanges.data.length === 0) {
return;
}
if (this.isUploading.data) {
console.log("Is already uploading... Abort")
return;
}
this.isUploading.setData(true)
console.log("Beginning upload... "+flushreason ?? "");
// At last, we build the changeset and upload
const self = this;
const pending = self.pendingChanges.data;
const neededIds = Changes.GetNeededIds(pending)
console.log("Needed ids", neededIds)
OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => {
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
try{
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")
self.pendingChanges.setData([])
self.isUploading.setData(false)
return true; // Unregister the callback
}
State.state.osmConnection.UploadChangeset(
State.state.layoutToUse.data,
State.state.allElements,
(csId) => Changes.createChangesetFor(csId, changes),
() => {
console.log("Upload successfull!")
self.pendingChanges.setData([]);
self.isUploading.setData(false)
},
() => {
console.log("Upload failed - trying again later")
return self.isUploading.setData(false);
} // Failed - mark to try again
)
}catch(e){
console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e)
self.pendingChanges.setData([])
self.isUploading.setData(false)
}
return true;
});
}
public applyAction(action: OsmChangeAction) {
const changes = action.Perform(this)
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes);
this.pendingChanges.ping();
}
}

View file

@ -258,7 +258,7 @@ export class ChangesetHandler {
}, function (err, response) {
if (response === undefined) {
console.log("err", err);
if(options.onFail){
if (options.onFail) {
options.onFail()
}
return;

View file

@ -15,7 +15,7 @@ export interface Relation {
export default class ExtractRelations {
public static RegisterRelations(overpassJson: any) : void{
public static RegisterRelations(overpassJson: any): void {
const memberships = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(overpassJson))
State.state.knownRelations.setData(memberships)
}

View file

@ -6,12 +6,14 @@ export class Geocoding {
private static readonly host = "https://nominatim.openstreetmap.org/search?";
static Search(query: string,
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[],
osm_type: string, osm_id: string}[]) => void),
handleResult: ((places: {
display_name: string, lat: number, lon: number, boundingbox: number[],
osm_type: string, osm_id: string
}[]) => void),
onFail: (() => void)) {
const b = State.state.leafletMap.data.getBounds();
const url = Geocoding.host + "format=json&limit=1&viewbox=" +
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}`+
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}` +
"&accept-language=nl&q=" + query;
Utils.downloadJson(
url)

View file

@ -22,7 +22,7 @@ export default class UserDetails {
public dryRun: boolean;
home: { lon: number; lat: number };
public backend: string;
constructor(backend: string) {
this.backend = backend;
}
@ -47,10 +47,10 @@ export class OsmConnection {
public auth;
public userDetails: UIEventSource<UserDetails>;
public isLoggedIn: UIEventSource<boolean>
private fakeUser: boolean;
_dryRun: boolean;
public preferencesHandler: OsmPreferences;
public changesetHandler: ChangesetHandler;
private fakeUser: boolean;
private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [];
private readonly _iframeMode: Boolean | boolean;
private readonly _singlePage: boolean;
@ -59,8 +59,9 @@ export class OsmConnection {
oauth_secret: string,
url: string
};
private isChecking = false;
constructor(dryRun: boolean,
constructor(dryRun: boolean,
fakeUser: boolean,
oauth_token: UIEventSource<string>,
// Used to keep multiple changesets open and to write to the correct changeset
@ -77,17 +78,17 @@ export class OsmConnection {
this.userDetails = new UIEventSource<UserDetails>(new UserDetails(this._oauth_config.url), "userDetails");
this.userDetails.data.dryRun = dryRun || fakeUser;
if(fakeUser){
if (fakeUser) {
const ud = this.userDetails.data;
ud.csCount = 5678
ud.loggedIn= true;
ud.loggedIn = true;
ud.unreadMessages = 0
ud.name = "Fake user"
ud.totalMessages = 42;
}
const self =this;
const self = this;
this.isLoggedIn = this.userDetails.map(user => user.loggedIn).addCallback(isLoggedIn => {
if(self.userDetails.data.loggedIn == false && isLoggedIn == true){
if (self.userDetails.data.loggedIn == false && isLoggedIn == true) {
// We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
// This means someone attempted to toggle this; so we attempt to login!
self.AttemptLogin()
@ -150,7 +151,7 @@ export class OsmConnection {
}
public AttemptLogin() {
if(this.fakeUser){
if (this.fakeUser) {
console.log("AttemptLogin called, but ignored as fakeUser is set")
return;
}
@ -191,7 +192,7 @@ export class OsmConnection {
data.loggedIn = true;
console.log("Login completed, userinfo is ", userInfo);
data.name = userInfo.getAttribute('display_name');
data.uid= Number(userInfo.getAttribute("id"))
data.uid = Number(userInfo.getAttribute("id"))
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
data.img = undefined;
@ -249,20 +250,19 @@ export class OsmConnection {
});
}
private isChecking = false;
private CheckForMessagesContinuously(){
const self =this;
if(this.isChecking){
private CheckForMessagesContinuously() {
const self = this;
if (this.isChecking) {
return;
}
this.isChecking = true;
UIEventSource.Chronic(5 * 60 * 1000).addCallback(_ => {
if (self.isLoggedIn .data) {
console.log("Checking for messages")
self.AttemptLogin();
}
if (self.isLoggedIn.data) {
console.log("Checking for messages")
self.AttemptLogin();
}
});
}

View file

@ -52,7 +52,7 @@ export abstract class OsmObject {
const splitted = id.split("/");
const type = splitted[0];
const idN = Number(splitted[1]);
if(idN <0){
if (idN < 0) {
return;
}
@ -438,7 +438,7 @@ export class OsmWay extends OsmObject {
for (const nodeId of element.nodes) {
const node = nodeDict.get(nodeId)
if(node === undefined){
if (node === undefined) {
console.error("Error: node ", nodeId, "not found in ", nodeDict)
// This is probably part of a relation which hasn't been fully downloaded
continue;
@ -498,7 +498,7 @@ export class OsmRelation extends OsmObject {
let tags = this.TagsXML();
let cs = ""
if(changesetId !== undefined){
if (changesetId !== undefined) {
cs = `changeset="${changesetId}"`
}
return ` <relation id="${this.id}" ${cs} ${this.VersionXML()}>

View file

@ -29,7 +29,7 @@ export class OsmPreferences {
return this.longPreferences[prefix + key];
}
const source = new UIEventSource<string>(undefined, "long-osm-preference:"+prefix+key);
const source = new UIEventSource<string>(undefined, "long-osm-preference:" + prefix + key);
this.longPreferences[prefix + key] = source;
const allStartWith = prefix + key + "-combined";
@ -107,7 +107,7 @@ export class OsmPreferences {
if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) {
this.UpdatePreferences();
}
const pref = new UIEventSource<string>(this.preferences.data[key],"osm-preference:"+key);
const pref = new UIEventSource<string>(this.preferences.data[key], "osm-preference:" + key);
pref.addCallback((v) => {
this.SetPreference(key, v);
});

View file

@ -15,7 +15,7 @@ export class Overpass {
private readonly _timeout: UIEventSource<number>;
private readonly _extraScripts: string[];
private _includeMeta: boolean;
constructor(filter: TagsFilter, extraScripts: string[],
interpreterUrl: UIEventSource<string>,
timeout: UIEventSource<number>,
@ -42,7 +42,7 @@ export class Overpass {
onFail("Runtime error (timeout)")
return;
}
ExtractRelations.RegisterRelations(json)
// @ts-ignore

View file

@ -12,12 +12,9 @@ export default class AspectedRouting {
this.program = JSON.parse(JSON.stringify(program))
delete this.program.name
delete this.program.description
delete this.program.unit
delete this.program.unit
}
public evaluate(properties){
return AspectedRouting.interpret(this.program, properties)
}
/**
* Interprets the given Aspected-routing program for the given properties
*/
@ -191,4 +188,8 @@ export default class AspectedRouting {
return result;
}
public evaluate(properties) {
return AspectedRouting.interpret(this.program, properties)
}
}

View file

@ -96,7 +96,7 @@ export default class SimpleMetaTagger {
const value = feature.properties[key]
const [, denomination] = unit.findDenomination(value)
let canonical = denomination?.canonicalValue(value) ?? undefined;
if(canonical === value){
if (canonical === value) {
break;
}
console.log("Rewritten ", key, ` from '${value}' into '${canonical}'`)
@ -110,7 +110,7 @@ export default class SimpleMetaTagger {
}
}
if(rewritten){
if (rewritten) {
State.state.allElements.getEventSourceById(feature.id).ping();
}
})

View file

@ -7,18 +7,6 @@ export class And extends TagsFilter {
super();
this.and = and
}
normalize(){
const ands = []
for (const c of this.and) {
if(c instanceof And){
ands.push(...c.and)
}else{
ands.push(c)
}
}
return new And(ands)
}
private static combine(filter: string, choices: string[]): string[] {
const values = [];
@ -28,6 +16,18 @@ export class And extends TagsFilter {
return values;
}
normalize() {
const ands = []
for (const c of this.and) {
if (c instanceof And) {
ands.push(...c.and)
} else {
ands.push(c)
}
}
return new And(ands)
}
matchesProperties(tags: any): boolean {
for (const tagsFilter of this.and) {
if (!tagsFilter.matchesProperties(tags)) {

View file

@ -4,19 +4,19 @@ export default class ComparingTag implements TagsFilter {
private readonly _key: string;
private readonly _predicate: (value: string) => boolean;
private readonly _representation: string;
constructor(key: string, predicate : (value:string | undefined) => boolean, representation: string = "") {
constructor(key: string, predicate: (value: string | undefined) => boolean, representation: string = "") {
this._key = key;
this._predicate = predicate;
this._representation = representation;
}
asChange(properties: any): { k: string; v: string }[] {
throw "A comparable tag can not be used to be uploaded to OSM"
}
asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) {
return this._key+this._representation
return this._key + this._representation
}
asOverpass(): string[] {
@ -38,5 +38,5 @@ export default class ComparingTag implements TagsFilter {
usedKeys(): string[] {
return [this._key];
}
}

View file

@ -16,7 +16,7 @@ export class RegexTag extends TagsFilter {
}
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
if(fromTag === undefined){
if (fromTag === undefined) {
return;
}
if (typeof possibleRegex === "string") {
@ -45,7 +45,7 @@ export class RegexTag extends TagsFilter {
matchesProperties(tags: any): boolean {
for (const key in tags) {
if(key === undefined){
if (key === undefined) {
continue;
}
if (RegexTag.doesMatch(key, this.key)) {
@ -86,14 +86,14 @@ export class RegexTag extends TagsFilter {
}
asChange(properties: any): { k: string; v: string }[] {
if(this.invert){
if (this.invert) {
return []
}
if (typeof this.key === "string") {
if( typeof this.value === "string"){
if (typeof this.value === "string") {
return [{k: this.key, v: this.value}]
}
if(this.value.toString() != "/^..*$/"){
if (this.value.toString() != "/^..*$/") {
console.warn("Regex value in tag; using wildcard:", this.key, this.value)
}
return [{k: this.key, v: undefined}]

View file

@ -24,19 +24,19 @@ export class Tag extends TagsFilter {
matchesProperties(properties: any): boolean {
for (const propertiesKey in properties) {
if(!properties.hasOwnProperty(propertiesKey)){
if (!properties.hasOwnProperty(propertiesKey)) {
continue
}
if (this.key === propertiesKey) {
const value = properties[propertiesKey];
if(value === undefined){
if (value === undefined) {
continue
}
return value === this.value;
}
}
// The tag was not found
if (this.value === "") {
// and it shouldn't be found!
return true;
@ -52,6 +52,7 @@ export class Tag extends TagsFilter {
}
return [`["${this.key}"="${this.value}"]`];
}
asHumanString(linkToWiki?: boolean, shorten?: boolean) {
let v = this.value;
if (shorten) {
@ -84,6 +85,6 @@ export class Tag extends TagsFilter {
}
asChange(properties: any): { k: string; v: string }[] {
return [{k: this.key, v: this.value}];
return [{k: this.key, v: this.value}];
}
}

View file

@ -10,6 +10,15 @@ import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
import {isRegExp} from "util";
export class TagUtils {
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],
]
static ApplyTemplate(template: string, tags: any): string {
for (const k in tags) {
while (template.indexOf("{" + k + "}") >= 0) {
@ -75,21 +84,21 @@ export class TagUtils {
keyValues[tagsFilter.key].push(...tagsFilter.value.split(";"));
continue;
}
if(allowRegex && tagsFilter instanceof RegexTag) {
if (allowRegex && tagsFilter instanceof RegexTag) {
const key = tagsFilter.key
if(isRegExp(key)) {
if (isRegExp(key)) {
console.error("Invalid type to flatten the multiAnswer: key is a regex too", tagsFilter);
throw "Invalid type to FlattenMultiAnswer"
}
const keystr = <string>key
if (keyValues[keystr] === undefined) {
keyValues[keystr ] = [];
keyValues[keystr] = [];
}
keyValues[keystr].push(tagsFilter);
continue;
}
console.error("Invalid type to flatten the multiAnswer", tagsFilter);
throw "Invalid type to FlattenMultiAnswer"
@ -138,13 +147,13 @@ export class TagUtils {
const actualValue = properties[splitKey].split(";");
for (const neededValue of neededValues) {
if(neededValue instanceof RegexTag) {
if(!neededValue.matchesProperties(properties)) {
if (neededValue instanceof RegexTag) {
if (!neededValue.matchesProperties(properties)) {
return false
}
continue
}
}
if (actualValue.indexOf(neededValue) < 0) {
return false;
}
@ -170,15 +179,6 @@ export class TagUtils {
}
}
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) {

View file

@ -11,13 +11,13 @@ export abstract class TagsFilter {
abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any);
abstract usedKeys(): string[];
/**
* Converts the tagsFilter into a list of key-values that should be uploaded to OSM.
* Throws an error if not applicable.
*
*
* Note: properties are the already existing tags-object. It is only used in the substituting tag
*/
abstract asChange(properties:any): {k: string, v:string}[]
abstract asChange(properties: any): { k: string, v: string }[]
}

View file

@ -184,7 +184,7 @@ export class UIEventSource<T> {
addCallbackAndRunD(callback: (data: T) => void) {
this.addCallbackAndRun(data => {
if (data !== undefined && data !== null) {
return callback(data)
return callback(data)
}
})
}
@ -194,9 +194,9 @@ export class UIEventSourceTools {
private static readonly _download_cache = new Map<string, UIEventSource<any>>()
public static downloadJsonCached(url: string): UIEventSource<any>{
public static downloadJsonCached(url: string): UIEventSource<any> {
const cached = UIEventSourceTools._download_cache.get(url)
if(cached !== undefined){
if (cached !== undefined) {
return cached;
}
const src = new UIEventSource<any>(undefined)

View file

@ -48,7 +48,7 @@ export default class Hash {
}
hash.setData(newValue)
}
window.addEventListener('popstate', _ => {
let newValue = window.location.hash.substr(1);
if (newValue === "") {

View file

@ -24,7 +24,7 @@ export default class LiveQueryHandler {
const source = new UIEventSource({});
LiveQueryHandler[url] = source;
console.log("Fetching live data from a third-party (unknown) API:",url)
console.log("Fetching live data from a third-party (unknown) API:", url)
Utils.downloadJson(url).then(data => {
for (const shorthandDescription of shorthandsSet) {

View file

@ -4,19 +4,19 @@ import {UIEventSource} from "../UIEventSource";
* UIEventsource-wrapper around localStorage
*/
export class LocalStorageSource {
static GetParsed<T>(key: string, defaultValue : T) : UIEventSource<T>{
static GetParsed<T>(key: string, defaultValue: T): UIEventSource<T> {
return LocalStorageSource.Get(key).map(
str => {
if(str === undefined){
if (str === undefined) {
return defaultValue
}
try{
try {
return JSON.parse(str)
}catch{
} catch {
return defaultValue
}
}, [],
}, [],
value => JSON.stringify(value)
)
}
@ -24,17 +24,17 @@ export class LocalStorageSource {
static Get(key: string, defaultValue: string = undefined): UIEventSource<string> {
try {
const saved = localStorage.getItem(key);
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:"+key);
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:" + key);
source.addCallback((data) => {
try{
try {
localStorage.setItem(key, data);
}catch(e){
} catch (e) {
// Probably exceeded the quota with this item!
// Lets nuke everything
localStorage.clear()
}
});
return source;
} catch (e) {

View file

@ -69,6 +69,10 @@ export class QueryParameters {
return docs.join("\n\n");
}
public static wasInitialized(key: string): boolean {
return QueryParameters._wasInitialized.has(key)
}
private static addOrder(key) {
if (this.order.indexOf(key) < 0) {
this.order.push(key)
@ -104,10 +108,6 @@ export class QueryParameters {
console.log(QueryParameters.GenerateQueryParameterDocs())
}
}
public static wasInitialized(key: string) : boolean{
return QueryParameters._wasInitialized.has(key)
}
private static Serialize() {
const parts = []