UX: add crosshair to map center

This commit is contained in:
Pieter Vander Vennet 2023-10-15 01:05:09 +02:00
parent a464877629
commit 4da5261db5
5 changed files with 132 additions and 82 deletions

View file

@ -331,6 +331,30 @@
} }
] ]
}, },
{
"id": "show_crosshair",
"question": {
"en":"Should a crosshair be shown in the center of the display?"
},
"questionHint": {
"en":"This can help to accurately position a new element"
},
"mappings": [
{
"if": "mapcomplete-show_crosshair=yes",
"then": "Show a crosshair in the center of the map (when zoomed in above level 17)"
},
{
"if": "mapcomplete-show_crosshair=no",
"then": "Do not show a crosshair in the center of the map"
},
{
"if": "mapcomplete-show_crosshair=",
"then": "Do not show a crosshair in the center of the map",
"hideInAnswer": true
}
]
},
{ {
"id": "fixate-north", "id": "fixate-north",
"question": { "question": {

View file

@ -249,6 +249,14 @@
], ],
"sources": [] "sources": []
}, },
{
"path": "cross.svg",
"license": "CC0-1.0",
"authors": [
"Pieter Vander Vennet"
],
"sources": []
},
{ {
"path": "cross_bottom_right.svg", "path": "cross_bottom_right.svg",
"license": "TRIVIAL", "license": "TRIVIAL",

View file

@ -39,6 +39,7 @@ export default class UserRelatedState {
public readonly installedUserThemes: Store<string[]> public readonly installedUserThemes: Store<string[]>
public readonly showAllQuestionsAtOnce: UIEventSource<boolean> public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
public readonly showCrosshair: UIEventSource<"yes" | undefined>
public readonly fixateNorth: UIEventSource<undefined | "yes"> public readonly fixateNorth: UIEventSource<undefined | "yes">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string> public readonly language: UIEventSource<string>
@ -102,6 +103,7 @@ export default class UserRelatedState {
) )
this.language = this.osmConnection.GetPreference("language") this.language = this.osmConnection.GetPreference("language")
this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags") this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags")
this.showCrosshair = <UIEventSource<any>>this.osmConnection.GetPreference("show_crosshair")
this.fixateNorth = <UIEventSource<"yes">>this.osmConnection.GetPreference("fixate-north") this.fixateNorth = <UIEventSource<"yes">>this.osmConnection.GetPreference("fixate-north")
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")

View file

@ -4,6 +4,7 @@
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import Translations from "../i18n/Translations"
/** /**
* A 'TagHint' will show the given tags in a human readable form. * A 'TagHint' will show the given tags in a human readable form.
@ -25,7 +26,7 @@
{#if $userDetails.loggedIn} {#if $userDetails.loggedIn}
<div> <div>
{#if tags === undefined} {#if tags === undefined}
<slot name="no-tags">No tags</slot> <slot name="no-tags"><Tr cls="subtle" t={Translations.t.general.noTagsSelected}></Tr></slot>
{:else if embedIn === undefined} {:else if embedIn === undefined}
<FromHtml src={tagsExplanation} /> <FromHtml src={tagsExplanation} />
{:else} {:else}

View file

@ -1,114 +1,114 @@
<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"
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
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, { state, layer, selectedElement, tags }); return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags })
}, },
[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">
@ -233,6 +233,17 @@
</div> </div>
</div> </div>
<LoginToggle ignoreLoading={true} {state }>
<If condition={state.userRelatedState.showCrosshair.map(s => s === "yes")}>
<If condition={state.mapProperties.zoom.map(z => z >= 17)}>
<div class="absolute top-0 left-0 flex items-center justify-center pointer-events-none w-full h-full">
<ToSvelte construct={Svg.cross_svg()} />
</div>
</If>
</If>
</LoginToggle>
<If <If
condition={selectedElementView.map( condition={selectedElementView.map(
(v) => (v) =>
@ -257,6 +268,7 @@
</ModalRight> </ModalRight>
</If> </If>
<If <If
condition={selectedElementView.map( condition={selectedElementView.map(
(v) => (v) =>
@ -364,7 +376,8 @@
<!-- Menu page --> <!-- Menu page -->
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false) }> <FloatOver on:close={() => state.guistate.menuIsOpened.setData(false) }>
<span slot="close-button"><!-- Hide the default close button --></span> <span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup condition1={featureSwitches.featureSwitchEnableLogin} condition2={state.featureSwitches. featureSwitchCommunityIndex} <TabbedGroup condition1={featureSwitches.featureSwitchEnableLogin}
condition2={state.featureSwitches. featureSwitchCommunityIndex}
tab={state.guistate.menuViewTabIndex}> tab={state.guistate.menuViewTabIndex}>
<div slot="post-tablist"> <div slot="post-tablist">
<XCircleIcon <XCircleIcon
@ -464,3 +477,5 @@
</TabbedGroup> </TabbedGroup>
</FloatOver> </FloatOver>
</If> </If>