UX: add possibility to select map features by only using the keyboard, see #1181
This commit is contained in:
parent
7469a0d607
commit
48171d30f5
9 changed files with 304 additions and 104 deletions
|
@ -404,6 +404,7 @@
|
|||
"key": "Key combination",
|
||||
"openLayersPanel": "Opens the layers and filters panel",
|
||||
"selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers",
|
||||
"selectItem": "Select the POI which is closest to the map center (crosshair). Only when in keyboard navigation is used",
|
||||
"selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers",
|
||||
"selectMapnik": "Set the background layer to OpenStreetMap-carto",
|
||||
"selectOsmbasedmap": "Set the background layer to on OpenStreetMap-based map (or disable the background raster layer)",
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import { FeatureSource } from "../FeatureSource"
|
||||
import { Store, Stores, UIEventSource } from "../../UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import { GeoOperations } from "../../GeoOperations"
|
||||
import FilteringFeatureSource from "./FilteringFeatureSource"
|
||||
import LayerState from "../../State/LayerState"
|
||||
|
||||
export default class NearbyFeatureSource implements FeatureSource {
|
||||
public readonly features: Store<Feature[]>
|
||||
private readonly _targetPoint: Store<{ lon: number; lat: number }>
|
||||
private readonly _numberOfNeededFeatures: number
|
||||
private readonly _currentZoom: Store<number>
|
||||
|
||||
constructor(
|
||||
targetPoint: Store<{ lon: number; lat: number }>,
|
||||
sources: ReadonlyMap<string, FilteringFeatureSource>,
|
||||
numberOfNeededFeatures?: number,
|
||||
layerState?: LayerState,
|
||||
currentZoom?: Store<number>
|
||||
) {
|
||||
this._targetPoint = targetPoint.stabilized(100)
|
||||
this._numberOfNeededFeatures = numberOfNeededFeatures
|
||||
this._currentZoom = currentZoom.stabilized(500)
|
||||
|
||||
const allSources: Store<{ feat: Feature; d: number }[]>[] = []
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the given source by distance, slices down to the required number
|
||||
*/
|
||||
private createSource(
|
||||
source: Store<Feature[]>,
|
||||
minZoom: number,
|
||||
isActive?: Store<boolean>
|
||||
): Store<{ feat: Feature; d: number }[]> {
|
||||
const empty = []
|
||||
return source.stabilized(100).map(
|
||||
(feats) => {
|
||||
if (isActive && !isActive.data) {
|
||||
return empty
|
||||
}
|
||||
|
||||
if (this._currentZoom.data < minZoom) {
|
||||
return empty
|
||||
}
|
||||
const point = this._targetPoint.data
|
||||
const lonLat = <[number, number]>[point.lon, point.lat]
|
||||
const withDistance = feats.map((feat) => ({
|
||||
d: GeoOperations.distanceBetween(
|
||||
lonLat,
|
||||
GeoOperations.centerpointCoordinates(feat)
|
||||
),
|
||||
feat,
|
||||
}))
|
||||
withDistance.sort((a, b) => a.d - b.d)
|
||||
if (this._numberOfNeededFeatures !== undefined) {
|
||||
return withDistance.slice(0, this._numberOfNeededFeatures)
|
||||
}
|
||||
return withDistance
|
||||
},
|
||||
[this._targetPoint, isActive, this._currentZoom]
|
||||
)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import { GlobalFilter } from "./GlobalFilter"
|
|||
|
||||
export default class FilteredLayer {
|
||||
/**
|
||||
* Wether or not the specified layer is shown
|
||||
* Whether or not the specified layer is enabled by the user
|
||||
*/
|
||||
readonly isDisplayed: UIEventSource<boolean>
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface MapProperties {
|
|||
readonly allowRotating: UIEventSource<true | boolean>
|
||||
readonly lastClickLocation: Store<{ lon: number; lat: number }>
|
||||
readonly allowZooming: UIEventSource<true | boolean>
|
||||
readonly lastKeyNavigation: UIEventSource<number>
|
||||
}
|
||||
|
||||
export interface ExportableMap {
|
||||
|
|
|
@ -57,6 +57,7 @@ import FilteredLayer from "./FilteredLayer"
|
|||
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
|
||||
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||
import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource"
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -95,6 +96,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
readonly indexedFeatures: IndexedFeatureSource & LayoutSource
|
||||
readonly currentView: FeatureSource<Feature<Polygon>>
|
||||
readonly featuresInView: FeatureSource
|
||||
/**
|
||||
* Contains a few (<10) >features that are near the center of the map.
|
||||
*/
|
||||
readonly closestFeatures: FeatureSource
|
||||
readonly newFeatures: WritableFeatureSource
|
||||
readonly layerState: LayerState
|
||||
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
|
||||
|
@ -131,6 +136,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.map = new UIEventSource<MlMap>(undefined)
|
||||
const initial = new InitialMapPositioning(layout)
|
||||
this.mapProperties = new MapLibreAdaptor(this.map, initial)
|
||||
|
||||
const geolocationState = new GeoLocationState()
|
||||
|
||||
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
|
||||
|
@ -234,6 +240,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
})
|
||||
)
|
||||
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
|
||||
|
||||
this.dataIsLoading = layoutSource.isLoading
|
||||
|
||||
const indexedElements = this.indexedFeatures
|
||||
|
@ -331,7 +338,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
)
|
||||
|
||||
this.perLayerFiltered = this.showNormalDataOn(this.map)
|
||||
|
||||
this.closestFeatures = new NearbyFeatureSource(
|
||||
this.mapProperties.location,
|
||||
this.perLayerFiltered,
|
||||
3,
|
||||
this.layerState,
|
||||
this.mapProperties.zoom
|
||||
)
|
||||
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
|
||||
this.imageUploadManager = new ImageUploadManager(
|
||||
layout,
|
||||
|
@ -364,6 +377,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
return true
|
||||
})
|
||||
}
|
||||
|
||||
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
|
||||
const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
|
||||
this.perLayer.forEach((fs, layerName) => {
|
||||
|
@ -404,6 +418,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
return filteringFeatureSource
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Various small methods that need to be called
|
||||
*/
|
||||
|
@ -425,6 +450,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the feature that is 'i' closest to the map center
|
||||
* @param i
|
||||
* @private
|
||||
*/
|
||||
private selectClosestAtCenter(i: number = 0) {
|
||||
const toSelect = this.closestFeatures.features.data[i]
|
||||
if (!toSelect) {
|
||||
return
|
||||
}
|
||||
const layer = this.layout.getMatchingLayer(toSelect.properties)
|
||||
this.selectedElement.setData(undefined)
|
||||
this.selectedLayer.setData(layer)
|
||||
this.selectedElement.setData(toSelect)
|
||||
}
|
||||
private initHotkeys() {
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "Escape", onUp: true },
|
||||
|
@ -436,6 +476,36 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
}
|
||||
)
|
||||
|
||||
this.mapProperties.lastKeyNavigation.addCallbackAndRunD((_) => {
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: " ",
|
||||
onUp: true,
|
||||
},
|
||||
Translations.t.hotkeyDocumentation.selectItem,
|
||||
() => this.selectClosestAtCenter(0)
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: "Spacebar",
|
||||
onUp: true,
|
||||
},
|
||||
Translations.t.hotkeyDocumentation.selectItem,
|
||||
() => this.selectClosestAtCenter(0)
|
||||
)
|
||||
for (let i = 1; i < 9; i++) {
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: "" + i,
|
||||
onUp: true,
|
||||
},
|
||||
Translations.t.hotkeyDocumentation.selectItem,
|
||||
() => this.selectClosestAtCenter(i - 1)
|
||||
)
|
||||
}
|
||||
return true // unregister
|
||||
})
|
||||
|
||||
this.featureSwitches.featureSwitchBackgroundSelection.addCallbackAndRun((enable) => {
|
||||
if (!enable) {
|
||||
return
|
||||
|
@ -531,17 +601,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
})
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
<div class="flex flex-col">
|
||||
<!-- Title element-->
|
||||
<h3>
|
||||
|
||||
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
|
||||
</h3>
|
||||
|
||||
<div
|
||||
class="no-weblate title-icons links-as-button mr-2 flex flex-row flex-wrap items-center gap-x-0.5 p-1 pt-0.5 sm:pt-1"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import type { Feature } from "geojson";
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import SelectedElementTitle from "./SelectedElementTitle.svelte";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
|
||||
|
||||
export let state: SpecialVisualizationState;
|
||||
export let feature: Feature
|
||||
let id = feature.properties.id
|
||||
let tags = state.featureProperties.getStore(id);
|
||||
let layer: LayerConfig = state.layout.getMatchingLayer(tags.data)
|
||||
|
||||
</script>
|
||||
<TagRenderingAnswer config={layer.title} selectedElement={feature} {state} {tags} {layer} />
|
||||
|
|
@ -9,7 +9,6 @@ import SvelteUIElement from "../Base/SvelteUIElement"
|
|||
import MaplibreMap from "./MaplibreMap.svelte"
|
||||
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||
import * as htmltoimage from "html-to-image"
|
||||
import { draw } from "svelte/transition"
|
||||
|
||||
/**
|
||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||
|
@ -41,6 +40,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
readonly lastClickLocation: Store<undefined | { lon: number; lat: number }>
|
||||
readonly minzoom: UIEventSource<number>
|
||||
readonly maxzoom: UIEventSource<number>
|
||||
/**
|
||||
* When was the last navigation by arrow keys?
|
||||
* If set, this is a hint to use arrow compatibility
|
||||
* Number of _seconds_ since epoch
|
||||
*/
|
||||
readonly lastKeyNavigation: UIEventSource<number> = new UIEventSource<number>(undefined)
|
||||
private readonly _maplibreMap: Store<MLMap>
|
||||
/**
|
||||
* Used for internal bookkeeping (to remove a rasterLayer when done loading)
|
||||
|
@ -128,6 +133,16 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
map.on("dblclick", (e) => {
|
||||
handleClick(e)
|
||||
})
|
||||
map.getContainer().addEventListener("keydown", (event) => {
|
||||
if (
|
||||
event.key === "ArrowRight" ||
|
||||
event.key === "ArrowLeft" ||
|
||||
event.key === "ArrowUp" ||
|
||||
event.key === "ArrowDown"
|
||||
) {
|
||||
this.lastKeyNavigation.setData(Date.now() / 1000)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.rasterLayer.addCallbackAndRun((_) =>
|
||||
|
|
|
@ -1,120 +1,127 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||
import MapControlButton from "./Base/MapControlButton.svelte"
|
||||
import ToSvelte from "./Base/ToSvelte.svelte"
|
||||
import If from "./Base/If.svelte"
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
||||
import type { Feature } from "geojson"
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import Filterview from "./BigComponents/Filterview.svelte"
|
||||
import ThemeViewState from "../Models/ThemeViewState"
|
||||
import type { MapProperties } from "../Models/MapProperties"
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte"
|
||||
import Translations from "./i18n/Translations"
|
||||
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||
import FloatOver from "./Base/FloatOver.svelte"
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
||||
import Constants from "../Models/Constants"
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||
import LoginButton from "./Base/LoginButton.svelte"
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
||||
import ModalRight from "./Base/ModalRight.svelte"
|
||||
import { Utils } from "../Utils"
|
||||
import Hotkeys from "./Base/Hotkeys"
|
||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
||||
import Svg from "../Svg"
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
||||
import IfHidden from "./Base/IfHidden.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import { OpenJosm } from "./BigComponents/OpenJosm"
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
||||
import LanguagePicker from "./LanguagePicker"
|
||||
import Locale from "./i18n/Locale"
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||
import { Map as MlMap } from "maplibre-gl";
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||
import If from "./Base/If.svelte";
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||
import type { Feature } from "geojson";
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import Filterview from "./BigComponents/Filterview.svelte";
|
||||
import ThemeViewState from "../Models/ThemeViewState";
|
||||
import type { MapProperties } from "../Models/MapProperties";
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||
import FloatOver from "./Base/FloatOver.svelte";
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||
import Constants from "../Models/Constants";
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import LoginButton from "./Base/LoginButton.svelte";
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
||||
import ModalRight from "./Base/ModalRight.svelte";
|
||||
import { Utils } from "../Utils";
|
||||
import Hotkeys from "./Base/Hotkeys";
|
||||
import { VariableUiElement } from "./Base/VariableUIElement";
|
||||
import SvelteUIElement from "./Base/SvelteUIElement";
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||
import Svg from "../Svg";
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||
import IfHidden from "./Base/IfHidden.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { OpenJosm } from "./BigComponents/OpenJosm";
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||
import LanguagePicker from "./LanguagePicker";
|
||||
import Locale from "./i18n/Locale";
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
|
||||
import Cross from "../assets/svg/Cross.svelte";
|
||||
import Summary from "./BigComponents/Summary.svelte";
|
||||
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
export let state: ThemeViewState;
|
||||
let layout = state.layout;
|
||||
|
||||
let maplibremap: UIEventSource<MlMap> = state.map
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
||||
let maplibremap: UIEventSource<MlMap> = state.map;
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement;
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer;
|
||||
|
||||
let currentZoom = state.mapProperties.zoom;
|
||||
let showCrosshair = state.userRelatedState.showCrosshair;
|
||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation;
|
||||
let centerFeatures = state.closestFeatures.features;
|
||||
$: console.log("Centerfeatures are", $centerFeatures)
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||
return new SvelteUIElement(SelectedElementView, {
|
||||
state,
|
||||
layer,
|
||||
selectedElement,
|
||||
tags,
|
||||
}).SetClass("h-full w-full")
|
||||
tags
|
||||
}).SetClass("h-full w-full");
|
||||
},
|
||||
[selectedLayer]
|
||||
)
|
||||
);
|
||||
|
||||
const selectedElementTitle = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id)
|
||||
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
|
||||
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
||||
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags });
|
||||
},
|
||||
[selectedLayer]
|
||||
)
|
||||
);
|
||||
|
||||
let mapproperties: MapProperties = state.mapProperties
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
||||
let availableLayers = state.availableLayers
|
||||
let userdetails = state.osmConnection.userDetails
|
||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||
let mapproperties: MapProperties = state.mapProperties;
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches;
|
||||
let availableLayers = state.availableLayers;
|
||||
let userdetails = state.osmConnection.userDetails;
|
||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
|
||||
let rasterLayerName =
|
||||
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name
|
||||
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
|
||||
onDestroy(
|
||||
rasterLayer.addCallbackAndRunD((l) => {
|
||||
rasterLayerName = l.properties.name
|
||||
rasterLayerName = l.properties.name;
|
||||
})
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
|
@ -216,6 +223,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{#if $arrowKeysWereUsed !== undefined}
|
||||
<div class="pointer-events-auto interactive p-1">
|
||||
{#each $centerFeatures as feat, i (feat.properties.id)}
|
||||
<div class="flex">
|
||||
<b>{i+1}.</b><Summary {state} feature={feat}/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col items-end">
|
||||
<!-- bottom right elements -->
|
||||
<If condition={state.floors.map((f) => f.length > 1)}>
|
||||
|
@ -247,15 +263,13 @@
|
|||
</div>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
<If condition={state.userRelatedState.showCrosshair.map((s) => s === "yes")}>
|
||||
<If condition={state.mapProperties.zoom.map((z) => z >= 17)}>
|
||||
{#if $showCrosshair === "yes" && ($currentZoom >= 17 || $arrowKeysWereUsed !== undefined) }
|
||||
<div
|
||||
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<ToSvelte construct={Svg.cross_svg()} />
|
||||
<Cross class="h-4 w-4" />
|
||||
</div>
|
||||
</If>
|
||||
</If>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
||||
<If
|
||||
|
|
Loading…
Reference in a new issue