From 285fe9ab833b0cf0c0ea00a011457a83bd7ad6d2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 9 Oct 2023 01:26:12 +0200 Subject: [PATCH] UX: place 'add new button' in bottom-left instead of having a dynamic pin --- langs/en.json | 2 +- langs/nl.json | 2 +- .../Sources/LastClickFeatureSource.ts | 68 +- src/Logic/Web/ThemeViewStateHashActor.ts | 2 +- src/Models/Constants.ts | 2 +- src/Models/ThemeViewState.ts | 1164 +++++++++-------- src/UI/ThemeViewGUI.svelte | 193 +-- 7 files changed, 730 insertions(+), 703 deletions(-) diff --git a/langs/en.json b/langs/en.json index 7167afb95..7bd2338e5 100644 --- a/langs/en.json +++ b/langs/en.json @@ -124,7 +124,7 @@ "pleaseLogin": "Please log in to add a new feature", "presetInfo": "The new POI will have {tags}", "stillLoading": "The data is still loading. Please wait a bit before you add a new feature.", - "title": "Add a new feature?", + "title": "Add a new feature", "warnVisibleForEveryone": "Your addition will be visible for everyone", "wrongType": "This feature is not a node or a way and can not be imported", "zoomInFurther": "Zoom in further to add a feature.", diff --git a/langs/nl.json b/langs/nl.json index 70101f0b2..03f4923e4 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -124,7 +124,7 @@ "pleaseLogin": "Gelieve je aan te melden om een object toe te voegen", "presetInfo": "Het nieuwe object krijgt de attributen {tags}", "stillLoading": "De data worden nog geladen. Nog even geduld en dan kan je een object toevoegen.", - "title": "Nieuw object toevoegen?", + "title": "Nieuw object toevoegen", "warnVisibleForEveryone": "Je toevoeging is voor iedereen zichtbaar", "wrongType": "Dit object is geen punt of lijn en kan daarom niet geïmporteerd worden", "zoomInFurther": "Gelieve verder in te zoomen om een object toe te voegen.", diff --git a/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts b/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts index ecd8d8288..b45169c0a 100644 --- a/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts @@ -1,10 +1,11 @@ -import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" -import { WritableFeatureSource } from "../FeatureSource" -import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" -import { Feature, Point } from "geojson" -import { TagUtils } from "../../Tags/TagUtils" -import BaseUIElement from "../../../UI/BaseUIElement" -import { Utils } from "../../../Utils" +import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"; +import { WritableFeatureSource } from "../FeatureSource"; +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"; +import { Feature, Point } from "geojson"; +import { TagUtils } from "../../Tags/TagUtils"; +import BaseUIElement from "../../../UI/BaseUIElement"; +import { Utils } from "../../../Utils"; +import { OsmTags } from "../../../Models/OsmFeature"; /** * Highly specialized feature source. @@ -12,8 +13,14 @@ import { Utils } from "../../../Utils" */ export class LastClickFeatureSource implements WritableFeatureSource { public readonly features: UIEventSource = new UIEventSource([]) + private i: number = 0 + private readonly hasNoteLayer: string + private readonly renderings: string[]; + private readonly hasPresets: string; constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) { + this.hasNoteLayer = layout.layers.some((l) => l.id === "note") ? "yes" : "no" + this.hasPresets= layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no" const allPresets: BaseUIElement[] = [] for (const layer of layout.layers) for (let i = 0; i < (layer.presets ?? []).length; i++) { @@ -26,35 +33,36 @@ export class LastClickFeatureSource implements WritableFeatureSource { allPresets.push(html) } - const renderings = Utils.Dedup( + this.renderings = Utils.Dedup( allPresets.map((uiElem) => Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML ) ) - let i = 0 - location.addCallbackAndRunD(({ lon, lat }) => { - const properties = { - lastclick: "yes", - id: "last_click_" + i, - has_note_layer: layout.layers.some((l) => l.id === "note") ? "yes" : "no", - has_presets: layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no", - renderings: renderings.join(""), - number_of_presets: "" + renderings.length, - first_preset: renderings[0], - } - i++ - - const point = >{ - type: "Feature", - properties, - geometry: { - type: "Point", - coordinates: [lon, lat], - }, - } - this.features.setData([point]) + this.features.setData([this.createFeature(lon, lat)]) }) } + + public createFeature(lon: number, lat: number): Feature { + const properties: OsmTags = { + lastclick: "yes", + id: "last_click_" + this.i, + has_note_layer: this.hasNoteLayer , + has_presets:this.hasPresets , + renderings: this.renderings.join(""), + number_of_presets: "" +this. renderings.length, + first_preset: this.renderings[0], + } + this. i++ + + return >{ + type: "Feature", + properties, + geometry: { + type: "Point", + coordinates: [lon, lat], + }, + } + } } diff --git a/src/Logic/Web/ThemeViewStateHashActor.ts b/src/Logic/Web/ThemeViewStateHashActor.ts index b10208f09..93a2326e9 100644 --- a/src/Logic/Web/ThemeViewStateHashActor.ts +++ b/src/Logic/Web/ThemeViewStateHashActor.ts @@ -175,7 +175,7 @@ export default class ThemeViewStateHashActor { } private back() { - console.log("Got a back event") + console.trace("Got a back event") const state = this._state // history.pushState(null, null, window.location.pathname); if (state.selectedElement.data) { diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index ad68f6979..39aa182ad 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -58,7 +58,7 @@ export default class Constants { importHelperUnlock: 5000, } - static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 18 : 19 + static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 17 : 18 /** * Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes. * (Note that pendingChanges might upload sooner if the popup is closed or similar) diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index a15f6b056..6a03a41d9 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -1,62 +1,58 @@ -import LayoutConfig from "./ThemeConfig/LayoutConfig" -import { SpecialVisualizationState } from "../UI/SpecialVisualization" -import { Changes } from "../Logic/Osm/Changes" -import { Store, UIEventSource } from "../Logic/UIEventSource" +import LayoutConfig from "./ThemeConfig/LayoutConfig"; +import { SpecialVisualizationState } from "../UI/SpecialVisualization"; +import { Changes } from "../Logic/Osm/Changes"; +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { ExportableMap, MapProperties } from "./MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Point, Polygon } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { Map as MlMap } from "maplibre-gl"; +import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"; +import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"; +import { GeoLocationState } from "../Logic/State/GeoLocationState"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { QueryParameters } from "../Logic/Web/QueryParameters"; +import UserRelatedState from "../Logic/State/UserRelatedState"; +import LayerConfig from "./ThemeConfig/LayerConfig"; +import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"; +import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"; +import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"; +import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; +import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"; +import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"; +import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"; +import ShowDataLayer from "../UI/Map/ShowDataLayer"; +import TitleHandler from "../Logic/Actors/TitleHandler"; +import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"; +import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"; +import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; +import { BBox } from "../Logic/BBox"; +import Constants from "./Constants"; +import Hotkeys from "../UI/Base/Hotkeys"; +import Translations from "../UI/i18n/Translations"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"; +import { MenuState } from "./MenuState"; +import MetaTagging from "../Logic/MetaTagging"; +import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"; import { - FeatureSource, - IndexedFeatureSource, - WritableFeatureSource, -} from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { ExportableMap, MapProperties } from "./MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Point, Polygon } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { Map as MlMap } from "maplibre-gl" -import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" -import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" -import { GeoLocationState } from "../Logic/State/GeoLocationState" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { QueryParameters } from "../Logic/Web/QueryParameters" -import UserRelatedState from "../Logic/State/UserRelatedState" -import LayerConfig from "./ThemeConfig/LayerConfig" -import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers" -import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" -import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" -import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" -import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" -import ShowDataLayer from "../UI/Map/ShowDataLayer" -import TitleHandler from "../Logic/Actors/TitleHandler" -import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" -import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" -import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" -import { BBox } from "../Logic/BBox" -import Constants from "./Constants" -import Hotkeys from "../UI/Base/Hotkeys" -import Translations from "../UI/i18n/Translations" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" -import { MenuState } from "./MenuState" -import MetaTagging from "../Logic/MetaTagging" -import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" -import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" -import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" -import { Utils } from "../Utils" -import { EliCategory } from "./RasterLayerProperties" -import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" -import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" -import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" -import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" -import NoElementsInViewDetector, { - FeatureViewState, -} from "../Logic/Actors/NoElementsInViewDetector" -import FilteredLayer from "./FilteredLayer" -import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" -import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" -import { Imgur } from "../Logic/ImageProviders/Imgur" + NewGeometryFromChangesFeatureSource +} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"; +import { Utils } from "../Utils"; +import { EliCategory } from "./RasterLayerProperties"; +import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"; +import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"; +import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; +import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"; +import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"; +import FilteredLayer from "./FilteredLayer"; +import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"; +import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; +import { Imgur } from "../Logic/ImageProviders/Imgur"; /** * @@ -67,559 +63,573 @@ import { Imgur } from "../Logic/ImageProviders/Imgur" * It ties up all the needed elements and starts some actors. */ export default class ThemeViewState implements SpecialVisualizationState { - readonly layout: LayoutConfig - readonly map: UIEventSource - readonly changes: Changes - readonly featureSwitches: FeatureSwitchState - readonly featureSwitchIsTesting: Store - readonly featureSwitchUserbadge: Store + readonly layout: LayoutConfig; + readonly map: UIEventSource; + readonly changes: Changes; + readonly featureSwitches: FeatureSwitchState; + readonly featureSwitchIsTesting: Store; + readonly featureSwitchUserbadge: Store; - readonly featureProperties: FeaturePropertiesStore + readonly featureProperties: FeaturePropertiesStore; - readonly osmConnection: OsmConnection - readonly selectedElement: UIEventSource - readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> - readonly mapProperties: MapProperties & ExportableMap - readonly osmObjectDownloader: OsmObjectDownloader + readonly osmConnection: OsmConnection; + readonly selectedElement: UIEventSource; + readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; + readonly mapProperties: MapProperties & ExportableMap; + readonly osmObjectDownloader: OsmObjectDownloader; - readonly dataIsLoading: Store - /** - * Indicates if there is _some_ data in view, even if it is not shown due to the filters - */ - readonly hasDataInView: Store + readonly dataIsLoading: Store; + /** + * Indicates if there is _some_ data in view, even if it is not shown due to the filters + */ + readonly hasDataInView: Store; - readonly guistate: MenuState - readonly fullNodeDatabase?: FullNodeDatabaseSource + readonly guistate: MenuState; + readonly fullNodeDatabase?: FullNodeDatabaseSource; - readonly historicalUserLocations: WritableFeatureSource> - readonly indexedFeatures: IndexedFeatureSource & LayoutSource - readonly currentView: FeatureSource> - readonly featuresInView: FeatureSource - readonly newFeatures: WritableFeatureSource - readonly layerState: LayerState - readonly perLayer: ReadonlyMap - readonly perLayerFiltered: ReadonlyMap + readonly historicalUserLocations: WritableFeatureSource>; + readonly indexedFeatures: IndexedFeatureSource & LayoutSource; + readonly currentView: FeatureSource>; + readonly featuresInView: FeatureSource; + readonly newFeatures: WritableFeatureSource; + readonly layerState: LayerState; + readonly perLayer: ReadonlyMap; + readonly perLayerFiltered: ReadonlyMap; - readonly availableLayers: Store - readonly selectedLayer: UIEventSource - readonly userRelatedState: UserRelatedState - readonly geolocation: GeoLocationHandler + readonly availableLayers: Store; + readonly selectedLayer: UIEventSource; + readonly userRelatedState: UserRelatedState; + readonly geolocation: GeoLocationHandler; - readonly imageUploadManager: ImageUploadManager + readonly imageUploadManager: ImageUploadManager; - readonly lastClickObject: WritableFeatureSource - readonly overlayLayerStates: ReadonlyMap< - string, - { readonly isDisplayed: UIEventSource } - > - /** - * All 'level'-tags that are available with the current features - */ - readonly floors: Store + readonly addNewPoint: UIEventSource = new UIEventSource(false); - constructor(layout: LayoutConfig) { - Utils.initDomPurify() - this.layout = layout - this.featureSwitches = new FeatureSwitchState(layout) - this.guistate = new MenuState( - this.featureSwitches.featureSwitchWelcomeMessage.data, - layout.id - ) - this.map = new UIEventSource(undefined) - const initial = new InitialMapPositioning(layout) - this.mapProperties = new MapLibreAdaptor(this.map, initial) - const geolocationState = new GeoLocationState() + readonly lastClickObject: LastClickFeatureSource; + readonly overlayLayerStates: ReadonlyMap< + string, + { readonly isDisplayed: UIEventSource } + >; + /** + * All 'level'-tags that are available with the current features + */ + readonly floors: Store; + private readonly newPointDialog: FilteredLayer; - this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting - this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin + constructor(layout: LayoutConfig) { + Utils.initDomPurify(); + this.layout = layout; + this.featureSwitches = new FeatureSwitchState(layout); + this.guistate = new MenuState( + this.featureSwitches.featureSwitchWelcomeMessage.data, + layout.id + ); + this.map = new UIEventSource(undefined); + const initial = new InitialMapPositioning(layout); + this.mapProperties = new MapLibreAdaptor(this.map, initial); + const geolocationState = new GeoLocationState(); - this.osmConnection = new OsmConnection({ - dryRun: this.featureSwitches.featureSwitchIsTesting, - fakeUser: this.featureSwitches.featureSwitchFakeUser.data, - oauth_token: QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), + this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting; + this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin; + + this.osmConnection = new OsmConnection({ + dryRun: this.featureSwitches.featureSwitchIsTesting, + fakeUser: this.featureSwitches.featureSwitchFakeUser.data, + oauth_token: QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ) + }); + this.userRelatedState = new UserRelatedState( + this.osmConnection, + layout?.language, + layout, + this.featureSwitches, + this.mapProperties + ); + this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { + this.mapProperties.allowRotating.setData(fixated !== "yes"); + }); + this.selectedElement = new UIEventSource(undefined, "Selected element"); + this.selectedLayer = new UIEventSource(undefined, "Selected layer"); + + this.selectedElementAndLayer = this.selectedElement.mapD( + (feature) => { + const layer = this.selectedLayer.data; + if (!layer) { + return undefined; + } + return { layer, feature }; + }, + [this.selectedLayer] + ); + + this.geolocation = new GeoLocationHandler( + geolocationState, + this.selectedElement, + this.mapProperties, + this.userRelatedState.gpsLocationHistoryRetentionTime + ); + + this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location); + + const self = this; + this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id); + + { + const overlayLayerStates = new Map }>(); + for (const rasterInfo of this.layout.tileLayerSources) { + const isDisplayed = QueryParameters.GetBooleanQueryParameter( + "overlay-" + rasterInfo.id, + rasterInfo.defaultState ?? true, + "Wether or not overlayer layer " + rasterInfo.id + " is shown" + ); + const state = { isDisplayed }; + overlayLayerStates.set(rasterInfo.id, state); + new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state); + } + this.overlayLayerStates = overlayLayerStates; + } + + { + /* Setup the layout source + * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too + */ + + if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) { + this.fullNodeDatabase = new FullNodeDatabaseSource(); + } + + const layoutSource = new LayoutSource( + layout.layers, + this.featureSwitches, + this.mapProperties, + this.osmConnection.Backend(), + (id) => self.layerState.filteredLayers.get(id).isDisplayed, + this.fullNodeDatabase + ); + + this.indexedFeatures = layoutSource; + + let currentViewIndex = 0 + const empty = []; + this.currentView = new StaticFeatureSource( + this.mapProperties.bounds.map((bbox) => { + if (!bbox) { + return empty; + } + currentViewIndex++; + return [ + bbox.asGeoJson({ + zoom: this.mapProperties.zoom.data, + ...this.mapProperties.location.data, + id: "current_view_"+currentViewIndex + }) + ]; }) - this.userRelatedState = new UserRelatedState( - this.osmConnection, - layout?.language, - layout, - this.featureSwitches, - this.mapProperties - ) - this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { - this.mapProperties.allowRotating.setData(fixated !== "yes") - }) - this.selectedElement = new UIEventSource(undefined, "Selected element") - this.selectedLayer = new UIEventSource(undefined, "Selected layer") - - this.selectedElementAndLayer = this.selectedElement.mapD( - (feature) => { - const layer = this.selectedLayer.data - if (!layer) { - return undefined - } - return { layer, feature } - }, - [this.selectedLayer] - ) - - this.geolocation = new GeoLocationHandler( - geolocationState, - this.selectedElement, - this.mapProperties, - this.userRelatedState.gpsLocationHistoryRetentionTime - ) - - this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) - - const self = this - this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) + ); + this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds); + this.dataIsLoading = layoutSource.isLoading; + const indexedElements = this.indexedFeatures; + this.featureProperties = new FeaturePropertiesStore(indexedElements); + this.changes = new Changes( { - const overlayLayerStates = new Map }>() - for (const rasterInfo of this.layout.tileLayerSources) { - const isDisplayed = QueryParameters.GetBooleanQueryParameter( - "overlay-" + rasterInfo.id, - rasterInfo.defaultState ?? true, - "Wether or not overlayer layer " + rasterInfo.id + " is shown" - ) - const state = { isDisplayed } - overlayLayerStates.set(rasterInfo.id, state) - new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) - } - this.overlayLayerStates = overlayLayerStates - } + dryRun: this.featureSwitches.featureSwitchIsTesting, + allElements: indexedElements, + featurePropertiesStore: this.featureProperties, + osmConnection: this.osmConnection, + historicalUserLocations: this.geolocation.historicalUserLocations + }, + layout?.isLeftRightSensitive() ?? false + ); + this.historicalUserLocations = this.geolocation.historicalUserLocations; + this.newFeatures = new NewGeometryFromChangesFeatureSource( + this.changes, + indexedElements, + this.featureProperties + ); + layoutSource.addSource(this.newFeatures); + const perLayer = new PerLayerFeatureSourceSplitter( + Array.from(this.layerState.filteredLayers.values()).filter( + (l) => l.layerDef?.source !== null + ), + new ChangeGeometryApplicator(this.indexedFeatures, this.changes), { - /* Setup the layout source - * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too - */ - - if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) { - this.fullNodeDatabase = new FullNodeDatabaseSource() - } - - const layoutSource = new LayoutSource( - layout.layers, - this.featureSwitches, - this.mapProperties, - this.osmConnection.Backend(), - (id) => self.layerState.filteredLayers.get(id).isDisplayed, - this.fullNodeDatabase - ) - - this.indexedFeatures = layoutSource - - const empty = [] - let currentViewIndex = 0 - this.currentView = new StaticFeatureSource( - this.mapProperties.bounds.map((bbox) => { - if (!bbox) { - return empty - } - currentViewIndex++ - return [ - bbox.asGeoJson({ - zoom: this.mapProperties.zoom.data, - ...this.mapProperties.location.data, - id: "current_view", - }), - ] - }) - ) - this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) - this.dataIsLoading = layoutSource.isLoading - - const indexedElements = this.indexedFeatures - this.featureProperties = new FeaturePropertiesStore(indexedElements) - this.changes = new Changes( - { - dryRun: this.featureSwitches.featureSwitchIsTesting, - allElements: indexedElements, - featurePropertiesStore: this.featureProperties, - osmConnection: this.osmConnection, - historicalUserLocations: this.geolocation.historicalUserLocations, - }, - layout?.isLeftRightSensitive() ?? false - ) - this.historicalUserLocations = this.geolocation.historicalUserLocations - this.newFeatures = new NewGeometryFromChangesFeatureSource( - this.changes, - indexedElements, - this.featureProperties - ) - layoutSource.addSource(this.newFeatures) - - const perLayer = new PerLayerFeatureSourceSplitter( - Array.from(this.layerState.filteredLayers.values()).filter( - (l) => l.layerDef?.source !== null - ), - new ChangeGeometryApplicator(this.indexedFeatures, this.changes), - { - constructStore: (features, layer) => - new GeoIndexedStoreForLayer(features, layer), - handleLeftovers: (features) => { - console.warn( - "Got ", - features.length, - "leftover features, such as", - features[0].properties - ) - }, - } - ) - this.perLayer = perLayer.perLayer + constructStore: (features, layer) => + new GeoIndexedStoreForLayer(features, layer), + handleLeftovers: (features) => { + console.warn( + "Got ", + features.length, + "leftover features, such as", + features[0].properties + ); + } } - this.perLayer.forEach((fs) => { - new SaveFeatureSourceToLocalStorage( - this.osmConnection.Backend(), - fs.layer.layerDef.id, - 15, - fs, - this.featureProperties, - fs.layer.layerDef.maxAgeOfCache - ) - }) + ); + this.perLayer = perLayer.perLayer; + } + this.perLayer.forEach((fs) => { + new SaveFeatureSourceToLocalStorage( + this.osmConnection.Backend(), + fs.layer.layerDef.id, + 15, + fs, + this.featureProperties, + fs.layer.layerDef.maxAgeOfCache + ); + }); + this.newPointDialog = this.layerState.filteredLayers.get("last_click"); - this.floors = this.featuresInView.features.stabilized(500).map((features) => { - if (!features) { - return [] - } - const floors = new Set() - for (const feature of features) { - let level = feature.properties["_level"] - if (level) { - const levels = level.split(";") - for (const l of levels) { - floors.add(l) - } - } else { - floors.add("0") // '0' is the default and is thus _always_ present - } - } - const sorted = Array.from(floors) - // Sort alphabetically first, to deal with floor "A", "B" and "C" - sorted.sort() - sorted.sort((a, b) => { - // We use the laxer 'parseInt' to deal with floor '1A' - const na = parseInt(a) - const nb = parseInt(b) - if (isNaN(na) || isNaN(nb)) { - return 0 - } - return na - nb - }) - sorted.reverse(/* new list, no side-effects */) - return sorted - }) - - const lastClick = (this.lastClickObject = new LastClickFeatureSource( - this.mapProperties.lastClickLocation, - this.layout - )) - - this.osmObjectDownloader = new OsmObjectDownloader( - this.osmConnection.Backend(), - this.changes - ) - - this.perLayerFiltered = this.showNormalDataOn(this.map) - - this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView - this.imageUploadManager = new ImageUploadManager( - layout, - Imgur.singleton, - this.featureProperties, - this.osmConnection, - this.changes - ) - - this.initActors() - this.addLastClick(lastClick) - this.drawSpecialLayers() - this.initHotkeys() - this.miscSetup() - if (!Utils.runningFromConsole) { - console.log("State setup completed", this) + this.floors = this.featuresInView.features.stabilized(500).map((features) => { + if (!features) { + return []; + } + const floors = new Set(); + for (const feature of features) { + let level = feature.properties["_level"]; + if (level) { + const levels = level.split(";"); + for (const l of levels) { + floors.add(l); + } + } else { + floors.add("0"); // '0' is the default and is thus _always_ present } + } + const sorted = Array.from(floors); + // Sort alphabetically first, to deal with floor "A", "B" and "C" + sorted.sort(); + sorted.sort((a, b) => { + // We use the laxer 'parseInt' to deal with floor '1A' + const na = parseInt(a); + const nb = parseInt(b); + if (isNaN(na) || isNaN(nb)) { + return 0; + } + return na - nb; + }); + sorted.reverse(/* new list, no side-effects */); + return sorted; + }); + + const lastClick = (this.lastClickObject = new LastClickFeatureSource( + this.mapProperties.lastClickLocation, + this.layout + )); + + this.osmObjectDownloader = new OsmObjectDownloader( + this.osmConnection.Backend(), + this.changes + ); + + this.perLayerFiltered = this.showNormalDataOn(this.map); + + this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView; + this.imageUploadManager = new ImageUploadManager( + layout, + Imgur.singleton, + this.featureProperties, + this.osmConnection, + this.changes + ); + + this.initActors(); + // TODO remove this.addLastClick(lastClick); + this.drawSpecialLayers(); + this.initHotkeys(); + this.miscSetup(); + if (!Utils.runningFromConsole) { + console.log("State setup completed", this); } + } - public showNormalDataOn(map: Store): ReadonlyMap { - const filteringFeatureSource = new Map() - this.perLayer.forEach((fs, layerName) => { - const doShowLayer = this.mapProperties.zoom.map( - (z) => - (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), - [fs.layer.isDisplayed] - ) + public showNormalDataOn(map: Store): ReadonlyMap { + const filteringFeatureSource = new Map(); + this.perLayer.forEach((fs, layerName) => { + const doShowLayer = this.mapProperties.zoom.map( + (z) => + (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), + [fs.layer.isDisplayed] + ); - if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { - /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) - * - * This means that we don't have to filter it, nor do we have to display it - * - * Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. - * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer! - * */ - return - } - const filtered = new FilteringFeatureSource( - fs.layer, - fs, - (id) => this.featureProperties.getStore(id), - this.layerState.globalFilters - ) - filteringFeatureSource.set(layerName, filtered) + if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { + /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) + * + * This means that we don't have to filter it, nor do we have to display it + * + * Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. + * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer! + * */ + return; + } + const filtered = new FilteringFeatureSource( + fs.layer, + fs, + (id) => this.featureProperties.getStore(id), + this.layerState.globalFilters + ); + filteringFeatureSource.set(layerName, filtered); - new ShowDataLayer(map, { - layer: fs.layer.layerDef, - features: filtered, - doShowLayer, - selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - fetchStore: (id) => this.featureProperties.getStore(id), - }) - }) - return filteringFeatureSource + new ShowDataLayer(map, { + layer: fs.layer.layerDef, + features: filtered, + doShowLayer, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer, + fetchStore: (id) => this.featureProperties.getStore(id) + }); + }); + return filteringFeatureSource; + } + + /** + * Various small methods that need to be called + */ + private miscSetup() { + this.userRelatedState.markLayoutAsVisited(this.layout); + + this.selectedElement.addCallbackAndRunD((feature) => { + // As soon as we have a selected element, we clear the selected element + // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature + // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear + if (feature.properties.id === "last_click") { + return; + } + this.lastClickObject.features.setData([]); + }); + + if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { + Utils.LoadCustomCss(this.layout.customCss); } + } + private initHotkeys() { + Hotkeys.RegisterHotkey( + { nomod: "Escape", onUp: true }, + Translations.t.hotkeyDocumentation.closeSidebar, + () => { + this.selectedElement.setData(undefined); + this.guistate.closeAll(); + } + ); + + Hotkeys.RegisterHotkey( + { + nomod: "b" + }, + Translations.t.hotkeyDocumentation.openLayersPanel, + () => { + if (this.featureSwitches.featureSwitchFilter.data) { + this.guistate.openFilterView(); + } + } + ); + + Hotkeys.RegisterHotkey( + { shift: "O" }, + Translations.t.hotkeyDocumentation.selectMapnik, + () => { + this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto); + } + ); + const setLayerCategory = (category: EliCategory) => { + const available = this.availableLayers.data; + const current = this.mapProperties.rasterLayer; + const best = RasterLayerUtils.SelectBestLayerAccordingTo( + available, + category, + current.data + ); + console.log("Best layer for category", category, "is", best.properties.id); + current.setData(best); + }; + + Hotkeys.RegisterHotkey( + { nomod: "O" }, + Translations.t.hotkeyDocumentation.selectOsmbasedmap, + () => setLayerCategory("osmbasedmap") + ); + + Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => + setLayerCategory("map") + ); + + Hotkeys.RegisterHotkey( + { nomod: "P" }, + Translations.t.hotkeyDocumentation.selectAerial, + () => setLayerCategory("photo") + ); + } + + private addLastClick(last_click: LastClickFeatureSource) { + // The last_click gets a _very_ special treatment as it interacts with various parts + + this.featureProperties.trackFeatureSource(last_click); + this.indexedFeatures.addSource(last_click); + + last_click.features.addCallbackAndRunD((features) => { + if (this.selectedLayer.data?.id === "last_click") { + // The last-click location moved, but we have selected the last click of the previous location + // So, we update _after_ clearing the selection to make sure no stray data is sticking around + this.selectedElement.setData(undefined); + this.selectedElement.setData(features[0]); + } + }); + + new ShowDataLayer(this.map, { + features: new FilteringFeatureSource(this.newPointDialog, last_click), + doShowLayer: this.featureSwitches.featureSwitchEnableLogin, + layer: this.newPointDialog.layerDef, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer, + onClick: (feature: Feature) => { + if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { + this.map.data.flyTo({ + zoom: Constants.minZoomLevelToAddNewPoint, + center: this.mapProperties.lastClickLocation.data + }); + return; + } + // We first clear the selection to make sure no weird state is around + this.selectedLayer.setData(undefined); + this.selectedElement.setData(undefined); + + this.selectedElement.setData(feature); + this.selectedLayer.setData(this.newPointDialog.layerDef); + } + }); + } + + public openNewDialog() { + this.selectedLayer.setData(undefined); + this.selectedElement.setData(undefined); + + const { lon, lat } = this.mapProperties.location.data; + const feature = this.lastClickObject.createFeature(lon, lat) + this.featureProperties.trackFeature(feature) + this.selectedElement.setData(feature); + this.selectedLayer.setData(this.newPointDialog.layerDef); + } + + /** + * Add the special layers to the map + */ + private drawSpecialLayers() { + type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] + const empty = []; /** - * Various small methods that need to be called + * A listing which maps the layerId onto the featureSource */ - private miscSetup() { - this.userRelatedState.markLayoutAsVisited(this.layout) - - this.selectedElement.addCallbackAndRunD((feature) => { - // As soon as we have a selected element, we clear the selected element - // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature - // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear - if (feature.properties.id === "last_click") { - return - } - this.lastClickObject.features.setData([]) - }) - - if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { - Utils.LoadCustomCss(this.layout.customCss) - } + const specialLayers: Record< + Exclude | "current_view", + FeatureSource + > = { + home_location: this.userRelatedState.homeLocation, + gps_location: this.geolocation.currentUserLocation, + gps_location_history: this.geolocation.historicalUserLocations, + gps_track: this.geolocation.historicalUserLocationsTrack, + selected_element: new StaticFeatureSource( + this.selectedElement.map((f) => (f === undefined ? empty : [f])) + ), + range: new StaticFeatureSource( + this.mapProperties.maxbounds.map((bbox) => + bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })] + ) + ), + current_view: this.currentView + }; + if (this.layout?.lockLocation) { + const bbox = new BBox(this.layout.lockLocation); + this.mapProperties.maxbounds.setData(bbox); + ShowDataLayer.showRange( + this.map, + new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]), + this.featureSwitches.featureSwitchIsTesting + ); + } + const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view"); + if (currentViewLayer?.tagRenderings?.length > 0) { + const params = MetaTagging.createExtraFuncParams(this); + this.featureProperties.trackFeatureSource(specialLayers.current_view); + specialLayers.current_view.features.addCallbackAndRunD((features) => { + MetaTagging.addMetatags( + features, + params, + currentViewLayer, + this.layout, + this.osmObjectDownloader, + this.featureProperties + ); + }); } - private initHotkeys() { - Hotkeys.RegisterHotkey( - { nomod: "Escape", onUp: true }, - Translations.t.hotkeyDocumentation.closeSidebar, - () => { - this.selectedElement.setData(undefined) - this.guistate.closeAll() - } - ) + const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range"); - Hotkeys.RegisterHotkey( - { - nomod: "b", - }, - Translations.t.hotkeyDocumentation.openLayersPanel, - () => { - if (this.featureSwitches.featureSwitchFilter.data) { - this.guistate.openFilterView() - } - } - ) + const rangeIsDisplayed = rangeFLayer?.isDisplayed; - Hotkeys.RegisterHotkey( - { shift: "O" }, - Translations.t.hotkeyDocumentation.selectMapnik, - () => { - this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) - } - ) - const setLayerCategory = (category: EliCategory) => { - const available = this.availableLayers.data - const current = this.mapProperties.rasterLayer - const best = RasterLayerUtils.SelectBestLayerAccordingTo( - available, - category, - current.data - ) - console.log("Best layer for category", category, "is", best.properties.id) - current.setData(best) - } - - Hotkeys.RegisterHotkey( - { nomod: "O" }, - Translations.t.hotkeyDocumentation.selectOsmbasedmap, - () => setLayerCategory("osmbasedmap") - ) - - Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => - setLayerCategory("map") - ) - - Hotkeys.RegisterHotkey( - { nomod: "P" }, - Translations.t.hotkeyDocumentation.selectAerial, - () => setLayerCategory("photo") - ) + if ( + !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) + ) { + rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true); } - private addLastClick(last_click: LastClickFeatureSource) { - // The last_click gets a _very_ special treatment as it interacts with various parts + this.layerState.filteredLayers.forEach((flayer) => { + const id = flayer.layerDef.id; + const features: FeatureSource = specialLayers[id]; + if (features === undefined) { + return; + } - const last_click_layer = this.layerState.filteredLayers.get("last_click") - this.featureProperties.trackFeatureSource(last_click) - this.indexedFeatures.addSource(last_click) + this.featureProperties.trackFeatureSource(features); + new ShowDataLayer(this.map, { + features, + doShowLayer: flayer.isDisplayed, + layer: flayer.layerDef, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer + }); + }); + } - last_click.features.addCallbackAndRunD((features) => { - if (this.selectedLayer.data?.id === "last_click") { - // The last-click location moved, but we have selected the last click of the previous location - // So, we update _after_ clearing the selection to make sure no stray data is sticking around - this.selectedElement.setData(undefined) - this.selectedElement.setData(features[0]) - } - }) + /** + * Setup various services for which no reference are needed + */ + private initActors() { + // Unselect the selected element if it is panned out of view + this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { + const selected = this.selectedElement.data; + if (selected === undefined) { + return; + } + const bbox = BBox.get(selected); + if (!bbox.overlapsWith(bounds)) { + this.selectedElement.setData(undefined); + } + }); - new ShowDataLayer(this.map, { - features: new FilteringFeatureSource(last_click_layer, last_click), - doShowLayer: this.featureSwitches.featureSwitchEnableLogin, - layer: last_click_layer.layerDef, - selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - onClick: (feature: Feature) => { - if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { - this.map.data.flyTo({ - zoom: Constants.minZoomLevelToAddNewPoint, - center: this.mapProperties.lastClickLocation.data, - }) - return - } - // We first clear the selection to make sure no weird state is around - this.selectedLayer.setData(undefined) - this.selectedElement.setData(undefined) - - this.selectedElement.setData(feature) - this.selectedLayer.setData(last_click_layer.layerDef) - }, - }) - } - - /** - * Add the special layers to the map - */ - private drawSpecialLayers() { - type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] - const empty = [] - /** - * A listing which maps the layerId onto the featureSource - */ - const specialLayers: Record< - Exclude | "current_view", - FeatureSource - > = { - home_location: this.userRelatedState.homeLocation, - gps_location: this.geolocation.currentUserLocation, - gps_location_history: this.geolocation.historicalUserLocations, - gps_track: this.geolocation.historicalUserLocationsTrack, - selected_element: new StaticFeatureSource( - this.selectedElement.map((f) => (f === undefined ? empty : [f])) - ), - range: new StaticFeatureSource( - this.mapProperties.maxbounds.map((bbox) => - bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })] - ) - ), - current_view: this.currentView, - } - if (this.layout?.lockLocation) { - const bbox = new BBox(this.layout.lockLocation) - this.mapProperties.maxbounds.setData(bbox) - ShowDataLayer.showRange( - this.map, - new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]), - this.featureSwitches.featureSwitchIsTesting - ) - } - const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") - if (currentViewLayer?.tagRenderings?.length > 0) { - const params = MetaTagging.createExtraFuncParams(this) - this.featureProperties.trackFeatureSource(specialLayers.current_view) - specialLayers.current_view.features.addCallbackAndRunD((features) => { - MetaTagging.addMetatags( - features, - params, - currentViewLayer, - this.layout, - this.osmObjectDownloader, - this.featureProperties - ) - }) - } - - const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") - - const rangeIsDisplayed = rangeFLayer?.isDisplayed - - if ( - !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) - ) { - rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) - } - - this.layerState.filteredLayers.forEach((flayer) => { - const id = flayer.layerDef.id - const features: FeatureSource = specialLayers[id] - if (features === undefined) { - return - } - - this.featureProperties.trackFeatureSource(features) - new ShowDataLayer(this.map, { - features, - doShowLayer: flayer.isDisplayed, - layer: flayer.layerDef, - selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - }) - }) - } - - /** - * Setup various services for which no reference are needed - */ - private initActors() { - // Unselect the selected element if it is panned out of view - this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { - const selected = this.selectedElement.data - if (selected === undefined) { - return - } - const bbox = BBox.get(selected) - if (!bbox.overlapsWith(bounds)) { - this.selectedElement.setData(undefined) - } - }) - - this.selectedElement.addCallback((selected) => { - if (selected === undefined) { - // We did _unselect_ an item - we always remove the lastclick-object - this.lastClickObject.features.setData([]) - this.selectedLayer.setData(undefined) - } - }) - new ThemeViewStateHashActor(this) - new MetaTagging(this) - new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) - new ChangeToElementsActor(this.changes, this.featureProperties) - new PendingChangesUploader(this.changes, this.selectedElement) - new SelectedElementTagsUpdater(this) - new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) - new PreferredRasterLayerSelector( - this.mapProperties.rasterLayer, - this.availableLayers, - this.featureSwitches.backgroundLayerId, - this.userRelatedState.preferredBackgroundLayer - ) - } + this.selectedElement.addCallback((selected) => { + if (selected === undefined) { + // We did _unselect_ an item - we always remove the lastclick-object + this.lastClickObject.features.setData([]); + this.selectedLayer.setData(undefined); + } + }); + new ThemeViewStateHashActor(this); + new MetaTagging(this); + new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this); + new ChangeToElementsActor(this.changes, this.featureProperties); + new PendingChangesUploader(this.changes, this.selectedElement); + new SelectedElementTagsUpdater(this); + new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers); + new PreferredRasterLayerSelector( + this.mapProperties.rasterLayer, + this.availableLayers, + this.featureSwitches.backgroundLayerId, + this.userRelatedState.preferredBackgroundLayer + ); + } } diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index dff70dc7e..43f86cda0 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -1,115 +1,114 @@
@@ -169,22 +168,30 @@
-
- - state.guistate.openFilterView()}> - - +
+ + + + +
@@ -319,7 +326,7 @@ new CopyrightPanel(state)} slot="content3" /> -
+
@@ -347,7 +354,9 @@ - + { + selectedElement.setData(undefined) + }}>