diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index 938c1ccac4..dbb4ae8559 100644 --- a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -2,8 +2,50 @@ import { FeatureSource } from "../FeatureSource" import { Feature } from "geojson" import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" +import FeaturePropertiesStore from "./FeaturePropertiesStore" +import { UIEventSource } from "../../UIEventSource" import { Utils } from "../../../Utils" +class SingleTileSaver { + private readonly _storage: UIEventSource + private readonly _registeredIds = new Set() + private readonly _featureProperties: FeaturePropertiesStore + private readonly _isDirty = new UIEventSource(false) + constructor( + storage: UIEventSource & { flush: () => void }, + featureProperties: FeaturePropertiesStore + ) { + this._storage = storage + this._featureProperties = featureProperties + this._isDirty.stabilized(1000).addCallbackD((isDirty) => { + if (!isDirty) { + return + } + // 'isDirty' is set when tags of some object have changed + storage.flush() + this._isDirty.setData(false) + }) + } + + public saveFeatures(features: Feature[]) { + if (Utils.sameList(features, this._storage.data)) { + return + } + for (const feature of features) { + const id = feature.properties.id + if (this._registeredIds.has(id)) { + continue + } + this._featureProperties.getStore(id)?.addCallbackAndRunD(() => { + this._isDirty.setData(true) + }) + this._registeredIds.add(id) + } + + this._storage.setData(features) + } +} + /*** * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run * @@ -12,16 +54,27 @@ import { Utils } from "../../../Utils" * Also see the sibling class */ export default class SaveFeatureSourceToLocalStorage { - constructor(layername: string, zoomlevel: number, features: FeatureSource) { + constructor( + layername: string, + zoomlevel: number, + features: FeatureSource, + featureProperties: FeaturePropertiesStore + ) { const storage = TileLocalStorage.construct(layername) + const singleTileSavers: Map = new Map() features.features.addCallbackAndRunD((features) => { const sliced = GeoOperations.slice(zoomlevel, features) + sliced.forEach((features, tileIndex) => { - const src = storage.getTileSource(tileIndex) - if (Utils.sameList(src.data, features)) { - return + let tileSaver = singleTileSavers.get(tileIndex) + if (tileSaver === undefined) { + const src = storage.getTileSource(tileIndex) + tileSaver = new SingleTileSaver(src, featureProperties) + singleTileSavers.set(tileIndex, tileSaver) } - src.setData(features) + // Don't cache not-uploaded features yet - they'll be cached when the receive their id + features = features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) + tileSaver.saveFeatures(features) }) }) } diff --git a/Logic/FeatureSource/Actors/TileLocalStorage.ts b/Logic/FeatureSource/Actors/TileLocalStorage.ts index 642110d4ee..9ee40c6034 100644 --- a/Logic/FeatureSource/Actors/TileLocalStorage.ts +++ b/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -4,12 +4,14 @@ import { UIEventSource } from "../../UIEventSource" /** * A class which allows to read/write a tile to local storage. * - * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage + * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage. + * + * Note: OSM-features with a negative id are ignored */ export default class TileLocalStorage { private static perLayer: Record> = {} private readonly _layername: string - private readonly cachedSources: Record> = {} + private readonly cachedSources: Record & { flush: () => void }> = {} private constructor(layername: string) { this._layername = layername @@ -27,24 +29,26 @@ export default class TileLocalStorage { } /** - * Constructs a UIEventSource element which is synced with localStorage - * @param layername - * @param tileIndex + * Constructs a UIEventSource element which is synced with localStorage. + * Supports 'flush' */ - public getTileSource(tileIndex: number): UIEventSource { + public getTileSource(tileIndex: number): UIEventSource & { flush: () => void } { const cached = this.cachedSources[tileIndex] if (cached) { return cached } - const src = UIEventSource.FromPromise(this.GetIdb(tileIndex)) + const src = & { flush: () => void }>( + UIEventSource.FromPromise(this.GetIdb(tileIndex)) + ) + src.flush = () => this.SetIdb(tileIndex, src.data) src.addCallbackD((data) => this.SetIdb(tileIndex, data)) this.cachedSources[tileIndex] = src return src } - private SetIdb(tileIndex: number, data): void { + private async SetIdb(tileIndex: number, data): Promise { try { - IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) + await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) } catch (e) { console.error( "Could not save tile to indexed-db: ", @@ -52,7 +56,9 @@ export default class TileLocalStorage { "tileIndex is:", tileIndex, "for layer", - this._layername + this._layername, + "data is", + data ) } } diff --git a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts index 96bbb52755..972c48a8e7 100644 --- a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -20,7 +20,14 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { const storage = TileLocalStorage.construct(layername) super( zoomlevel, - (tileIndex) => new StaticFeatureSource(storage.getTileSource(tileIndex)), + (tileIndex) => + new StaticFeatureSource( + storage + .getTileSource(tileIndex) + .map((features) => + features?.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) + ) + ), mapProperties, options ) diff --git a/Logic/Web/IdbLocalStorage.ts b/Logic/Web/IdbLocalStorage.ts index 2d23db76ec..8abc2a9e5a 100644 --- a/Logic/Web/IdbLocalStorage.ts +++ b/Logic/Web/IdbLocalStorage.ts @@ -38,11 +38,11 @@ export class IdbLocalStorage { return src } - public static SetDirectly(key: string, value) { - idb.set(key, value) + public static SetDirectly(key: string, value): Promise { + return idb.set(key, value) } - static GetDirectly(key: string) { + static GetDirectly(key: string): Promise { return idb.get(key) } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 82323baf57..7d668fdb73 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -158,7 +158,12 @@ export default class ThemeViewState implements SpecialVisualizationState { this.perLayer = perLayer.perLayer this.perLayer.forEach((fs) => { - new SaveFeatureSourceToLocalStorage(fs.layer.layerDef.id, 15, fs) + new SaveFeatureSourceToLocalStorage( + fs.layer.layerDef.id, + 15, + fs, + this.featureProperties + ) const filtered = new FilteringFeatureSource( fs.layer,