UX: add possibility to select map features by only using the keyboard, see #1181

This commit is contained in:
Pieter Vander Vennet 2023-11-16 03:32:04 +01:00
parent 7469a0d607
commit 48171d30f5
9 changed files with 304 additions and 104 deletions

View file

@ -404,6 +404,7 @@
"key": "Key combination", "key": "Key combination",
"openLayersPanel": "Opens the layers and filters panel", "openLayersPanel": "Opens the layers and filters panel",
"selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers", "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", "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", "selectMapnik": "Set the background layer to OpenStreetMap-carto",
"selectOsmbasedmap": "Set the background layer to on OpenStreetMap-based map (or disable the background raster layer)", "selectOsmbasedmap": "Set the background layer to on OpenStreetMap-based map (or disable the background raster layer)",

View file

@ -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]
)
}
}

View file

@ -12,7 +12,7 @@ import { GlobalFilter } from "./GlobalFilter"
export default class FilteredLayer { 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> readonly isDisplayed: UIEventSource<boolean>
/** /**

View file

@ -14,6 +14,7 @@ export interface MapProperties {
readonly allowRotating: UIEventSource<true | boolean> readonly allowRotating: UIEventSource<true | boolean>
readonly lastClickLocation: Store<{ lon: number; lat: number }> readonly lastClickLocation: Store<{ lon: number; lat: number }>
readonly allowZooming: UIEventSource<true | boolean> readonly allowZooming: UIEventSource<true | boolean>
readonly lastKeyNavigation: UIEventSource<number>
} }
export interface ExportableMap { export interface ExportableMap {

View file

@ -57,6 +57,7 @@ import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
import { Imgur } from "../Logic/ImageProviders/Imgur" 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 indexedFeatures: IndexedFeatureSource & LayoutSource
readonly currentView: FeatureSource<Feature<Polygon>> readonly currentView: FeatureSource<Feature<Polygon>>
readonly featuresInView: FeatureSource readonly featuresInView: FeatureSource
/**
* Contains a few (<10) >features that are near the center of the map.
*/
readonly closestFeatures: FeatureSource
readonly newFeatures: WritableFeatureSource readonly newFeatures: WritableFeatureSource
readonly layerState: LayerState readonly layerState: LayerState
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
@ -131,6 +136,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.map = new UIEventSource<MlMap>(undefined) this.map = new UIEventSource<MlMap>(undefined)
const initial = new InitialMapPositioning(layout) const initial = new InitialMapPositioning(layout)
this.mapProperties = new MapLibreAdaptor(this.map, initial) this.mapProperties = new MapLibreAdaptor(this.map, initial)
const geolocationState = new GeoLocationState() const geolocationState = new GeoLocationState()
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
@ -234,6 +240,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
}) })
) )
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading this.dataIsLoading = layoutSource.isLoading
const indexedElements = this.indexedFeatures const indexedElements = this.indexedFeatures
@ -331,7 +338,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
) )
this.perLayerFiltered = this.showNormalDataOn(this.map) 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.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
this.imageUploadManager = new ImageUploadManager( this.imageUploadManager = new ImageUploadManager(
layout, layout,
@ -364,6 +377,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
return true return true
}) })
} }
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> { public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
const filteringFeatureSource = new Map<string, FilteringFeatureSource>() const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
this.perLayer.forEach((fs, layerName) => { this.perLayer.forEach((fs, layerName) => {
@ -404,6 +418,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
return filteringFeatureSource 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 * 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() { private initHotkeys() {
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "Escape", onUp: true }, { 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) => { this.featureSwitches.featureSwitchBackgroundSelection.addCallbackAndRun((enable) => {
if (!enable) { if (!enable) {
return 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 * Add the special layers to the map
*/ */

View file

@ -38,9 +38,9 @@
<div class="flex flex-col"> <div class="flex flex-col">
<!-- Title element--> <!-- Title element-->
<h3> <h3>
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} /> <TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
</h3> </h3>
<div <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" 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"
> >

View file

@ -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} />

View file

@ -9,7 +9,6 @@ import SvelteUIElement from "../Base/SvelteUIElement"
import MaplibreMap from "./MaplibreMap.svelte" import MaplibreMap from "./MaplibreMap.svelte"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties" import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import * as htmltoimage from "html-to-image" import * as htmltoimage from "html-to-image"
import { draw } from "svelte/transition"
/** /**
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` * 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 lastClickLocation: Store<undefined | { lon: number; lat: number }>
readonly minzoom: UIEventSource<number> readonly minzoom: UIEventSource<number>
readonly maxzoom: 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> private readonly _maplibreMap: Store<MLMap>
/** /**
* Used for internal bookkeeping (to remove a rasterLayer when done loading) * 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) => { map.on("dblclick", (e) => {
handleClick(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((_) => this.rasterLayer.addCallbackAndRun((_) =>

View file

@ -1,120 +1,127 @@
<script lang="ts"> <script lang="ts">
import { Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl" import { Map as MlMap } from "maplibre-gl";
import MaplibreMap from "./Map/MaplibreMap.svelte" import MaplibreMap from "./Map/MaplibreMap.svelte";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState" import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import MapControlButton from "./Base/MapControlButton.svelte" import MapControlButton from "./Base/MapControlButton.svelte";
import ToSvelte from "./Base/ToSvelte.svelte" import ToSvelte from "./Base/ToSvelte.svelte";
import If from "./Base/If.svelte" import If from "./Base/If.svelte";
import { GeolocationControl } from "./BigComponents/GeolocationControl" import { GeolocationControl } from "./BigComponents/GeolocationControl";
import type { Feature } from "geojson" import type { Feature } from "geojson";
import SelectedElementView from "./BigComponents/SelectedElementView.svelte" import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
import LayerConfig from "../Models/ThemeConfig/LayerConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import Filterview from "./BigComponents/Filterview.svelte" import Filterview from "./BigComponents/Filterview.svelte";
import ThemeViewState from "../Models/ThemeViewState" import ThemeViewState from "../Models/ThemeViewState";
import type { MapProperties } from "../Models/MapProperties" import type { MapProperties } from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte" import Geosearch from "./BigComponents/Geosearch.svelte";
import Translations from "./i18n/Translations" import Translations from "./i18n/Translations";
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid" import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte" import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte" import FloatOver from "./Base/FloatOver.svelte";
import PrivacyPolicy from "./BigComponents/PrivacyPolicy" import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
import Constants from "../Models/Constants" import Constants from "../Models/Constants";
import TabbedGroup from "./Base/TabbedGroup.svelte" import TabbedGroup from "./Base/TabbedGroup.svelte";
import UserRelatedState from "../Logic/State/UserRelatedState" import UserRelatedState from "../Logic/State/UserRelatedState";
import LoginToggle from "./Base/LoginToggle.svelte" import LoginToggle from "./Base/LoginToggle.svelte";
import LoginButton from "./Base/LoginButton.svelte" import LoginButton from "./Base/LoginButton.svelte";
import CopyrightPanel from "./BigComponents/CopyrightPanel" import CopyrightPanel from "./BigComponents/CopyrightPanel";
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
import ModalRight from "./Base/ModalRight.svelte" import ModalRight from "./Base/ModalRight.svelte";
import { Utils } from "../Utils" import { Utils } from "../Utils";
import Hotkeys from "./Base/Hotkeys" import Hotkeys from "./Base/Hotkeys";
import { VariableUiElement } from "./Base/VariableUIElement" import { VariableUiElement } from "./Base/VariableUIElement";
import SvelteUIElement from "./Base/SvelteUIElement" import SvelteUIElement from "./Base/SvelteUIElement";
import OverlayToggle from "./BigComponents/OverlayToggle.svelte" import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
import LevelSelector from "./BigComponents/LevelSelector.svelte" import LevelSelector from "./BigComponents/LevelSelector.svelte";
import ExtraLinkButton from "./BigComponents/ExtraLinkButton" import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
import Svg from "../Svg" import Svg from "../Svg";
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
import type { RasterLayerPolygon } from "../Models/RasterLayers" import type { RasterLayerPolygon } from "../Models/RasterLayers";
import { AvailableRasterLayers } from "../Models/RasterLayers" import { AvailableRasterLayers } from "../Models/RasterLayers";
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
import IfHidden from "./Base/IfHidden.svelte" import IfHidden from "./Base/IfHidden.svelte";
import { onDestroy } from "svelte" import { onDestroy } from "svelte";
import { OpenJosm } from "./BigComponents/OpenJosm" import { OpenJosm } from "./BigComponents/OpenJosm";
import MapillaryLink from "./BigComponents/MapillaryLink.svelte" import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte" import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
import StateIndicator from "./BigComponents/StateIndicator.svelte" import StateIndicator from "./BigComponents/StateIndicator.svelte";
import LanguagePicker from "./LanguagePicker" import LanguagePicker from "./LanguagePicker";
import Locale from "./i18n/Locale" import Locale from "./i18n/Locale";
import ShareScreen from "./BigComponents/ShareScreen.svelte" import ShareScreen from "./BigComponents/ShareScreen.svelte";
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte" import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte" import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
import Cross from "../assets/svg/Cross.svelte";
import Summary from "./BigComponents/Summary.svelte";
export let state: ThemeViewState export let state: ThemeViewState;
let layout = state.layout let layout = state.layout;
let maplibremap: UIEventSource<MlMap> = state.map let maplibremap: UIEventSource<MlMap> = state.map;
let selectedElement: UIEventSource<Feature> = state.selectedElement let selectedElement: UIEventSource<Feature> = state.selectedElement;
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer 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( const selectedElementView = selectedElement.map(
(selectedElement) => { (selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements // Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected // 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 // 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) { if (selectedElement === undefined || layer === undefined) {
return undefined return undefined;
} }
if (!(layer.tagRenderings?.length > 0) || layer.title === 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, { return new SvelteUIElement(SelectedElementView, {
state, state,
layer, layer,
selectedElement, selectedElement,
tags, tags
}).SetClass("h-full w-full") }).SetClass("h-full w-full");
}, },
[selectedLayer] [selectedLayer]
) );
const selectedElementTitle = selectedElement.map( const selectedElementTitle = selectedElement.map(
(selectedElement) => { (selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements // Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected // 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 // 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) { if (selectedElement === undefined || layer === undefined) {
return undefined return undefined;
} }
const tags = state.featureProperties.getStore(selectedElement.properties.id) const tags = state.featureProperties.getStore(selectedElement.properties.id);
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags }) return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags });
}, },
[selectedLayer] [selectedLayer]
) );
let mapproperties: MapProperties = state.mapProperties let mapproperties: MapProperties = state.mapProperties;
let featureSwitches: FeatureSwitchState = state.featureSwitches let featureSwitches: FeatureSwitchState = state.featureSwitches;
let availableLayers = state.availableLayers let availableLayers = state.availableLayers;
let userdetails = state.osmConnection.userDetails let userdetails = state.osmConnection.userDetails;
let currentViewLayer = layout.layers.find((l) => l.id === "current_view") let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
let rasterLayerName = let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
onDestroy( onDestroy(
rasterLayer.addCallbackAndRunD((l) => { rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name rasterLayerName = l.properties.name;
}) })
) );
</script> </script>
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
@ -216,6 +223,15 @@
</div> </div>
</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"> <div class="flex flex-col items-end">
<!-- bottom right elements --> <!-- bottom right elements -->
<If condition={state.floors.map((f) => f.length > 1)}> <If condition={state.floors.map((f) => f.length > 1)}>
@ -247,15 +263,13 @@
</div> </div>
<LoginToggle ignoreLoading={true} {state}> <LoginToggle ignoreLoading={true} {state}>
<If condition={state.userRelatedState.showCrosshair.map((s) => s === "yes")}> {#if $showCrosshair === "yes" && ($currentZoom >= 17 || $arrowKeysWereUsed !== undefined) }
<If condition={state.mapProperties.zoom.map((z) => z >= 17)}> <div
<div class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center justify-center"
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center justify-center" >
> <Cross class="h-4 w-4" />
<ToSvelte construct={Svg.cross_svg()} /> </div>
</div> {/if}
</If>
</If>
</LoginToggle> </LoginToggle>
<If <If