Refactoring: split 'ThemeViewState' into many classes

This commit is contained in:
Pieter Vander Vennet 2025-01-23 05:01:55 +01:00
parent 2b858bd2aa
commit dbcbf2787d
34 changed files with 1503 additions and 1227 deletions

View file

@ -2,18 +2,17 @@ import { QueryParameters } from "../Web/QueryParameters"
import { BBox } from "../BBox"
import Constants from "../../Models/Constants"
import { GeoLocationState } from "../State/GeoLocationState"
import { UIEventSource } from "../UIEventSource"
import { Store, UIEventSource } from "../UIEventSource"
import { Feature, LineString, Point } from "geojson"
import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSource"
import { LocalStorageSource } from "../Web/LocalStorageSource"
import { GeoOperations } from "../GeoOperations"
import { OsmTags } from "../../Models/OsmFeature"
import StaticFeatureSource, {
WritableStaticFeatureSource,
} from "../FeatureSource/Sources/StaticFeatureSource"
import StaticFeatureSource, { WritableStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource"
import { MapProperties } from "../../Models/MapProperties"
import { Orientation } from "../../Sensors/Orientation"
;("use strict")
("use strict")
/**
* The geolocation-handler takes a map-location and a geolocation state.
* It'll move the map as appropriate given the state of the geolocation-API
@ -45,14 +44,14 @@ export default class GeoLocationHandler {
public readonly mapHasMoved: UIEventSource<Date | undefined> = new UIEventSource<
Date | undefined
>(undefined)
private readonly selectedElement: UIEventSource<Feature>
private readonly mapProperties?: MapProperties
private readonly selectedElement: Store<object>
private readonly mapProperties: MapProperties
private readonly gpsLocationHistoryRetentionTime?: UIEventSource<number>
constructor(
geolocationState: GeoLocationState,
selectedElement: UIEventSource<Feature>,
mapProperties?: MapProperties,
selectedElement: Store<object>,
mapProperties: MapProperties,
gpsLocationHistoryRetentionTime?: UIEventSource<number>
) {
this.geolocationState = geolocationState
@ -62,7 +61,7 @@ export default class GeoLocationHandler {
this.gpsLocationHistoryRetentionTime = gpsLocationHistoryRetentionTime
// Did an interaction move the map?
const initTime = new Date()
mapLocation.addCallbackD(() => {
mapLocation?.addCallbackD(() => {
if (new Date().getTime() - initTime.getTime() < 250) {
return
}
@ -139,7 +138,7 @@ export default class GeoLocationHandler {
}
}
mapLocation.setData({
mapLocation?.setData({
lon: newLocation.longitude,
lat: newLocation.latitude,
})

View file

@ -1,7 +1,7 @@
import { BBox } from "../BBox"
import { Store } from "../UIEventSource"
import ThemeViewState from "../../Models/ThemeViewState"
import Constants from "../../Models/Constants"
import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState"
export type FeatureViewState =
| "no-data"
@ -11,7 +11,7 @@ export type FeatureViewState =
export default class NoElementsInViewDetector {
public readonly hasFeatureInView: Store<FeatureViewState>
constructor(themeViewState: ThemeViewState) {
constructor(themeViewState: WithChangesState) {
const state = themeViewState
const minZoom = Math.min(
...themeViewState.theme.layers
@ -32,7 +32,6 @@ export default class NoElementsInViewDetector {
return "zoom-to-low"
}
let minzoomWithData = 9999
for (const [layerName, source] of themeViewState.perLayerFiltered) {
if (priviliged.has(layerName)) {
@ -45,7 +44,6 @@ export default class NoElementsInViewDetector {
}
const layer = themeViewState.theme.getLayer(layerName)
if (mapProperties.zoom.data < layer.minzoom) {
minzoomWithData = Math.min(layer.minzoom)
continue
}
if (!state.layerState.filteredLayers.get(layerName).isDisplayed.data) {

View file

@ -4,10 +4,12 @@
import SimpleMetaTagger from "../SimpleMetaTagger"
import { OsmTags } from "../../Models/OsmFeature"
import { Utils } from "../../Utils"
import ThemeViewState from "../../Models/ThemeViewState"
import { BBox } from "../BBox"
import { Feature } from "geojson"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
import { Changes } from "../Osm/Changes"
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState"
export default class SelectedElementTagsUpdater {
private static readonly metatags = new Set([
@ -18,9 +20,9 @@ export default class SelectedElementTagsUpdater {
"uid",
"id",
])
private readonly state: ThemeViewState
private readonly state: WithChangesState
constructor(state: ThemeViewState) {
constructor(state: WithChangesState) {
this.state = state
state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => {
if (!isLoggedIn && !Utils.runningFromConsole) {
@ -32,7 +34,11 @@ export default class SelectedElementTagsUpdater {
})
}
public static applyUpdate(latestTags: OsmTags, id: string, state: SpecialVisualizationState) {
public static applyUpdate(latestTags: OsmTags, id: string, state: {
theme: ThemeConfig,
changes: Changes,
featureProperties: FeaturePropertiesStore
}) {
try {
const leftRightSensitive = state.theme.isLeftRightSensitive()

View file

@ -6,8 +6,11 @@ import { OsmId } from "../../../Models/OsmFeature"
import { GeoOperations } from "../../GeoOperations"
import { IndexedFeatureSource } from "../FeatureSource"
import OsmObjectDownloader from "../../Osm/OsmObjectDownloader"
import { SpecialVisualizationState } from "../../../UI/SpecialVisualization"
import SelectedElementTagsUpdater from "../../Actors/SelectedElementTagsUpdater"
import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore"
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../Osm/Changes"
import { WithChangesState } from "../../../Models/ThemeViewState/WithChangesState"
/**
* Generates the favourites from the preferences and marks them as favourite
@ -22,7 +25,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
*/
public readonly allFavourites: Store<Feature[]>
constructor(state: SpecialVisualizationState) {
constructor(state: WithChangesState) {
const features: Store<Feature[]> = Stores.ListStabilized(
state.osmConnection.preferencesHandler.allPreferences.map((prefs) => {
const feats: Feature[] = []
@ -71,7 +74,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
this.allFavourites.addCallbackD((features) => {
for (const feature of features) {
this.updateFeature(feature, state.osmObjectDownloader, state)
this.updateFeature(feature, state)
}
return true
@ -80,11 +83,15 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
private async updateFeature(
feature: Feature,
osmObjectDownloader: OsmObjectDownloader,
state: SpecialVisualizationState
state: {
theme: ThemeConfig,
changes: Changes,
featureProperties: FeaturePropertiesStore,
osmObjectDownloader: OsmObjectDownloader,
}
) {
const id = feature.properties.id
const upstream = await osmObjectDownloader.DownloadObjectAsync(id)
const upstream = await state.osmObjectDownloader.DownloadObjectAsync(id)
if (upstream === "deleted") {
this.removeFavourite(feature)
return

View file

@ -46,7 +46,7 @@ export class Changes {
constructor(
state: {
featureSwitches: {
featureSwitches?: {
featureSwitchMorePrivacy?: Store<boolean>
featureSwitchIsTesting?: Store<boolean>
}
@ -56,8 +56,7 @@ export class Changes {
historicalUserLocations?: FeatureSource
allElements?: IndexedFeatureSource
},
leftRightSensitive: boolean = false,
reportError?: (string: string | Error, extramessage?: string) => void
leftRightSensitive: boolean = false
) {
this._leftRightSensitive = leftRightSensitive
// We keep track of all changes just as well
@ -73,7 +72,7 @@ export class Changes {
}
this.state = state
this.backend = state.osmConnection.Backend()
this._reportError = reportError
this._reportError = state.reportError
this._changesetHandler = new ChangesetHandler(
state.featureSwitches?.featureSwitchIsTesting ?? new ImmutableStore(false),
state.osmConnection,

View file

@ -1,8 +1,8 @@
import { Store, UIEventSource } from "../UIEventSource"
import GeocodingProvider, { GeocodingOptions, GeocodeResult } from "./GeocodingProvider"
import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingProvider"
import { OsmId } from "../../Models/OsmFeature"
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
import { Utils } from "../../Utils"
import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
export default class OpenStreetMapIdSearch implements GeocodingProvider {
private static readonly regex =
@ -13,11 +13,11 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider {
w: "way",
r: "relation",
}
private readonly _osmObjectDownloader: OsmObjectDownloader
private readonly _state: SpecialVisualizationState
constructor(state: SpecialVisualizationState) {
this._state = state
constructor(osmObjectDownloader: OsmObjectDownloader) {
this._osmObjectDownloader = osmObjectDownloader
}
/**
@ -49,7 +49,7 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider {
private async getInfoAbout(id: OsmId): Promise<GeocodeResult> {
const [osm_type, osm_id] = id.split("/")
const obj = await this._state.osmObjectDownloader.DownloadObjectAsync(id)
const obj = await this._osmObjectDownloader.DownloadObjectAsync(id)
if (obj === "deleted") {
return {
display_name: id + " was deleted",
@ -74,13 +74,13 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider {
}
}
async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> {
async search(query: string, _: GeocodingOptions): Promise<GeocodeResult[]> {
if (!isNaN(Number(query))) {
const n = Number(query)
return Utils.NoNullInplace(
await Promise.all([
this.getInfoAbout(`node/${n}`).catch((x) => undefined),
this.getInfoAbout(`way/${n}`).catch((x) => undefined),
this.getInfoAbout(`node/${n}`).catch(() => undefined),
this.getInfoAbout(`way/${n}`).catch(() => undefined),
this.getInfoAbout(`relation/${n}`).catch(() => undefined),
])
)

View file

@ -39,9 +39,9 @@ export default class SearchState {
new LocalElementSearch(state, 5),
new CoordinateSearch(),
new OpenLocationCodeSearch(),
new OpenStreetMapIdSearch(state),
new OpenStreetMapIdSearch(state.osmObjectDownloader),
new PhotonSearch(true, 2),
new PhotonSearch(),
new PhotonSearch()
// new NominatimGeocoding(),
]
@ -122,7 +122,7 @@ export default class SearchState {
const layersToShow = payload.map((fsr) => fsr.layer.id)
console.log("Layers to show are", layersToShow)
for (const [name, otherLayer] of state.layerState.filteredLayers) {
for (const otherLayer of state.layerState.filteredLayers.values()) {
const layer = otherLayer.layerDef
if (!layer.isNormal()) {
continue

View file

@ -17,7 +17,6 @@ import FeatureSwitchState from "./FeatureSwitchState"
import Constants from "../../Models/Constants"
import { QueryParameters } from "../Web/QueryParameters"
import { ThemeMetaTagging } from "./UserSettingsMetaTagging"
import { MapProperties } from "../../Models/MapProperties"
import Showdown from "showdown"
import { LocalStorageSource } from "../Web/LocalStorageSource"
import { GeocodeResult } from "../Search/GeocodingProvider"
@ -53,7 +52,7 @@ export class OptionallySyncedHistory<T> {
))
this.syncPreference.addCallback((syncmode) => {
if (syncmode === "sync") {
let list = [...thisSession.data, ...synced.data].slice(0, maxHistory)
const list = [...thisSession.data, ...synced.data].slice(0, maxHistory)
if (this._isSame) {
for (let i = 0; i < list.length; i++) {
for (let j = i + 1; j < list.length; j++) {
@ -178,7 +177,6 @@ export default class UserRelatedState {
* Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource
*/
public readonly preferencesAsTags: UIEventSource<Record<string, string>>
private readonly _mapProperties: MapProperties
public readonly recentlyVisitedThemes: OptionallySyncedHistory<string>
public readonly recentlyVisitedSearch: OptionallySyncedHistory<GeocodeResult>
@ -187,10 +185,9 @@ export default class UserRelatedState {
osmConnection: OsmConnection,
layout?: ThemeConfig,
featureSwitches?: FeatureSwitchState,
mapProperties?: MapProperties
currentRasterLayer?: Store<{ properties: { id: string } }>
) {
this.osmConnection = osmConnection
this._mapProperties = mapProperties
this.showAllQuestionsAtOnce = UIEventSource.asBoolean(
this.osmConnection.getPreference("show-all-questions", "false")
@ -224,7 +221,7 @@ export default class UserRelatedState {
this.translationMode = this.initTranslationMode()
this.homeLocation = this.initHomeLocation()
this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches)
this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches, currentRasterLayer)
this.recentlyVisitedThemes = new OptionallySyncedHistory<string>(
"theme",
@ -405,7 +402,8 @@ export default class UserRelatedState {
* */
private initAmendedPrefs(
layout?: ThemeConfig,
featureSwitches?: FeatureSwitchState
featureSwitches?: FeatureSwitchState,
currentRasterLayer?: Store<{ properties: { id: string } }>
): UIEventSource<Record<string, string>> {
const amendedPrefs = new UIEventSource<Record<string, string>>({
_theme: layout?.id,
@ -541,7 +539,7 @@ export default class UserRelatedState {
if (tags[key] === null) {
continue
}
let pref = this.osmConnection.GetPreference(key, undefined, { prefix: "" })
const pref = this.osmConnection.GetPreference(key, undefined, { prefix: "" })
pref.set(tags[key])
}
@ -560,7 +558,7 @@ export default class UserRelatedState {
}
}
this._mapProperties?.rasterLayer?.addCallbackAndRun((l) => {
currentRasterLayer?.addCallbackAndRun((l) => {
amendedPrefs.data["__current_background"] = l?.properties?.id
amendedPrefs.ping()
})

View file

@ -1,9 +1,16 @@
import ThemeViewState from "../../Models/ThemeViewState"
import Hash from "./Hash"
import { MenuState } from "../../Models/MenuState"
import { IndexedFeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"
import { UIEventSource } from "../UIEventSource"
export default class ThemeViewStateHashActor {
private readonly _state: ThemeViewState
private readonly _state: {
indexedFeatures: IndexedFeatureSource,
selectedElement: UIEventSource<Feature>,
guistate: MenuState,
previewedImage: UIEventSource<object>
}
private isUpdatingHash = false
public static readonly documentation = [
@ -16,7 +23,7 @@ export default class ThemeViewStateHashActor {
"",
"The possible hashes are:",
"",
MenuState.pageNames.map((tab) => "`" + tab + "`").join(","),
MenuState.pageNames.map((tab) => "`" + tab + "`").join(",")
]
/**
@ -28,7 +35,12 @@ export default class ThemeViewStateHashActor {
* As such, we use a change in the hash to close the appropriate windows
*
*/
constructor(state: ThemeViewState) {
constructor(state: {
indexedFeatures: IndexedFeatureSource,
selectedElement: UIEventSource<Feature>,
guistate: MenuState,
previewedImage: UIEventSource<object>
}) {
this._state = state
const hashOnLoad = Hash.hash.data