forked from MapComplete/MapComplete
Refactoring: port import flow
This commit is contained in:
parent
8ed4da4e9d
commit
ace7caada1
48 changed files with 852 additions and 574 deletions
|
@ -414,6 +414,9 @@ class GetParsed implements ExtraFunction {
|
|||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if(typeof value !== "string"){
|
||||
return value
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
if (parsed === null) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IdbLocalStorage } from "../../Web/IdbLocalStorage"
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import {IdbLocalStorage} from "../../Web/IdbLocalStorage"
|
||||
import {UIEventSource} from "../../UIEventSource"
|
||||
|
||||
/**
|
||||
* A class which allows to read/write a tile to local storage.
|
||||
|
@ -10,23 +10,25 @@ import { UIEventSource } from "../../UIEventSource"
|
|||
*/
|
||||
export default class TileLocalStorage<T> {
|
||||
private static perLayer: Record<string, TileLocalStorage<any>> = {}
|
||||
private static readonly useIndexedDb = typeof indexedDB !== "undefined"
|
||||
private readonly _layername: string
|
||||
private readonly inUse = new UIEventSource(false)
|
||||
private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {}
|
||||
private readonly _maxAgeSeconds: number;
|
||||
|
||||
private static readonly useIndexedDb = typeof indexedDB !== "undefined"
|
||||
private constructor(layername: string) {
|
||||
private constructor(layername: string, maxAgeSeconds: number) {
|
||||
this._layername = layername
|
||||
this._maxAgeSeconds = maxAgeSeconds;
|
||||
}
|
||||
|
||||
public static construct<T>(backend: string, layername: string): TileLocalStorage<T> {
|
||||
public static construct<T>(backend: string, layername: string, maxAgeS: number): TileLocalStorage<T> {
|
||||
const key = backend + "_" + layername
|
||||
const cached = TileLocalStorage.perLayer[key]
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const tls = new TileLocalStorage<T>(key)
|
||||
const tls = new TileLocalStorage<T>(key, maxAgeS)
|
||||
TileLocalStorage.perLayer[key] = tls
|
||||
return tls
|
||||
}
|
||||
|
@ -50,13 +52,15 @@ export default class TileLocalStorage<T> {
|
|||
}
|
||||
|
||||
private async SetIdb(tileIndex: number, data: any): Promise<void> {
|
||||
if(!TileLocalStorage.useIndexedDb){
|
||||
if (!TileLocalStorage.useIndexedDb) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await this.inUse.AsPromise((inUse) => !inUse)
|
||||
this.inUse.setData(true)
|
||||
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
|
||||
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex + "_date", Date.now())
|
||||
|
||||
this.inUse.setData(false)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
|
@ -72,10 +76,24 @@ export default class TileLocalStorage<T> {
|
|||
}
|
||||
}
|
||||
|
||||
private GetIdb(tileIndex: number): Promise<any> {
|
||||
if(!TileLocalStorage.useIndexedDb){
|
||||
private async GetIdb(tileIndex: number): Promise<any> {
|
||||
if (!TileLocalStorage.useIndexedDb) {
|
||||
return undefined
|
||||
}
|
||||
return IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex)
|
||||
const date = <any>await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex + "_date")
|
||||
const maxAge = this._maxAgeSeconds
|
||||
const timeDiff = Date.now() - date
|
||||
if (timeDiff >= maxAge) {
|
||||
console.log("Dropping cache for", this._layername, tileIndex, "out of date")
|
||||
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, undefined)
|
||||
|
||||
return undefined
|
||||
}
|
||||
const data = await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex)
|
||||
return <any>data
|
||||
}
|
||||
|
||||
invalidate(zoomlevel: number, tileIndex) {
|
||||
this.getTileSource(tileIndex).setData(undefined)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,6 @@ export interface WritableFeatureSource<T extends Feature = Feature> extends Feat
|
|||
features: UIEventSource<T[]>
|
||||
}
|
||||
|
||||
export interface Tiled {
|
||||
tileIndex: number
|
||||
bbox: BBox
|
||||
}
|
||||
|
||||
/**
|
||||
* A feature source which only contains features for the defined layer
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ import FeatureSourceMerger from "./FeatureSourceMerger"
|
|||
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
|
||||
import {BBox} from "../../BBox"
|
||||
import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
|
||||
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
||||
/**
|
||||
* This source will fetch the needed data from various sources for the given layout.
|
||||
|
@ -27,7 +28,8 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
featureSwitches: FeatureSwitchState,
|
||||
mapProperties: { bounds: Store<BBox>; zoom: Store<number> },
|
||||
backend: string,
|
||||
isDisplayed: (id: string) => Store<boolean>
|
||||
isDisplayed: (id: string) => Store<boolean>,
|
||||
fullNodeDatabaseSource?: FullNodeDatabaseSource
|
||||
) {
|
||||
const { bounds, zoom } = mapProperties
|
||||
// remove all 'special' layers
|
||||
|
@ -39,6 +41,7 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
(l) =>
|
||||
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
|
||||
isActive: isDisplayed(l.id),
|
||||
maxAge: l.maxAgeOfCache
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -55,7 +58,8 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
bounds,
|
||||
zoom,
|
||||
backend,
|
||||
featureSwitches
|
||||
featureSwitches,
|
||||
fullNodeDatabaseSource
|
||||
)
|
||||
const geojsonSources: FeatureSource[] = geojsonlayers.map((l) =>
|
||||
LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id))
|
||||
|
@ -96,7 +100,8 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
bounds: Store<BBox>,
|
||||
zoom: Store<number>,
|
||||
backend: string,
|
||||
featureSwitches: FeatureSwitchState
|
||||
featureSwitches: FeatureSwitchState,
|
||||
fullNodeDatabase: FullNodeDatabaseSource
|
||||
): OsmFeatureSource | undefined {
|
||||
if (osmLayers.length == 0) {
|
||||
return undefined
|
||||
|
@ -121,8 +126,8 @@ export default class LayoutSource extends FeatureSourceMerger {
|
|||
bounds,
|
||||
backend,
|
||||
isActive,
|
||||
patchRelations: true
|
||||
|
||||
patchRelations: true,
|
||||
fullNodeDatabase
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { TagsFilter } from "../../Tags/TagsFilter"
|
|||
import { Feature } from "geojson"
|
||||
import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
|
||||
import OsmObjectDownloader from "../../Osm/OsmObjectDownloader"
|
||||
import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
||||
/**
|
||||
* If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile'
|
||||
|
@ -16,9 +17,19 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
private readonly isActive: Store<boolean>
|
||||
private readonly _backend: string
|
||||
private readonly allowedTags: TagsFilter
|
||||
private options: {
|
||||
bounds: Store<BBox>
|
||||
readonly allowedFeatures: TagsFilter
|
||||
backend?: "https://openstreetmap.org/" | string
|
||||
/**
|
||||
* If given: this featureSwitch will not update if the store contains 'false'
|
||||
*/
|
||||
isActive?: Store<boolean>,
|
||||
patchRelations?: true | boolean,
|
||||
fullNodeDatabase?: FullNodeDatabaseSource
|
||||
};
|
||||
|
||||
public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
public rawDataHandlers: ((osmJson: any, tileIndex: number) => void)[] = []
|
||||
|
||||
private readonly _downloadedTiles: Set<number> = new Set<number>()
|
||||
private readonly _downloadedData: Feature[][] = []
|
||||
|
@ -35,9 +46,11 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
* If given: this featureSwitch will not update if the store contains 'false'
|
||||
*/
|
||||
isActive?: Store<boolean>,
|
||||
patchRelations?: true | boolean
|
||||
patchRelations?: true | boolean,
|
||||
fullNodeDatabase?: FullNodeDatabaseSource
|
||||
}) {
|
||||
super()
|
||||
this.options = options;
|
||||
this._bounds = options.bounds
|
||||
this.allowedTags = options.allowedFeatures
|
||||
this.isActive = options.isActive ?? new ImmutableStore(true)
|
||||
|
@ -119,7 +132,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
return feature
|
||||
}
|
||||
|
||||
private async LoadTile(z, x, y): Promise<void> {
|
||||
private async LoadTile(z: number, x: number, y: number): Promise<void> {
|
||||
console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend)
|
||||
if (z >= 22) {
|
||||
throw "This is an absurd high zoom level"
|
||||
|
@ -141,9 +154,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
try {
|
||||
const osmJson = await Utils.downloadJsonCached(url, 2000)
|
||||
try {
|
||||
this.rawDataHandlers.forEach((handler) =>
|
||||
handler(osmJson, Tiles.tile_index(z, x, y))
|
||||
)
|
||||
this.options?.fullNodeDatabase?.handleOsmJson(osmJson, z, x, y)
|
||||
let features = <Feature<any, { id: string }>[]>OsmToGeoJson(
|
||||
osmJson,
|
||||
// @ts-ignore
|
||||
|
@ -181,7 +192,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
y,
|
||||
"due to",
|
||||
e,
|
||||
"; retrying with smaller bounds"
|
||||
e === "rate limited" ? "; stopping now" : "; retrying with smaller bounds"
|
||||
)
|
||||
if (e === "rate limited") {
|
||||
return
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
import {FeatureSource, FeatureSourceForLayer, Tiled } from "../FeatureSource"
|
||||
import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject"
|
||||
import SimpleFeatureSource from "../Sources/SimpleFeatureSource"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import { UIEventSource } from "../../UIEventSource"
|
||||
import { OsmTags } from "../../../Models/OsmFeature";
|
||||
import { BBox } from "../../BBox";
|
||||
import { Feature, Point } from "geojson";
|
||||
import {OsmNode, OsmObject, OsmWay} from "../../Osm/OsmObject"
|
||||
import {UIEventSource} from "../../UIEventSource"
|
||||
import {BBox} from "../../BBox";
|
||||
import StaticFeatureSource from "../Sources/StaticFeatureSource";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
|
||||
export default class FullNodeDatabaseSource {
|
||||
public readonly loadedTiles = new Map<number, FeatureSource & Tiled>()
|
||||
private readonly onTileLoaded: (tile: Tiled & FeatureSourceForLayer) => void
|
||||
private readonly layer: FilteredLayer
|
||||
|
||||
private readonly loadedTiles = new Map<number, Map<number, OsmNode>>()
|
||||
private readonly nodeByIds = new Map<number, OsmNode>()
|
||||
private readonly parentWays = new Map<number, UIEventSource<OsmWay[]>>()
|
||||
|
||||
constructor(layer: FilteredLayer, onTileLoaded: (tile: Tiled & FeatureSourceForLayer) => void) {
|
||||
this.onTileLoaded = onTileLoaded
|
||||
this.layer = layer
|
||||
if (this.layer === undefined) {
|
||||
throw "Layer is undefined"
|
||||
}
|
||||
}
|
||||
private smallestZoom = 99
|
||||
private largestZoom = 0
|
||||
|
||||
public handleOsmJson(osmJson: any, tileId: number) {
|
||||
public handleOsmJson(osmJson: any, z: number, x: number, y: number) : void {
|
||||
const allObjects = OsmObject.ParseObjects(osmJson.elements)
|
||||
const nodesById = new Map<number, OsmNode>()
|
||||
|
||||
this.smallestZoom = Math.min(this.smallestZoom, z)
|
||||
this.largestZoom = Math.max(this.largestZoom, z)
|
||||
|
||||
for (const osmObj of allObjects) {
|
||||
if (osmObj.type !== "node") {
|
||||
continue
|
||||
|
@ -59,10 +53,9 @@ export default class FullNodeDatabaseSource {
|
|||
osmNode.asGeoJson()
|
||||
)
|
||||
|
||||
const featureSource = new SimpleFeatureSource(this.layer, tileId)
|
||||
featureSource.features.setData(asGeojsonFeatures)
|
||||
this.loadedTiles.set(tileId, featureSource)
|
||||
this.onTileLoaded(featureSource)
|
||||
const featureSource = new StaticFeatureSource(asGeojsonFeatures)
|
||||
const tileId = Tiles.tile_index(z, x, y)
|
||||
this.loadedTiles.set(tileId, nodesById)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +69,7 @@ export default class FullNodeDatabaseSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the parent way list
|
||||
* Gets all the ways that the given node is a part of
|
||||
* @param nodeId
|
||||
* @constructor
|
||||
*/
|
||||
|
@ -84,8 +77,20 @@ export default class FullNodeDatabaseSource {
|
|||
return this.parentWays.get(nodeId)
|
||||
}
|
||||
|
||||
getNodesWithin(bBox: BBox) : Feature<Point, OsmTags>[]{
|
||||
// TODO
|
||||
throw "TODO"
|
||||
/**
|
||||
* Gets (at least) all nodes which are part of this BBOX; might also return some nodes that fall outside of the bbox but are closeby
|
||||
* @param bbox
|
||||
*/
|
||||
getNodesWithin(bbox: BBox) : Map<number, OsmNode>{
|
||||
const allById = new Map<number, OsmNode>()
|
||||
for (let z = this.smallestZoom; z < this.largestZoom; z++) {
|
||||
const range = Tiles.tileRangeFrom(bbox, z)
|
||||
Tiles.MapRange(range, (x, y ) => {
|
||||
const tileId = Tiles.tile_index(z, x, y)
|
||||
const nodesById = this.loadedTiles.get(tileId)
|
||||
nodesById?.forEach((v,k) => allById.set(k,v))
|
||||
})
|
||||
}
|
||||
return allById
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import DynamicTileSource from "./DynamicTileSource"
|
||||
import { Store } from "../../UIEventSource"
|
||||
import { BBox } from "../../BBox"
|
||||
import {Store} from "../../UIEventSource"
|
||||
import {BBox} from "../../BBox"
|
||||
import TileLocalStorage from "../Actors/TileLocalStorage"
|
||||
import { Feature } from "geojson"
|
||||
import {Feature} from "geojson"
|
||||
import StaticFeatureSource from "../Sources/StaticFeatureSource"
|
||||
import {constants} from "http2";
|
||||
import HTTP_STATUS_CONTINUE = module
|
||||
|
||||
export default class LocalStorageFeatureSource extends DynamicTileSource {
|
||||
constructor(
|
||||
|
@ -15,18 +17,25 @@ export default class LocalStorageFeatureSource extends DynamicTileSource {
|
|||
zoom: Store<number>
|
||||
},
|
||||
options?: {
|
||||
isActive?: Store<boolean>
|
||||
isActive?: Store<boolean>,
|
||||
maxAge?: number // In seconds
|
||||
}
|
||||
) {
|
||||
const storage = TileLocalStorage.construct<Feature[]>(backend, layername)
|
||||
const storage = TileLocalStorage.construct<Feature[]>(backend, layername, options?.maxAge ?? 24 * 60 * 60)
|
||||
super(
|
||||
zoomlevel,
|
||||
(tileIndex) =>
|
||||
new StaticFeatureSource(
|
||||
storage
|
||||
.getTileSource(tileIndex)
|
||||
.map((features) =>
|
||||
features?.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/))
|
||||
.mapD((features) => {
|
||||
if (features.length === undefined) {
|
||||
console.trace("These are not features:", features)
|
||||
storage.invalidate(zoomlevel, tileIndex)
|
||||
return []
|
||||
}
|
||||
return features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/));
|
||||
}
|
||||
)
|
||||
),
|
||||
mapProperties,
|
||||
|
|
|
@ -9,6 +9,7 @@ import {IndexedFeatureSource} from "./FeatureSource/FeatureSource"
|
|||
import OsmObjectDownloader from "./Osm/OsmObjectDownloader"
|
||||
import {Utils} from "../Utils";
|
||||
import {GeoJSONFeature} from "maplibre-gl";
|
||||
import {UIEventSource} from "./UIEventSource";
|
||||
|
||||
/**
|
||||
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
|
||||
|
@ -18,7 +19,7 @@ import {GeoJSONFeature} from "maplibre-gl";
|
|||
export default class MetaTagging {
|
||||
private static errorPrintCount = 0
|
||||
private static readonly stopErrorOutputAt = 10
|
||||
private static retaggingFuncCache = new Map<string, ((feature: Feature) => void)[]>()
|
||||
private static retaggingFuncCache = new Map<string, ((feature: Feature, propertiesStore: UIEventSource<any>) => void)[]>()
|
||||
|
||||
constructor(state: {
|
||||
layout: LayoutConfig
|
||||
|
@ -27,19 +28,7 @@ export default class MetaTagging {
|
|||
indexedFeatures: IndexedFeatureSource
|
||||
featureProperties: FeaturePropertiesStore
|
||||
}) {
|
||||
const params: ExtraFuncParams = {
|
||||
getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id),
|
||||
getFeaturesWithin: (layerId, bbox) => {
|
||||
if(layerId === '*' || layerId === null || layerId === undefined){
|
||||
const feats: Feature[][] = []
|
||||
state.perLayer.forEach((layer) => {
|
||||
feats.push(layer.GetFeaturesWithin(bbox))
|
||||
})
|
||||
return feats
|
||||
}
|
||||
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)];
|
||||
},
|
||||
}
|
||||
const params = MetaTagging.createExtraFuncParams(state)
|
||||
for (const layer of state.layout.layers) {
|
||||
if (layer.source === null) {
|
||||
continue
|
||||
|
@ -108,7 +97,7 @@ export default class MetaTagging {
|
|||
// The calculated functions - per layer - which add the new keys
|
||||
// Calculated functions are defined by the layer
|
||||
const layerFuncs = this.createRetaggingFunc(layer, ExtraFunctions.constructHelpers(params))
|
||||
const state: MetataggingState = { layout, osmObjectDownloader }
|
||||
const state: MetataggingState = {layout, osmObjectDownloader}
|
||||
|
||||
let atLeastOneFeatureChanged = false
|
||||
let strictlyEvaluated = 0
|
||||
|
@ -117,15 +106,7 @@ export default class MetaTagging {
|
|||
const tags = featurePropertiesStores?.getStore(feature.properties.id)
|
||||
let somethingChanged = false
|
||||
let definedTags = new Set(Object.getOwnPropertyNames(feature.properties))
|
||||
if (layerFuncs !== undefined) {
|
||||
let retaggingChanged = false
|
||||
try {
|
||||
retaggingChanged = layerFuncs(feature)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
somethingChanged = somethingChanged || retaggingChanged
|
||||
}
|
||||
|
||||
for (const metatag of metatagsToApply) {
|
||||
try {
|
||||
if (!metatag.keys.some((key) => !(key in feature.properties))) {
|
||||
|
@ -144,7 +125,7 @@ export default class MetaTagging {
|
|||
if (options?.evaluateStrict) {
|
||||
for (const key of metatag.keys) {
|
||||
const evaluated = feature.properties[key]
|
||||
if(evaluated !== undefined){
|
||||
if (evaluated !== undefined) {
|
||||
strictlyEvaluated++
|
||||
}
|
||||
}
|
||||
|
@ -175,12 +156,18 @@ export default class MetaTagging {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (layerFuncs !== undefined) {
|
||||
try {
|
||||
// We cannot do `somethingChanged || layerFuncs(feature)', due to the shortcutting behaviour it would not calculate the lazy functions
|
||||
somethingChanged = layerFuncs(feature, tags) || somethingChanged
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
try {
|
||||
featurePropertiesStores?.getStore(feature.properties.id)?.ping()
|
||||
tags?.ping()
|
||||
} catch (e) {
|
||||
console.error("Could not ping a store for a changed property due to", e)
|
||||
}
|
||||
|
@ -190,6 +177,25 @@ export default class MetaTagging {
|
|||
return atLeastOneFeatureChanged
|
||||
}
|
||||
|
||||
public static createExtraFuncParams(state: {
|
||||
indexedFeatures: IndexedFeatureSource,
|
||||
perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
|
||||
}) {
|
||||
return {
|
||||
getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id),
|
||||
getFeaturesWithin: (layerId, bbox) => {
|
||||
if (layerId === '*' || layerId === null || layerId === undefined) {
|
||||
const feats: Feature[][] = []
|
||||
state.perLayer.forEach((layer) => {
|
||||
feats.push(layer.GetFeaturesWithin(bbox))
|
||||
})
|
||||
return feats
|
||||
}
|
||||
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)];
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that implements that calculates a property and adds this property onto the feature properties
|
||||
* @param specification
|
||||
|
@ -197,28 +203,28 @@ export default class MetaTagging {
|
|||
* @param layerId
|
||||
* @private
|
||||
*/
|
||||
private static createFunctionForFeature( [key, code, isStrict]: [string, string, boolean],
|
||||
private static createFunctionForFeature([key, code, isStrict]: [string, string, boolean],
|
||||
helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>,
|
||||
layerId: string = "unkown layer"
|
||||
): ((feature: GeoJSONFeature) => void) | undefined {
|
||||
): ((feature: GeoJSONFeature, propertiesStore?: UIEventSource<any>) => void) | undefined {
|
||||
if (code === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
|
||||
const calculateAndAssign: ((feat: GeoJSONFeature) => (string | undefined)) = (feat) => {
|
||||
const calculateAndAssign: ((feat: GeoJSONFeature, store?: UIEventSource<any>) => string | any) = (feat, store) => {
|
||||
try {
|
||||
let result = new Function("feat", "{"+ExtraFunctions.types.join(", ")+"}", "return " + code + ";")(feat, helperFunctions)
|
||||
let result = new Function("feat", "{" + ExtraFunctions.types.join(", ") + "}", "return " + code + ";")(feat, helperFunctions)
|
||||
if (result === "") {
|
||||
result = undefined
|
||||
}
|
||||
if (result !== undefined && typeof result !== "string") {
|
||||
// Make sure it is a string!
|
||||
result = JSON.stringify(result)
|
||||
const oldValue= feat.properties[key]
|
||||
if(oldValue == result){
|
||||
return oldValue
|
||||
}
|
||||
delete feat.properties[key]
|
||||
feat.properties[key] = result
|
||||
store?.ping()
|
||||
return result
|
||||
} catch (e) {
|
||||
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
|
||||
|
@ -250,13 +256,12 @@ export default class MetaTagging {
|
|||
}
|
||||
}
|
||||
|
||||
if(isStrict){
|
||||
if (isStrict) {
|
||||
return calculateAndAssign
|
||||
}
|
||||
return (feature: any) => {
|
||||
return (feature: any, store?: UIEventSource<any>) => {
|
||||
delete feature.properties[key]
|
||||
Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature))
|
||||
return undefined
|
||||
Utils.AddLazyProperty(feature.properties, key, () => calculateAndAssign(feature, store))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,19 +271,19 @@ export default class MetaTagging {
|
|||
private static createRetaggingFunc(
|
||||
layer: LayerConfig,
|
||||
helpers: Record<ExtraFuncType, (feature: Feature) => Function>
|
||||
): (feature: any) => boolean {
|
||||
): (feature: Feature, tags: UIEventSource<Record<string, any>>) => boolean {
|
||||
const calculatedTags: [string, string, boolean][] = layer.calculatedTags
|
||||
if (calculatedTags === undefined || calculatedTags.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let functions: ((feature: Feature) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id)
|
||||
let functions: ((feature: Feature, propertiesStore?: UIEventSource<any>) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id)
|
||||
if (functions === undefined) {
|
||||
functions = calculatedTags.map(spec => this.createFunctionForFeature(spec, helpers, layer.id))
|
||||
MetaTagging.retaggingFuncCache.set(layer.id, functions)
|
||||
}
|
||||
|
||||
return (feature: Feature) => {
|
||||
return (feature: Feature, store: UIEventSource<Record<string, any>>) => {
|
||||
const tags = feature.properties
|
||||
if (tags === undefined) {
|
||||
return
|
||||
|
@ -286,7 +291,7 @@ export default class MetaTagging {
|
|||
|
||||
try {
|
||||
for (const f of functions) {
|
||||
f(feature)
|
||||
f(feature, store)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Invalid syntax in calculated tags or some other error: ", e)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OsmCreateAction} from "./OsmChangeAction"
|
||||
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction"
|
||||
import {Tag} from "../../Tags/Tag"
|
||||
import {Changes} from "../Changes"
|
||||
import {ChangeDescription} from "./ChangeDescription"
|
||||
|
@ -6,7 +6,7 @@ import CreateNewWayAction from "./CreateNewWayAction"
|
|||
import CreateWayWithPointReuseAction, {MergePointConfig} from "./CreateWayWithPointReuseAction"
|
||||
import {And} from "../../Tags/And"
|
||||
import {TagUtils} from "../../Tags/TagUtils"
|
||||
import {IndexedFeatureSource} from "../../FeatureSource/FeatureSource"
|
||||
import {FeatureSource, IndexedFeatureSource} from "../../FeatureSource/FeatureSource"
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig";
|
||||
import {Position} from "geojson";
|
||||
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
||||
|
@ -14,7 +14,7 @@ import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullN
|
|||
/**
|
||||
* More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
|
||||
*/
|
||||
export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAction {
|
||||
export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAction implements PreviewableAction {
|
||||
public newElementId: string = undefined
|
||||
public newElementIdNumber: number = undefined
|
||||
private readonly _tags: Tag[]
|
||||
|
@ -67,7 +67,6 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
|
|||
}
|
||||
|
||||
protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||
console.log("Running CMPWPRA")
|
||||
const descriptions: ChangeDescription[] = []
|
||||
descriptions.push(...(await this.createOuterWay.CreateChangeDescriptions(changes)))
|
||||
for (const innerWay of this.createInnerWays) {
|
||||
|
@ -103,4 +102,8 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
|
|||
|
||||
return descriptions
|
||||
}
|
||||
|
||||
getPreview(): Promise<FeatureSource> {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDescription } from "./ChangeDescription"
|
||||
import { OsmCreateAction } from "./OsmChangeAction"
|
||||
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction"
|
||||
import { Changes } from "../Changes"
|
||||
import { Tag } from "../../Tags/Tag"
|
||||
import CreateNewNodeAction from "./CreateNewNodeAction"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {OsmCreateAction} from "./OsmChangeAction"
|
||||
import {OsmCreateAction, PreviewableAction} from "./OsmChangeAction"
|
||||
import {Tag} from "../../Tags/Tag"
|
||||
import {Changes} from "../Changes"
|
||||
import {ChangeDescription} from "./ChangeDescription"
|
||||
|
@ -56,7 +56,7 @@ interface CoordinateInfo {
|
|||
/**
|
||||
* More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
|
||||
*/
|
||||
export default class CreateWayWithPointReuseAction extends OsmCreateAction {
|
||||
export default class CreateWayWithPointReuseAction extends OsmCreateAction implements PreviewableAction {
|
||||
public newElementId: string = undefined
|
||||
public newElementIdNumber: number = undefined
|
||||
private readonly _tags: Tag[]
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import { Changes } from "../Changes"
|
||||
import { ChangeDescription } from "./ChangeDescription"
|
||||
import {FeatureSource} from "../../FeatureSource/FeatureSource";
|
||||
|
||||
export default abstract class OsmChangeAction {
|
||||
public readonly trackStatistics: boolean
|
||||
|
@ -35,3 +36,7 @@ export abstract class OsmCreateAction extends OsmChangeAction {
|
|||
public newElementId: string
|
||||
public newElementIdNumber: number
|
||||
}
|
||||
|
||||
export interface PreviewableAction {
|
||||
getPreview(): Promise<FeatureSource>
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import OsmChangeAction from "./OsmChangeAction"
|
||||
import { Changes } from "../Changes"
|
||||
import { ChangeDescription } from "./ChangeDescription"
|
||||
import { Tag } from "../../Tags/Tag"
|
||||
import { FeatureSource } from "../../FeatureSource/FeatureSource"
|
||||
import { OsmNode, OsmObject, OsmWay } from "../OsmObject"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import OsmChangeAction, {PreviewableAction} from "./OsmChangeAction"
|
||||
import {Changes} from "../Changes"
|
||||
import {ChangeDescription} from "./ChangeDescription"
|
||||
import {Tag} from "../../Tags/Tag"
|
||||
import {FeatureSource} from "../../FeatureSource/FeatureSource"
|
||||
import {OsmNode, OsmObject, OsmWay} from "../OsmObject"
|
||||
import {GeoOperations} from "../../GeoOperations"
|
||||
import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource"
|
||||
import CreateNewNodeAction from "./CreateNewNodeAction"
|
||||
import ChangeTagAction from "./ChangeTagAction"
|
||||
import { And } from "../../Tags/And"
|
||||
import { Utils } from "../../../Utils"
|
||||
import { OsmConnection } from "../OsmConnection"
|
||||
import { Feature } from "@turf/turf"
|
||||
import { Geometry, LineString, Point } from "geojson"
|
||||
import {And} from "../../Tags/And"
|
||||
import {Utils} from "../../../Utils"
|
||||
import {OsmConnection} from "../OsmConnection"
|
||||
import {Feature} from "@turf/turf"
|
||||
import {Geometry, LineString, Point} from "geojson"
|
||||
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
|
||||
|
||||
export default class ReplaceGeometryAction extends OsmChangeAction {
|
||||
export default class ReplaceGeometryAction extends OsmChangeAction implements PreviewableAction{
|
||||
/**
|
||||
* The target feature - mostly used for the metadata
|
||||
*/
|
||||
|
@ -38,9 +38,14 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
private readonly identicalTo: number[]
|
||||
private readonly newTags: Tag[] | undefined
|
||||
|
||||
/**
|
||||
* Not really the 'new' element, but the target that has been applied.
|
||||
* Added for compatibility with other systems
|
||||
*/
|
||||
public readonly newElementId: string
|
||||
constructor(
|
||||
state: {
|
||||
osmConnection: OsmConnection
|
||||
osmConnection: OsmConnection,
|
||||
fullNodeDatabase?: FullNodeDatabaseSource
|
||||
},
|
||||
feature: any,
|
||||
|
@ -55,6 +60,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
this.feature = feature
|
||||
this.wayToReplaceId = wayToReplaceId
|
||||
this.theme = options.theme
|
||||
this.newElementId = wayToReplaceId
|
||||
|
||||
const geom = this.feature.geometry
|
||||
let coordinates: [number, number][]
|
||||
|
@ -81,7 +87,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
this.newTags = options.newTags
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
public async getPreview(): Promise<FeatureSource> {
|
||||
const { closestIds, allNodesById, detachedNodes, reprojectedNodes } =
|
||||
await this.GetClosestIds()
|
||||
|
@ -455,6 +460,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
}
|
||||
}
|
||||
|
||||
console.log("Adding tags", this.newTags,"to conflated way nr", this.wayToReplaceId)
|
||||
if (this.newTags !== undefined && this.newTags.length > 0) {
|
||||
const addExtraTags = new ChangeTagAction(
|
||||
this.wayToReplaceId,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "./OsmObject"
|
||||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
import {OsmNode, OsmObject, OsmRelation, OsmWay} from "./OsmObject"
|
||||
import {Store, UIEventSource} from "../UIEventSource"
|
||||
import Constants from "../../Models/Constants"
|
||||
import OsmChangeAction from "./Actions/OsmChangeAction"
|
||||
import { ChangeDescription, ChangeDescriptionTools } from "./Actions/ChangeDescription"
|
||||
import { Utils } from "../../Utils"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import {ChangeDescription, ChangeDescriptionTools} from "./Actions/ChangeDescription"
|
||||
import {Utils} from "../../Utils"
|
||||
import {LocalStorageSource} from "../Web/LocalStorageSource"
|
||||
import SimpleMetaTagger from "../SimpleMetaTagger"
|
||||
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource/FeatureSource"
|
||||
import { GeoLocationPointProperties } from "../State/GeoLocationState"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler"
|
||||
import { OsmConnection } from "./OsmConnection"
|
||||
import {FeatureSource, IndexedFeatureSource} from "../FeatureSource/FeatureSource"
|
||||
import {GeoLocationPointProperties} from "../State/GeoLocationState"
|
||||
import {GeoOperations} from "../GeoOperations"
|
||||
import {ChangesetHandler, ChangesetTag} from "./ChangesetHandler"
|
||||
import {OsmConnection} from "./OsmConnection"
|
||||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||
import OsmObjectDownloader from "./OsmObjectDownloader"
|
||||
|
||||
|
@ -25,9 +25,9 @@ export class Changes {
|
|||
public readonly state: { allElements?: IndexedFeatureSource; osmConnection: OsmConnection }
|
||||
public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined)
|
||||
public readonly backend: string
|
||||
public readonly isUploading = new UIEventSource(false)
|
||||
private readonly historicalUserLocations?: FeatureSource
|
||||
private _nextId: number = -1 // Newly assigned ID's are negative
|
||||
public readonly isUploading = new UIEventSource(false)
|
||||
private readonly previouslyCreated: OsmObject[] = []
|
||||
private readonly _leftRightSensitive: boolean
|
||||
private readonly _changesetHandler: ChangesetHandler
|
||||
|
@ -246,11 +246,12 @@ export class Changes {
|
|||
switch (change.type) {
|
||||
case "node":
|
||||
// @ts-ignore
|
||||
const nlat = change.changes.lat
|
||||
const nlat = Utils.Round7(change.changes.lat)
|
||||
// @ts-ignore
|
||||
const nlon = change.changes.lon
|
||||
const nlon = Utils.Round7(change.changes.lon)
|
||||
const n = <OsmNode>obj
|
||||
if (n.lat !== nlat || n.lon !== nlon) {
|
||||
console.log("Node moved:", n.lat, nlat, n.lon, nlon)
|
||||
n.lat = nlat
|
||||
n.lon = nlon
|
||||
changed = true
|
||||
|
@ -407,7 +408,7 @@ export class Changes {
|
|||
neededIds.map(async (id) => {
|
||||
try {
|
||||
const osmObj = await downloader.DownloadObjectAsync(id)
|
||||
return { id, osmObj }
|
||||
return {id, osmObj}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Could not download OSM-object",
|
||||
|
@ -421,7 +422,7 @@ export class Changes {
|
|||
|
||||
osmObjects = Utils.NoNull(osmObjects)
|
||||
|
||||
for (const { osmObj, id } of osmObjects) {
|
||||
for (const {osmObj, id} of osmObjects) {
|
||||
if (osmObj === "deleted") {
|
||||
pending = pending.filter((ch) => ch.type + "/" + ch.id !== id)
|
||||
}
|
||||
|
@ -572,9 +573,9 @@ export class Changes {
|
|||
)
|
||||
console.log(
|
||||
"Using current-open-changeset-" +
|
||||
theme +
|
||||
" from the preferences, got " +
|
||||
openChangeset.data
|
||||
theme +
|
||||
" from the preferences, got " +
|
||||
openChangeset.data
|
||||
)
|
||||
|
||||
return await self.flushSelectChanges(pendingChanges, openChangeset)
|
||||
|
|
|
@ -129,9 +129,9 @@ export class ChangesetHandler {
|
|||
const csId = await this.OpenChangeset(extraMetaTags)
|
||||
openChangeset.setData(csId)
|
||||
const changeset = generateChangeXML(csId, this._remappings)
|
||||
console.trace(
|
||||
console.log(
|
||||
"Opened a new changeset (openChangeset.data is undefined):",
|
||||
changeset
|
||||
changeset, extraMetaTags
|
||||
)
|
||||
const changes = await this.UploadChange(csId, changeset)
|
||||
const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(
|
||||
|
|
|
@ -306,22 +306,26 @@ export default class SimpleMetaTaggers {
|
|||
)
|
||||
private static surfaceArea = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_surface", "_surface:ha"],
|
||||
doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways",
|
||||
keys: ["_surface"],
|
||||
doc: "The surface area of the feature in square meters. Not set on points and ways",
|
||||
isLazy: true,
|
||||
},
|
||||
(feature) => {
|
||||
Object.defineProperty(feature.properties, "_surface", {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
get: () => {
|
||||
const sqMeters = "" + GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
delete feature.properties["_surface"]
|
||||
feature.properties["_surface"] = sqMeters
|
||||
return sqMeters
|
||||
},
|
||||
Utils.AddLazyProperty(feature.properties, "_surface", () => {
|
||||
return "" + GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
)
|
||||
private static surfaceAreaHa = new InlineMetaTagger(
|
||||
{
|
||||
keys: ["_surface:ha"],
|
||||
doc: "The surface area of the feature in hectare. Not set on points and ways",
|
||||
isLazy: true,
|
||||
},
|
||||
(feature) => {
|
||||
Utils.AddLazyProperty(feature.properties, "_surface:ha", () => {
|
||||
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature)
|
||||
return "" + Math.floor(sqMeters / 1000) / 10
|
||||
|
@ -581,6 +585,7 @@ export default class SimpleMetaTaggers {
|
|||
SimpleMetaTaggers.latlon,
|
||||
SimpleMetaTaggers.layerInfo,
|
||||
SimpleMetaTaggers.surfaceArea,
|
||||
SimpleMetaTaggers.surfaceAreaHa,
|
||||
SimpleMetaTaggers.lngth,
|
||||
SimpleMetaTaggers.canonicalize,
|
||||
SimpleMetaTaggers.country,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Tag } from "./Tag"
|
||||
import { TagsFilter } from "./TagsFilter"
|
||||
import {Tag} from "./Tag"
|
||||
import {TagsFilter} from "./TagsFilter"
|
||||
|
||||
export class RegexTag extends TagsFilter {
|
||||
public readonly key: RegExp | string
|
||||
|
@ -15,7 +15,20 @@ export class RegexTag extends TagsFilter {
|
|||
this.matchesEmpty = RegexTag.doesMatch("", this.value)
|
||||
}
|
||||
|
||||
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
|
||||
/**
|
||||
*
|
||||
* Checks that the value provided by the object properties (`fromTag`) match the specified regex `possibleRegex
|
||||
*
|
||||
* RegexTag.doesMatch("abc", /abc/) // => true
|
||||
* RegexTag.doesMatch("ab", /abc/) // => false
|
||||
* RegexTag.doesMatch("", /.+/) // => false
|
||||
* RegexTag.doesMatch("", new RegExp(".*")) // => true
|
||||
*
|
||||
* @param fromTag
|
||||
* @param possibleRegex
|
||||
* @private
|
||||
*/
|
||||
private static doesMatch(fromTag: string | number, possibleRegex: string | RegExp): boolean {
|
||||
if (fromTag === undefined) {
|
||||
return
|
||||
}
|
||||
|
@ -25,11 +38,8 @@ export class RegexTag extends TagsFilter {
|
|||
if (typeof possibleRegex === "string") {
|
||||
return fromTag === possibleRegex
|
||||
}
|
||||
if (typeof fromTag.match !== "function") {
|
||||
console.error("Error: fromTag is not a regex: ", fromTag, possibleRegex)
|
||||
throw "Error: fromTag is not a regex: " + fromTag + possibleRegex
|
||||
}
|
||||
return fromTag.match(possibleRegex) !== null
|
||||
return possibleRegex.test(fromTag)
|
||||
|
||||
}
|
||||
|
||||
private static source(r: string | RegExp) {
|
||||
|
@ -125,10 +135,38 @@ export class RegexTag extends TagsFilter {
|
|||
*
|
||||
* new RegexTag("key","value").matchesProperties({"otherkey":"value"}) // => false
|
||||
* new RegexTag("key","value",true).matchesProperties({"otherkey":"something"}) // => true
|
||||
*
|
||||
* const v: string = <any> {someJson: ""}
|
||||
* new RegexTag("key", new RegExp(".*")).matchesProperties({"key": v}) // => true
|
||||
* new RegexTag("key", new RegExp(".*")).matchesProperties({"key": ""}) // => true
|
||||
* new RegexTag("key", new RegExp(".*")).matchesProperties({"key": null}) // => true
|
||||
* new RegexTag("key", new RegExp(".*")).matchesProperties({"key": undefined}) // => true
|
||||
*
|
||||
* const v: string = <any> {someJson: ""}
|
||||
* new RegexTag("key", new RegExp(".+")).matchesProperties({"key": null}) // => false
|
||||
* new RegexTag("key", new RegExp(".+")).matchesProperties({"key": undefined}) // => false
|
||||
* new RegexTag("key", new RegExp(".+")).matchesProperties({"key": v}) // => false
|
||||
* new RegexTag("key", new RegExp(".+")).matchesProperties({"key": ""}) // => false
|
||||
*/
|
||||
matchesProperties(tags: Record<string, string>): boolean {
|
||||
if (typeof this.key === "string") {
|
||||
const value = tags[this.key] ?? ""
|
||||
const value = tags[this.key]
|
||||
if(!value || value === ""){
|
||||
// No tag is known, so we assume the empty string
|
||||
// If this regexTag matches the empty string, we return true, otherwise false
|
||||
// (Note: if inverted, we must reverse this)
|
||||
return this.invert !== this.matchesEmpty
|
||||
}
|
||||
if(typeof value !== "string"){
|
||||
if(typeof this.value !== "string"){
|
||||
const regExp = this.value
|
||||
if(regExp.source === ".*"){
|
||||
// We match anything, and we do have a value
|
||||
return !this.invert
|
||||
}
|
||||
return RegexTag.doesMatch(value, JSON.stringify(this.value)) != this.invert
|
||||
}
|
||||
}
|
||||
return RegexTag.doesMatch(value, this.value) != this.invert
|
||||
}
|
||||
|
||||
|
|
|
@ -35,15 +35,27 @@ export class Tag extends TagsFilter {
|
|||
* isEmpty.matchesProperties({"other_key": "value"}) // => true
|
||||
* isEmpty.matchesProperties({"key": undefined}) // => true
|
||||
*
|
||||
* const isTrue = new Tag("key", "true")
|
||||
* isTrue.matchesProperties({"key","true"}) // => true
|
||||
* isTrue.matchesProperteis({"key", true}) // => true
|
||||
*/
|
||||
matchesProperties(properties: Record<string, string>): boolean {
|
||||
const foundValue = properties[this.key]
|
||||
let foundValue = properties[this.key]
|
||||
|
||||
if (foundValue === undefined && (this.value === "" || this.value === undefined)) {
|
||||
// The tag was not found
|
||||
// and it shouldn't be found!
|
||||
return true
|
||||
}
|
||||
|
||||
if(typeof foundValue !== "string"){
|
||||
if(foundValue === true && (this.value === "true" || this.value === "yes" )){
|
||||
return true
|
||||
}
|
||||
if(foundValue === false && (this.value === "false" || this.value === "no" )){
|
||||
return true
|
||||
}
|
||||
foundValue = ""+foundValue
|
||||
}
|
||||
return foundValue === this.value
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export class IdbLocalStorage {
|
|||
return idb.set(key, copy)
|
||||
}
|
||||
|
||||
static GetDirectly(key: string): Promise<void> {
|
||||
static GetDirectly(key: string): Promise<any> {
|
||||
return idb.get(key)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue