diff --git a/src/Logic/FeatureSource/Sources/ThemeSource.ts b/src/Logic/FeatureSource/Sources/ThemeSource.ts index 40654b5acb..e9b54aecfc 100644 --- a/src/Logic/FeatureSource/Sources/ThemeSource.ts +++ b/src/Logic/FeatureSource/Sources/ThemeSource.ts @@ -1,6 +1,6 @@ import GeoJsonSource from "./GeoJsonSource" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" -import { FeatureSource, UpdatableFeatureSource } from "../FeatureSource" +import { FeatureSource, IndexedFeatureSource, UpdatableFeatureSource } from "../FeatureSource" import { Or } from "../../Tags/Or" import FeatureSwitchState from "../../State/FeatureSwitchState" import OverpassFeatureSource from "./OverpassFeatureSource" @@ -12,13 +12,15 @@ import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeature import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource" import DynamicMvtileSource from "../TiledFeatureSource/DynamicMvtTileSource" import FeatureSourceMerger from "./FeatureSourceMerger" +import { Feature } from "geojson" +import { OsmFeature } from "../../../Models/OsmFeature" /** * This source will fetch the needed data from various sources for the given layout. * * Note that special layers (with `source=null` will be ignored) */ -export default class ThemeSource extends FeatureSourceMerger { +export default class ThemeSource implements IndexedFeatureSource { /** * Indicates if a data source is loading something */ @@ -26,6 +28,61 @@ export default class ThemeSource extends FeatureSourceMerger { public static readonly fromCacheZoomLevel = 15 + public features: UIEventSource = new UIEventSource([]) + public readonly featuresById: Store> + private readonly core: Store + + + private readonly addedSources: FeatureSource[] = [] + private readonly addedItems: OsmFeature[] = [] + + constructor( + layers: LayerConfig[], + featureSwitches: FeatureSwitchState, + mapProperties: { bounds: Store; zoom: Store }, + backend: string, + isDisplayed: (id: string) => Store, + mvtAvailableLayers: Store>, + fullNodeDatabaseSource?: FullNodeDatabaseSource + ) { + const isLoading = new UIEventSource(true) + this.isLoading = isLoading + + const features = this.features = new UIEventSource([]) + const featuresById = this.featuresById = new UIEventSource(new Map()) + this.core = mvtAvailableLayers.mapD(mvtAvailableLayers => { + const core = new ThemeSourceCore(layers, featureSwitches, mapProperties, backend, isDisplayed, mvtAvailableLayers, isLoading, fullNodeDatabaseSource) + this.addedSources.forEach(src => core.addSource(src)) + this.addedItems.forEach(item => core.addItem(item)) + core.features.addCallbackAndRun(data => features.set(data)) + core.featuresById.addCallbackAndRun(data => featuresById.set(data)) + return core + }) + } + + public async downloadAll() { + return this.core.data.downloadAll() + } + + public addSource(source: FeatureSource) { + this.core.data?.addSource(source) + this.addedSources.push(source) + } + + + public addItem(obj: OsmFeature) { + this.core.data?.addItem(obj) + this.addedItems.push(obj) + } +} + +/** + * This source will fetch the needed data from various sources for the given layout. + * + * Note that special layers (with `source=null` will be ignored) + */ +class ThemeSourceCore extends FeatureSourceMerger { + /** * This source is _only_ triggered when the data is downloaded for CSV export * @private @@ -40,6 +97,7 @@ export default class ThemeSource extends FeatureSourceMerger { backend: string, isDisplayed: (id: string) => Store, mvtAvailableLayers: Set, + isLoading: UIEventSource, fullNodeDatabaseSource?: FullNodeDatabaseSource ) { const { bounds, zoom } = mapProperties @@ -58,7 +116,7 @@ export default class ThemeSource extends FeatureSourceMerger { mapProperties, { isActive: isDisplayed(layer.id), - maxAge: layer.maxAgeOfCache, + maxAge: layer.maxAgeOfCache } ) fromCache.set(layer.id, src) @@ -66,13 +124,11 @@ export default class ThemeSource extends FeatureSourceMerger { } const mvtSources: UpdatableFeatureSource[] = osmLayers .filter((f) => mvtAvailableLayers.has(f.id)) - .map((l) => ThemeSource.setupMvtSource(l, mapProperties, isDisplayed(l.id))) + .map((l) => ThemeSourceCore.setupMvtSource(l, mapProperties, isDisplayed(l.id))) const nonMvtSources: FeatureSource[] = [] const nonMvtLayers: LayerConfig[] = osmLayers.filter((l) => !mvtAvailableLayers.has(l.id)) - const isLoading = new UIEventSource(false) - - const osmApiSource = ThemeSource.setupOsmApiSource( + const osmApiSource = ThemeSourceCore.setupOsmApiSource( osmLayers, bounds, zoom, @@ -89,7 +145,7 @@ export default class ThemeSource extends FeatureSourceMerger { nonMvtLayers.map((l) => l.id), " cannot be fetched from the cache server, defaulting to overpass/OSM-api" ) - overpassSource = ThemeSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) + overpassSource = ThemeSourceCore.setupOverpass(osmLayers, bounds, zoom, featureSwitches) nonMvtSources.push(overpassSource) } @@ -102,7 +158,7 @@ export default class ThemeSource extends FeatureSourceMerger { osmApiSource?.isRunning?.addCallbackAndRun(() => setIsLoading()) const geojsonSources: UpdatableFeatureSource[] = geojsonlayers.map((l) => - ThemeSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) + ThemeSourceCore.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) ) const downloadAll = new OverpassFeatureSource( @@ -113,11 +169,11 @@ export default class ThemeSource extends FeatureSourceMerger { overpassUrl: featureSwitches.overpassUrl, overpassTimeout: featureSwitches.overpassTimeout, overpassMaxZoom: new ImmutableStore(99), - widenFactor: 0, + widenFactor: 0 }, { ignoreZoom: true, - isActive: new ImmutableStore(false), + isActive: new ImmutableStore(false) } ) @@ -129,7 +185,6 @@ export default class ThemeSource extends FeatureSourceMerger { downloadAll ) - this.isLoading = isLoading this._downloadAll = downloadAll this._mapBounds = mapProperties.bounds } @@ -192,7 +247,7 @@ export default class ThemeSource extends FeatureSourceMerger { backend, isActive, patchRelations: true, - fullNodeDatabase, + fullNodeDatabase }) } @@ -224,11 +279,11 @@ export default class ThemeSource extends FeatureSourceMerger { widenFactor: 1.5, overpassUrl: featureSwitches.overpassUrl, overpassTimeout: featureSwitches.overpassTimeout, - overpassMaxZoom: featureSwitches.overpassMaxZoom, + overpassMaxZoom: featureSwitches.overpassMaxZoom }, { padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)), - isActive, + isActive } ) } diff --git a/src/Logic/Web/ThemeViewStateHashActor.ts b/src/Logic/Web/ThemeViewStateHashActor.ts index 76e566e1bf..3ea1a21e4d 100644 --- a/src/Logic/Web/ThemeViewStateHashActor.ts +++ b/src/Logic/Web/ThemeViewStateHashActor.ts @@ -8,8 +8,7 @@ export default class ThemeViewStateHashActor { private readonly _state: { indexedFeatures: IndexedFeatureSource, selectedElement: UIEventSource, - guistate: MenuState, - previewedImage: UIEventSource + guistate: MenuState } private isUpdatingHash = false @@ -39,7 +38,6 @@ export default class ThemeViewStateHashActor { indexedFeatures: IndexedFeatureSource, selectedElement: UIEventSource, guistate: MenuState, - previewedImage: UIEventSource }) { this._state = state @@ -150,14 +148,8 @@ export default class ThemeViewStateHashActor { } private back() { + console.log("Going back via hash actor") const state = this._state - if (state.previewedImage.data) { - state.previewedImage.setData(undefined) - return - } - if (state.guistate.closeAll()) { - return - } - state.selectedElement.setData(undefined) + state.guistate.closeAll() } } diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index aae9381ffb..70c41f478c 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -1,5 +1,6 @@ import ThemeConfig from "./ThemeConfig/ThemeConfig" import { WithImageState } from "./ThemeViewState/WithImageState" +import { Store } from "../Logic/UIEventSource" /** * @@ -10,7 +11,7 @@ import { WithImageState } from "./ThemeViewState/WithImageState" * It ties up all the needed elements and starts some actors. */ export default class ThemeViewState extends WithImageState { - constructor(layout: ThemeConfig, mvtAvailableLayers: Set) { + constructor(layout: ThemeConfig, mvtAvailableLayers: Store>) { super(layout, mvtAvailableLayers) } } diff --git a/src/Models/ThemeViewState/WithChangesState.ts b/src/Models/ThemeViewState/WithChangesState.ts index 29f9de9935..d9247d1eb9 100644 --- a/src/Models/ThemeViewState/WithChangesState.ts +++ b/src/Models/ThemeViewState/WithChangesState.ts @@ -35,7 +35,7 @@ export class WithChangesState extends WithLayoutSourceState { */ readonly hasDataInView: Store - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme, mvtAvailableLayers) this.changes = new Changes( { @@ -82,7 +82,7 @@ export class WithChangesState extends WithLayoutSourceState { this.perLayerFiltered = this.showNormalDataOn(this.map) this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView - + this.toCacheSavers = theme.enableCache ? this.initSaveToLocalStorage() : undefined diff --git a/src/Models/ThemeViewState/WithGuiState.ts b/src/Models/ThemeViewState/WithGuiState.ts index 93798a26d0..9f99577bf8 100644 --- a/src/Models/ThemeViewState/WithGuiState.ts +++ b/src/Models/ThemeViewState/WithGuiState.ts @@ -3,6 +3,7 @@ import { MenuState } from "../MenuState" import Hotkeys from "../../UI/Base/Hotkeys" import Translations from "../../UI/i18n/Translations" import { WithSpecialLayers } from "./WithSpecialLayers" +import { Store } from "../../Logic/UIEventSource" /** * Does all things related to: @@ -11,9 +12,10 @@ import { WithSpecialLayers } from "./WithSpecialLayers" export class WithGuiState extends WithSpecialLayers { readonly guistate: MenuState - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme, mvtAvailableLayers) - this.guistate = new MenuState( + this.guistate = new MenuState(this.selectedElement) + this.guistate.openMenuIfNeeded( this.featureSwitches.featureSwitchWelcomeMessage.data, theme.id ) diff --git a/src/Models/ThemeViewState/WithLayoutSourceState.ts b/src/Models/ThemeViewState/WithLayoutSourceState.ts index 5be9d07874..ff492076cf 100644 --- a/src/Models/ThemeViewState/WithLayoutSourceState.ts +++ b/src/Models/ThemeViewState/WithLayoutSourceState.ts @@ -22,7 +22,7 @@ export class WithLayoutSourceState extends WithSelectedElementState { readonly floors: Store - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme) /* Set up the layout source * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generates a stream of new and changed features too diff --git a/src/Models/ThemeViewState/WithSearchState.ts b/src/Models/ThemeViewState/WithSearchState.ts index 979628557f..a106ea81a4 100644 --- a/src/Models/ThemeViewState/WithSearchState.ts +++ b/src/Models/ThemeViewState/WithSearchState.ts @@ -7,11 +7,12 @@ import { WithVisualFeedbackState } from "./WithVisualFeedbackState" import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" import LayerConfig from "../ThemeConfig/LayerConfig" import ShowDataLayer from "../../UI/Map/ShowDataLayer" +import { Store } from "../../Logic/UIEventSource" export class WithSearchState extends WithVisualFeedbackState { public readonly searchState: SearchState - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme, mvtAvailableLayers) this.searchState = new SearchState(this) this.initHotkeysSearch() @@ -48,20 +49,12 @@ export class WithSearchState extends WithVisualFeedbackState { ) Hotkeys.RegisterHotkey({ nomod: "Escape", onUp: true }, docs.closeSidebar, () => { - if (this.previewedImage.data !== undefined) { - this.previewedImage.setData(undefined) - return - } - if (this.selectedElement.data) { - this.selectedElement.setData(undefined) + + if (this.guistate.closeAll()) { return } if (this.searchState.showSearchDrawer.data) { this.searchState.showSearchDrawer.set(false) - return - } - if (this.guistate.closeAll()) { - return } Zoomcontrol.resetzoom() this.focusOnMap() diff --git a/src/Models/ThemeViewState/WithSelectedElementState.ts b/src/Models/ThemeViewState/WithSelectedElementState.ts index b6f7ec85c5..1877338e97 100644 --- a/src/Models/ThemeViewState/WithSelectedElementState.ts +++ b/src/Models/ThemeViewState/WithSelectedElementState.ts @@ -28,11 +28,10 @@ export class WithSelectedElementState extends UserMapFeatureswitchState { }) this.mapProperties.lastClickLocation.addCallbackD((lastClick) => { - if (lastClick.mode !== "left" || !lastClick.nearestFeature) { + if (lastClick.mode !== "left") { return } - const f = lastClick.nearestFeature - this.setSelectedElement(f) + this.setSelectedElement(lastClick.nearestFeature) }) @@ -67,7 +66,7 @@ export class WithSelectedElementState extends UserMapFeatureswitchState { const current = this.selectedElement.data if ( current?.properties?.id !== undefined && - current.properties.id === feature.properties.id + current.properties.id === feature?.properties?.id ) { console.log("Not setting selected, same id", current, feature) return // already set diff --git a/src/Models/ThemeViewState/WithSpecialLayers.ts b/src/Models/ThemeViewState/WithSpecialLayers.ts index 4207a2dd78..50c9ddfd61 100644 --- a/src/Models/ThemeViewState/WithSpecialLayers.ts +++ b/src/Models/ThemeViewState/WithSpecialLayers.ts @@ -14,7 +14,7 @@ import { LayerConfigJson } from "../ThemeConfig/Json/LayerConfigJson" import last_click_layerconfig from "../../assets/generated/layers/last_click.json" import { GeoOperations } from "../../Logic/GeoOperations" import summaryLayer from "../../assets/generated/layers/summary.json" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource" import { SummaryTileSource, @@ -43,7 +43,7 @@ export class WithSpecialLayers extends WithChangesState { readonly visualFeedbackViewportBounds: UIEventSource = new UIEventSource(undefined) - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme, mvtAvailableLayers) this.favourites = new FavouritesFeatureSource(this) @@ -64,12 +64,12 @@ export class WithSpecialLayers extends WithChangesState { this.featureSummary = this.setupSummaryLayer() this.initActorsSpecialLayers() + this.drawSelectedElement() this.drawSpecialLayers() this.drawLastClick() // Note: the lock-range is handled by UserMapFeatureSwitchState { // Activate metatagging for the 'current_view' layer - console.log(">>>", this.layerState.filteredLayers) const currentViewLayer = this.layerState.filteredLayers.get("current_view")?.layerDef if (currentViewLayer?.tagRenderings?.length > 0) { const params = MetaTagging.createExtraFuncParams(this) @@ -163,8 +163,10 @@ export class WithSpecialLayers extends WithChangesState { }) ) // show last click = new point/note marker + const features = new StaticFeatureSource(lastClickFiltered) + this.featureProperties.trackFeatureSource(features) new ShowDataLayer(this.map, { - features: new StaticFeatureSource(lastClickFiltered), + features, layer: lastClickLayerConfig, onClick: (feature) => { if (this.mapProperties.zoom.data >= Constants.minZoomLevelToAddNewPoint) { @@ -179,6 +181,13 @@ export class WithSpecialLayers extends WithChangesState { }) } + private drawSelectedElement() { + const src = new StaticFeatureSource( + this.selectedElement.map((f) => (f === undefined ? [] : [f])) + ) + ShowDataLayer.showMultipleLayers(this.map, src, this.theme.layers) + } + private drawSpecialLayers() { type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] @@ -187,6 +196,7 @@ export class WithSpecialLayers extends WithChangesState { | "last_click" // handled by this.drawLastClick() | "summary" // handled by setupSummaryLayer | "range" // handled by UserMapFeatureSwitchState + | "selected_element" // handled by this.drawSelectedElement > const empty = [] /** @@ -199,10 +209,7 @@ export class WithSpecialLayers extends WithChangesState { gps_track: this.geolocation.historicalUserLocationsTrack, current_view: this.currentView, favourite: this.favourites, - geocoded_image: new StaticFeatureSource(this.geocodedImages), - selected_element: new StaticFeatureSource( - this.selectedElement.map((f) => (f === undefined ? empty : [f])) - ) + geocoded_image: new StaticFeatureSource(this.geocodedImages) } diff --git a/src/Models/ThemeViewState/WithVisualFeedbackState.ts b/src/Models/ThemeViewState/WithVisualFeedbackState.ts index 99a317c28a..fbee6252b9 100644 --- a/src/Models/ThemeViewState/WithVisualFeedbackState.ts +++ b/src/Models/ThemeViewState/WithVisualFeedbackState.ts @@ -1,5 +1,5 @@ import ThemeConfig from "../ThemeConfig/ThemeConfig" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import Hotkeys from "../../UI/Base/Hotkeys" import Translations from "../../UI/i18n/Translations" import ThemeViewState from "../ThemeViewState" @@ -11,7 +11,7 @@ export class WithVisualFeedbackState extends ThemeViewState { */ public readonly visualFeedback: UIEventSource = new UIEventSource(false) - constructor(theme: ThemeConfig, mvtAvailableLayers: Set) { + constructor(theme: ThemeConfig, mvtAvailableLayers: Store>) { super(theme, mvtAvailableLayers) this.initHotkeysVisualFeedback() @@ -72,7 +72,7 @@ export class WithVisualFeedbackState extends ThemeViewState { if (this.selectedElement.data !== undefined) { return false } - if (this.guistate.isSomethingOpen() || this.previewedImage.data !== undefined) { + if (this.guistate.isSomethingOpen()) { return } if ( diff --git a/src/UI/Base/Popup.svelte b/src/UI/Base/Popup.svelte index b644bcbae4..728d92448e 100644 --- a/src/UI/Base/Popup.svelte +++ b/src/UI/Base/Popup.svelte @@ -9,7 +9,7 @@ export let fullscreen: boolean = false export let bodyPadding = "p-4 md:p-5 " export let shown: UIEventSource - export let dismissable = true + export let dismissable = false /** * Default: 50 */ diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index f6d7d2e81f..0fa91729d6 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -16,7 +16,6 @@ import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFea import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" import { TagsFilter } from "../../Logic/Tags/TagsFilter" -import { featureEach } from "@turf/turf" class PointRenderingLayer { private readonly _config: PointRenderingConfig @@ -542,7 +541,7 @@ export default class ShowDataLayer { mlmap: UIEventSource, features: FeatureSource, layers: LayerConfig[], - options?: Partial + options?: Partial> ) { const perLayer: PerLayerFeatureSourceSplitter = new PerLayerFeatureSourceSplitter( diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 34e545f05c..d032165ace 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -14,7 +14,6 @@ import FeatureSwitchState from "../Logic/State/FeatureSwitchState" import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" -import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import ThemeSource from "../Logic/FeatureSource/Sources/ThemeSource" import { Map as MlMap } from "maplibre-gl" @@ -71,7 +70,6 @@ export interface SpecialVisualizationState { readonly imageUploadManager: ImageUploadManager - readonly previewedImage: UIEventSource readonly nearbyImageSearcher: CombinedFetcher readonly geolocation: GeoLocationHandler readonly geocodedImages: UIEventSource diff --git a/src/index.ts b/src/index.ts index 1b5928b885..8bcc582b9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { Utils } from "./Utils" import Constants from "./Models/Constants" import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray" import { WithSearchState } from "./Models/ThemeViewState/WithSearchState" +import { UIEventSource } from "./Logic/UIEventSource" function webgl_support() { try { @@ -48,11 +49,11 @@ async function main() { if (!webgl_support()) { throw "WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this." } - const [theme, availableLayers] = await Promise.all([ - DetermineTheme.getTheme(), - await getAvailableLayers(), - ]) - console.log("The available layers on server are", Array.from(availableLayers)) + const availableLayers = UIEventSource.FromPromise(getAvailableLayers()) + const theme = await DetermineTheme.getTheme() + availableLayers.addCallbackAndRunD(availableLayers => { + console.log("The available layers on server are", Array.from(availableLayers)) + }) const state = new WithSearchState(theme, availableLayers) const target = document.getElementById("maindiv") const childs = Array.from(target.children)