forked from MapComplete/MapComplete
Feature: add favourite
This commit is contained in:
parent
a32ab16a5e
commit
f9827dd6ae
68 changed files with 1641 additions and 885 deletions
157
src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts
Normal file
157
src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
import StaticFeatureSource from "./StaticFeatureSource"
|
||||
import { Feature } from "geojson"
|
||||
import { Store, UIEventSource } from "../../UIEventSource"
|
||||
import { OsmConnection } from "../../Osm/OsmConnection"
|
||||
import { OsmId } from "../../../Models/OsmFeature"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore"
|
||||
import { IndexedFeatureSource } from "../FeatureSource"
|
||||
import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"
|
||||
|
||||
/**
|
||||
* Generates the favourites from the preferences and marks them as favourite
|
||||
*/
|
||||
export default class FavouritesFeatureSource extends StaticFeatureSource {
|
||||
public static readonly prefix = "mapcomplete-favourite-"
|
||||
private readonly _osmConnection: OsmConnection
|
||||
|
||||
constructor(
|
||||
connection: OsmConnection,
|
||||
indexedSource: FeaturePropertiesStore,
|
||||
allFeatures: IndexedFeatureSource,
|
||||
layout: LayoutConfig
|
||||
) {
|
||||
const detectedIds = new UIEventSource<Set<string>>(undefined)
|
||||
const features: Store<Feature[]> = connection.preferencesHandler.preferences.map(
|
||||
(prefs) => {
|
||||
const feats: Feature[] = []
|
||||
const allIds = new Set<string>()
|
||||
for (const key in prefs) {
|
||||
if (!key.startsWith(FavouritesFeatureSource.prefix)) {
|
||||
continue
|
||||
}
|
||||
const id = key.substring(FavouritesFeatureSource.prefix.length)
|
||||
const osmId = id.replace("-", "/")
|
||||
if (id.indexOf("-property-") > 0 || id.indexOf("-layer") > 0) {
|
||||
continue
|
||||
}
|
||||
allIds.add(osmId)
|
||||
const geometry = <[number, number]>JSON.parse(prefs[key])
|
||||
const properties = FavouritesFeatureSource.getPropertiesFor(connection, id)
|
||||
properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"]
|
||||
if (layout.layers.some((l) => l.id === properties._orig_layer)) {
|
||||
continue
|
||||
}
|
||||
properties.id = osmId
|
||||
properties._favourite = "yes"
|
||||
feats.push({
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: geometry,
|
||||
},
|
||||
})
|
||||
}
|
||||
console.log("Favouritess are", feats)
|
||||
detectedIds.setData(allIds)
|
||||
return feats
|
||||
}
|
||||
)
|
||||
|
||||
super(features)
|
||||
|
||||
this._osmConnection = connection
|
||||
detectedIds.addCallbackAndRunD((detected) =>
|
||||
this.markFeatures(detected, indexedSource, allFeatures)
|
||||
)
|
||||
// We use the indexedFeatureSource as signal to update
|
||||
allFeatures.features.map((_) =>
|
||||
this.markFeatures(detectedIds.data, indexedSource, allFeatures)
|
||||
)
|
||||
}
|
||||
|
||||
private static getPropertiesFor(
|
||||
osmConnection: OsmConnection,
|
||||
id: string
|
||||
): Record<string, string> {
|
||||
const properties: Record<string, string> = {}
|
||||
const prefs = osmConnection.preferencesHandler.preferences.data
|
||||
const minLength = FavouritesFeatureSource.prefix.length + id.length + "-property-".length
|
||||
for (const key in prefs) {
|
||||
if (key.length < minLength) {
|
||||
continue
|
||||
}
|
||||
if (!key.startsWith(FavouritesFeatureSource.prefix + id)) {
|
||||
continue
|
||||
}
|
||||
const propertyName = key.substring(minLength)
|
||||
properties[propertyName] = prefs[key]
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
public markAsFavourite(
|
||||
feature: Feature,
|
||||
layer: string,
|
||||
theme: string,
|
||||
tags: UIEventSource<Record<string, string> & { id: OsmId }>,
|
||||
isFavourite: boolean = true
|
||||
) {
|
||||
{
|
||||
const id = tags.data.id.replace("/", "-")
|
||||
const pref = this._osmConnection.GetPreference("favourite-" + id)
|
||||
if (isFavourite) {
|
||||
const center = GeoOperations.centerpointCoordinates(feature)
|
||||
pref.setData(JSON.stringify(center))
|
||||
this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer)
|
||||
this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme)
|
||||
|
||||
for (const key in tags.data) {
|
||||
const pref = this._osmConnection.GetPreference(
|
||||
"favourite-" + id + "-property-" + key.replaceAll(":", "__")
|
||||
)
|
||||
pref.setData(tags.data[key])
|
||||
}
|
||||
} else {
|
||||
this._osmConnection.preferencesHandler.removeAllWithPrefix(
|
||||
"mapcomplete-favourite-" + id
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isFavourite) {
|
||||
tags.data._favourite = "yes"
|
||||
tags.ping()
|
||||
} else {
|
||||
delete tags.data._favourite
|
||||
tags.ping()
|
||||
}
|
||||
}
|
||||
|
||||
private markFeatures(
|
||||
detected: Set<string>,
|
||||
featureProperties: FeaturePropertiesStore,
|
||||
allFeatures: IndexedFeatureSource
|
||||
) {
|
||||
const feature = allFeatures.features.data
|
||||
for (const f of feature) {
|
||||
const id = f.properties.id
|
||||
if (!id) {
|
||||
continue
|
||||
}
|
||||
const store = featureProperties.getStore(id)
|
||||
const origValue = store.data._favourite
|
||||
if (detected.has(id)) {
|
||||
if (origValue !== "yes") {
|
||||
store.data._favourite = "yes"
|
||||
store.ping()
|
||||
}
|
||||
} else {
|
||||
if (origValue) {
|
||||
store.data._favourite = ""
|
||||
store.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,14 @@ import FilteringFeatureSource from "./FilteringFeatureSource"
|
|||
import LayerState from "../../State/LayerState"
|
||||
|
||||
export default class NearbyFeatureSource implements FeatureSource {
|
||||
private readonly _result = new UIEventSource<Feature[]>(undefined)
|
||||
|
||||
public readonly features: Store<Feature[]>
|
||||
private readonly _targetPoint: Store<{ lon: number; lat: number }>
|
||||
private readonly _numberOfNeededFeatures: number
|
||||
private readonly _layerState?: LayerState
|
||||
private readonly _currentZoom: Store<number>
|
||||
private readonly _allSources: Store<{ feat: Feature; d: number }[]>[] = []
|
||||
|
||||
constructor(
|
||||
targetPoint: Store<{ lon: number; lat: number }>,
|
||||
|
@ -18,41 +22,44 @@ export default class NearbyFeatureSource implements FeatureSource {
|
|||
layerState?: LayerState,
|
||||
currentZoom?: Store<number>
|
||||
) {
|
||||
this._layerState = layerState
|
||||
this._targetPoint = targetPoint.stabilized(100)
|
||||
this._numberOfNeededFeatures = numberOfNeededFeatures
|
||||
this._currentZoom = currentZoom.stabilized(500)
|
||||
|
||||
const allSources: Store<{ feat: Feature; d: number }[]>[] = []
|
||||
this.features = Stores.ListStabilized(this._result)
|
||||
|
||||
sources.forEach((source, layer) => {})
|
||||
}
|
||||
|
||||
public registerSource(source: FeatureSource, layerId: string) {
|
||||
let minzoom = 999
|
||||
|
||||
const result = new UIEventSource<Feature[]>(undefined)
|
||||
this.features = Stores.ListStabilized(result)
|
||||
|
||||
function update() {
|
||||
let features: { feat: Feature; d: number }[] = []
|
||||
for (const src of allSources) {
|
||||
features.push(...src.data)
|
||||
}
|
||||
features.sort((a, b) => a.d - b.d)
|
||||
if (numberOfNeededFeatures !== undefined) {
|
||||
features = features.slice(0, numberOfNeededFeatures)
|
||||
}
|
||||
result.setData(features.map((f) => f.feat))
|
||||
const flayer = this._layerState?.filteredLayers.get(layerId)
|
||||
if (!flayer) {
|
||||
return
|
||||
}
|
||||
|
||||
sources.forEach((source, layer) => {
|
||||
const flayer = layerState?.filteredLayers.get(layer)
|
||||
minzoom = Math.min(minzoom, flayer.layerDef.minzoom)
|
||||
const calcSource = this.createSource(
|
||||
source.features,
|
||||
flayer.layerDef.minzoom,
|
||||
flayer.isDisplayed
|
||||
)
|
||||
calcSource.addCallbackAndRunD((features) => {
|
||||
update()
|
||||
})
|
||||
allSources.push(calcSource)
|
||||
minzoom = Math.min(minzoom, flayer.layerDef.minzoom)
|
||||
const calcSource = this.createSource(
|
||||
source.features,
|
||||
flayer.layerDef.minzoom,
|
||||
flayer.isDisplayed
|
||||
)
|
||||
calcSource.addCallbackAndRunD((features) => {
|
||||
this.update()
|
||||
})
|
||||
this._allSources.push(calcSource)
|
||||
}
|
||||
|
||||
private update() {
|
||||
let features: { feat: Feature; d: number }[] = []
|
||||
for (const src of this._allSources) {
|
||||
features.push(...src.data)
|
||||
}
|
||||
features.sort((a, b) => a.d - b.d)
|
||||
if (this._numberOfNeededFeatures !== undefined) {
|
||||
features = features.slice(0, this._numberOfNeededFeatures)
|
||||
}
|
||||
this._result.setData(features.map((f) => f.feat))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -285,4 +285,13 @@ export class OsmPreferences {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
removeAllWithPrefix(prefix: string) {
|
||||
for (const key in this.preferences.data) {
|
||||
if (key.startsWith(prefix)) {
|
||||
this.GetPreference(key, undefined, { prefix: "" }).setData(undefined)
|
||||
console.log("Clearing preference", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue