UX: fix #1805, disable zoom-in and zoom-out buttons when maxrange reached
This commit is contained in:
parent
346f45cff8
commit
48159b25f7
13 changed files with 202 additions and 184 deletions
|
@ -439,6 +439,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.selectedElement.setData(feature)
|
this.selectedElement.setData(feature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer {
|
||||||
|
const id = "gps_location"
|
||||||
|
const flayerGps = this.layerState.filteredLayers.get(id)
|
||||||
|
const features = this.geolocation.currentUserLocation
|
||||||
|
return new ShowDataLayer(map, {
|
||||||
|
features,
|
||||||
|
doShowLayer: flayerGps.isDisplayed,
|
||||||
|
layer: flayerGps.layerDef,
|
||||||
|
metaTags: this.userRelatedState.preferencesAsTags,
|
||||||
|
selectedElement: this.selectedElement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various small methods that need to be called
|
* Various small methods that need to be called
|
||||||
*/
|
*/
|
||||||
|
@ -671,6 +684,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
)
|
)
|
||||||
return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
|
return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the special layers to the map
|
* Add the special layers to the map
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge"
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation"
|
||||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||||
|
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A round button with an icon and possible a small text, which hovers above the map
|
* A round button with an icon and possible a small text, which hovers above the map
|
||||||
*/
|
*/
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
|
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
|
||||||
|
export let enabled : Store<boolean> = new ImmutableStore(true)
|
||||||
export let arialabel: Translation = undefined
|
export let arialabel: Translation = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@
|
||||||
on:click={(e) => dispatch("click", e)}
|
on:click={(e) => dispatch("click", e)}
|
||||||
on:keydown
|
on:keydown
|
||||||
use:ariaLabel={arialabel}
|
use:ariaLabel={arialabel}
|
||||||
class={twJoin("pointer-events-auto relative h-fit w-fit rounded-full", cls)}
|
class={twJoin("pointer-events-auto relative h-fit w-fit rounded-full", cls, $enabled ? "" : "disabled")}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,31 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte"
|
import { twMerge } from "tailwind-merge"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import Img from "./Img"
|
|
||||||
import { twJoin, twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export let imageUrl: string | BaseUIElement = undefined
|
|
||||||
export const message: string | BaseUIElement = undefined
|
|
||||||
export let options: {
|
export let options: {
|
||||||
imgSize?: string
|
imgSize?: string
|
||||||
extraClasses?: string
|
extraClasses?: string
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
let imgClasses = twJoin("block justify-center shrink-0 mr-4", options?.imgSize ?? "h-11 w-11")
|
|
||||||
const dispatch = createEventDispatcher<{ click }>()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={twMerge(options.extraClasses, "secondary no-image-background")}
|
class={twMerge(options.extraClasses, "secondary no-image-background")}
|
||||||
on:click={(e) => dispatch("click", e)}
|
on:click
|
||||||
>
|
>
|
||||||
<slot name="image">
|
<slot name="image" />
|
||||||
{#if imageUrl !== undefined}
|
|
||||||
{#if typeof imageUrl === "string"}
|
|
||||||
<Img src={imageUrl} class={imgClasses} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<slot name="message" />
|
<slot name="message" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -53,9 +53,6 @@
|
||||||
lat: number
|
lat: number
|
||||||
}>(undefined)
|
}>(undefined)
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
|
|
||||||
|
|
||||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
|
|
||||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||||
let initialMapProperties: Partial<MapProperties> & { location } = {
|
let initialMapProperties: Partial<MapProperties> & { location } = {
|
||||||
zoom: new UIEventSource<number>(19),
|
zoom: new UIEventSource<number>(19),
|
||||||
|
@ -73,6 +70,7 @@
|
||||||
minzoom: new UIEventSource<number>(18),
|
minzoom: new UIEventSource<number>(18),
|
||||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
||||||
}
|
}
|
||||||
|
state?.showCurrentLocationOn(map)
|
||||||
|
|
||||||
if (targetLayer) {
|
if (targetLayer) {
|
||||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
||||||
|
@ -120,7 +118,7 @@
|
||||||
|
|
||||||
<LocationInput
|
<LocationInput
|
||||||
{map}
|
{map}
|
||||||
on:click={(data) => dispatch("click", data)}
|
on:click
|
||||||
mapProperties={initialMapProperties}
|
mapProperties={initialMapProperties}
|
||||||
value={preciseLocation}
|
value={preciseLocation}
|
||||||
initialCoordinate={coordinate}
|
initialCoordinate={coordinate}
|
||||||
|
|
|
@ -23,18 +23,20 @@
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
import { BBox } from "../../Logic/BBox"
|
import { BBox } from "../../Logic/BBox"
|
||||||
import type { Feature, LineString, Point } from "geojson"
|
import type { Feature, LineString, Point } from "geojson"
|
||||||
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import SmallZoomButtons from "../Map/SmallZoomButtons.svelte"
|
||||||
|
|
||||||
const splitpoint_style = new LayerConfig(
|
const splitpoint_style = new LayerConfig(
|
||||||
<LayerConfigJson>split_point,
|
<LayerConfigJson>split_point,
|
||||||
"(BUILTIN) SplitRoadWizard.ts",
|
"(BUILTIN) SplitRoadWizard.ts",
|
||||||
true
|
true,
|
||||||
) as const
|
)
|
||||||
|
|
||||||
const splitroad_style = new LayerConfig(
|
const splitroad_style = new LayerConfig(
|
||||||
<LayerConfigJson>split_road,
|
<LayerConfigJson>split_road,
|
||||||
"(BUILTIN) SplitRoadWizard.ts",
|
"(BUILTIN) SplitRoadWizard.ts",
|
||||||
true
|
true,
|
||||||
) as const
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The way to focus on
|
* The way to focus on
|
||||||
|
@ -45,6 +47,7 @@
|
||||||
* A default is given
|
* A default is given
|
||||||
*/
|
*/
|
||||||
export let layer: LayerConfig = splitroad_style
|
export let layer: LayerConfig = splitroad_style
|
||||||
|
export let state: SpecialVisualizationState | undefined = undefined
|
||||||
/**
|
/**
|
||||||
* Optional: use these properties to set e.g. background layer
|
* Optional: use these properties to set e.g. background layer
|
||||||
*/
|
*/
|
||||||
|
@ -58,6 +61,7 @@
|
||||||
adaptor.bounds.setData(BBox.get(wayGeojson).pad(2))
|
adaptor.bounds.setData(BBox.get(wayGeojson).pad(2))
|
||||||
adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2))
|
adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2))
|
||||||
|
|
||||||
|
state?.showCurrentLocationOn(map)
|
||||||
new ShowDataLayer(map, {
|
new ShowDataLayer(map, {
|
||||||
features: new StaticFeatureSource([wayGeojson]),
|
features: new StaticFeatureSource([wayGeojson]),
|
||||||
drawMarkers: false,
|
drawMarkers: false,
|
||||||
|
@ -101,6 +105,7 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full w-full">
|
<div class="h-full w-full relative">
|
||||||
<MaplibreMap {map} />
|
<MaplibreMap {map} />
|
||||||
|
<SmallZoomButtons {adaptor} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import { createEventDispatcher, onDestroy } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import Move_arrows from "../../../assets/svg/Move_arrows.svelte"
|
import Move_arrows from "../../../assets/svg/Move_arrows.svelte"
|
||||||
|
import SmallZoomButtons from "../../Map/SmallZoomButtons.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A visualisation to pick a location on a map background
|
* A visualisation to pick a location on a map background
|
||||||
|
@ -95,4 +96,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DragInvitation hideSignal={mla.location} />
|
<DragInvitation hideSignal={mla.location} />
|
||||||
|
<SmallZoomButtons adaptor={mla} />
|
||||||
</div>
|
</div>
|
||||||
|
|
29
src/UI/Map/SmallZoomButtons.svelte
Normal file
29
src/UI/Map/SmallZoomButtons.svelte
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Translations from "../i18n/Translations.js";
|
||||||
|
import Min from "../../assets/svg/Min.svelte";
|
||||||
|
import MapControlButton from "../Base/MapControlButton.svelte";
|
||||||
|
import Plus from "../../assets/svg/Plus.svelte";
|
||||||
|
import type { MapProperties } from "../../Models/MapProperties"
|
||||||
|
|
||||||
|
export let adaptor: MapProperties
|
||||||
|
let canZoomIn = adaptor.maxzoom.map(mz => adaptor.zoom.data < mz, [adaptor.zoom] )
|
||||||
|
let canZoomOut = adaptor.minzoom.map(mz => adaptor.zoom.data > mz, [adaptor.zoom] )
|
||||||
|
</script>
|
||||||
|
<div class="absolute bottom-0 right-0 pointer-events-none flex flex-col">
|
||||||
|
<MapControlButton
|
||||||
|
enabled={canZoomIn}
|
||||||
|
cls="m-0.5 p-1"
|
||||||
|
arialabel={Translations.t.general.labels.zoomIn}
|
||||||
|
on:click={() => adaptor.zoom.update((z) => z + 1)}
|
||||||
|
>
|
||||||
|
<Plus class="h-5 w-5" />
|
||||||
|
</MapControlButton>
|
||||||
|
<MapControlButton
|
||||||
|
enabled={canZoomOut}
|
||||||
|
cls={"m-0.5 p-1"}
|
||||||
|
arialabel={Translations.t.general.labels.zoomOut}
|
||||||
|
on:click={() => adaptor.zoom.update((z) => z - 1)}
|
||||||
|
>
|
||||||
|
<Min class="h-5 w-5" />
|
||||||
|
</MapControlButton>
|
||||||
|
</div>
|
112
src/UI/Popup/SplitRoadWizard.svelte
Normal file
112
src/UI/Popup/SplitRoadWizard.svelte
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import type { Feature, Point } from "geojson"
|
||||||
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||||
|
import WaySplitMap from "../BigComponents/WaySplitMap.svelte"
|
||||||
|
import BackButton from "../Base/BackButton.svelte"
|
||||||
|
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
|
||||||
|
import Translations from "../i18n/Translations"
|
||||||
|
import NextButton from "../Base/NextButton.svelte"
|
||||||
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
import { OsmWay } from "../../Logic/Osm/OsmObject"
|
||||||
|
import type { WayId } from "../../Models/OsmFeature"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
|
||||||
|
export let state: SpecialVisualizationState
|
||||||
|
export let id: WayId
|
||||||
|
const t = Translations.t.split
|
||||||
|
let step: "initial" | "loading_way" | "splitting" | "applying_split" | "has_been_split" | "deleted" = "initial"
|
||||||
|
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
|
||||||
|
let splitPoints = new UIEventSource<Feature<
|
||||||
|
Point,
|
||||||
|
{
|
||||||
|
id: number
|
||||||
|
index: number
|
||||||
|
dist: number
|
||||||
|
location: number
|
||||||
|
}
|
||||||
|
>[]>([])
|
||||||
|
let splitpointsNotEmpty = splitPoints.map(sp => sp.length > 0)
|
||||||
|
|
||||||
|
let osmWay: OsmWay
|
||||||
|
|
||||||
|
async function downloadWay() {
|
||||||
|
step = "loading_way"
|
||||||
|
const dloaded = await state.osmObjectDownloader.DownloadObjectAsync(id)
|
||||||
|
if (dloaded === "deleted") {
|
||||||
|
step = "deleted"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
osmWay = dloaded
|
||||||
|
|
||||||
|
step = "splitting"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doSplit() {
|
||||||
|
step = "applying_split"
|
||||||
|
const splitAction = new SplitAction(
|
||||||
|
id,
|
||||||
|
splitPoints.data.map((ff) => <[number, number]>(<Point>ff.geometry).coordinates),
|
||||||
|
{
|
||||||
|
theme: state?.layout?.id,
|
||||||
|
},
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
await state.changes?.applyAction(splitAction)
|
||||||
|
// We throw away the old map and splitpoints, and create a new map from scratch
|
||||||
|
splitPoints.setData([])
|
||||||
|
|
||||||
|
// Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
|
||||||
|
state.selectedElement?.setData(undefined)
|
||||||
|
step = "has_been_split"
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<LoginToggle ignoreLoading={true} {state}>
|
||||||
|
<Tr slot="not-logged-in" t={t.loginToSplit} />
|
||||||
|
|
||||||
|
{#if step === "deleted"}
|
||||||
|
<!-- Empty -->
|
||||||
|
{:else if step === "initial"}
|
||||||
|
<button on:click={() => downloadWay()}>
|
||||||
|
<Scissors class="w-6 h-6 shrink-0" />
|
||||||
|
<Tr t={t.inviteToSplit} />
|
||||||
|
</button>
|
||||||
|
{:else if step === "loading_way"}
|
||||||
|
<Loading />
|
||||||
|
|
||||||
|
{:else if step === "splitting"}
|
||||||
|
<div class="flex flex-col interactive border-interactive p-2">
|
||||||
|
<div class="w-full h-80">
|
||||||
|
<WaySplitMap {state} {splitPoints} {osmWay} />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap-reverse md:flex-nowrap w-full">
|
||||||
|
<BackButton clss="w-full" on:click={() => {
|
||||||
|
splitPoints.set([])
|
||||||
|
step = "initial"
|
||||||
|
}}>
|
||||||
|
<Tr t={Translations.t.general.cancel} />
|
||||||
|
</BackButton>
|
||||||
|
<NextButton clss={ ($splitpointsNotEmpty ? "": "disabled ") + "w-full primary"} on:click={() => doSplit()}>
|
||||||
|
<Tr t={t.split} />
|
||||||
|
</NextButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{:else if step === "has_been_split"}
|
||||||
|
<Tr cls="thanks" t={ t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")} />
|
||||||
|
<button on:click={() => downloadWay()}>
|
||||||
|
<Scissors class="w-6 h-6" />
|
||||||
|
<Tr t={t.splitAgain} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</LoginToggle>
|
||||||
|
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
import Toggle from "../Input/Toggle"
|
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import { SubtleButton } from "../Base/SubtleButton"
|
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import { Button } from "../Base/Button"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
|
|
||||||
import Title from "../Base/Title"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
|
||||||
import { LoginToggle } from "./LoginButton"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
||||||
import WaySplitMap from "../BigComponents/WaySplitMap.svelte"
|
|
||||||
import { Feature, Point } from "geojson"
|
|
||||||
import { WayId } from "../../Models/OsmFeature"
|
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
|
||||||
import { Changes } from "../../Logic/Osm/Changes"
|
|
||||||
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
|
||||||
import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader"
|
|
||||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
|
||||||
|
|
||||||
export default class SplitRoadWizard extends Combine {
|
|
||||||
public dialogIsOpened: UIEventSource<boolean>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A UI Element used for splitting roads
|
|
||||||
*
|
|
||||||
* @param id The id of the road to remove
|
|
||||||
* @param state the state of the application
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
id: WayId,
|
|
||||||
state: {
|
|
||||||
layout?: LayoutConfig
|
|
||||||
osmConnection?: OsmConnection
|
|
||||||
osmObjectDownloader?: OsmObjectDownloader
|
|
||||||
changes?: Changes
|
|
||||||
indexedFeatures?: IndexedFeatureSource
|
|
||||||
selectedElement?: UIEventSource<Feature>
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const t = Translations.t.split
|
|
||||||
|
|
||||||
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
|
|
||||||
const splitPoints = new UIEventSource<Feature<Point>[]>([])
|
|
||||||
|
|
||||||
const hasBeenSplit = new UIEventSource(false)
|
|
||||||
|
|
||||||
// Toggle variable between show split button and map
|
|
||||||
const splitClicked = new UIEventSource<boolean>(false)
|
|
||||||
|
|
||||||
const leafletMap = new UIEventSource<BaseUIElement>(undefined)
|
|
||||||
|
|
||||||
function initMap() {
|
|
||||||
;(async function (
|
|
||||||
id: WayId,
|
|
||||||
splitPoints: UIEventSource<Feature[]>
|
|
||||||
): Promise<BaseUIElement> {
|
|
||||||
return new SvelteUIElement(WaySplitMap, {
|
|
||||||
osmWay: await state.osmObjectDownloader.DownloadObjectAsync(id),
|
|
||||||
splitPoints,
|
|
||||||
})
|
|
||||||
})(id, splitPoints).then((mapComponent) =>
|
|
||||||
leafletMap.setData(mapComponent.SetClass("w-full h-80"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle between splitmap
|
|
||||||
const splitButton = new SubtleButton(
|
|
||||||
new SvelteUIElement(Scissors).SetClass("h-6 w-6"),
|
|
||||||
new Toggle(
|
|
||||||
t.splitAgain.Clone().SetClass("text-lg font-bold"),
|
|
||||||
t.inviteToSplit.Clone().SetClass("text-lg font-bold"),
|
|
||||||
hasBeenSplit
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const splitToggle = new LoginToggle(splitButton, t.loginToSplit.Clone(), state)
|
|
||||||
|
|
||||||
// Save button
|
|
||||||
const saveButton = new Button(t.split.Clone(), async () => {
|
|
||||||
hasBeenSplit.setData(true)
|
|
||||||
splitClicked.setData(false)
|
|
||||||
const splitAction = new SplitAction(
|
|
||||||
id,
|
|
||||||
splitPoints.data.map((ff) => <[number, number]>(<Point>ff.geometry).coordinates),
|
|
||||||
{
|
|
||||||
theme: state?.layout?.id,
|
|
||||||
},
|
|
||||||
5
|
|
||||||
)
|
|
||||||
await state.changes?.applyAction(splitAction)
|
|
||||||
// We throw away the old map and splitpoints, and create a new map from scratch
|
|
||||||
splitPoints.setData([])
|
|
||||||
|
|
||||||
// Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
|
|
||||||
state.selectedElement?.setData(undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
saveButton.SetClass("btn btn-primary mr-3")
|
|
||||||
const disabledSaveButton = new Button(t.split.Clone(), undefined)
|
|
||||||
disabledSaveButton.SetClass("btn btn-disabled mr-3")
|
|
||||||
// Only show the save button if there are split points defined
|
|
||||||
const saveToggle = new Toggle(
|
|
||||||
disabledSaveButton,
|
|
||||||
saveButton,
|
|
||||||
splitPoints.map((data) => data.length === 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
const cancelButton = Translations.t.general.cancel
|
|
||||||
.Clone() // Not using Button() element to prevent full width button
|
|
||||||
.SetClass("btn btn-secondary mr-3")
|
|
||||||
.onClick(() => {
|
|
||||||
splitPoints.setData([])
|
|
||||||
splitClicked.setData(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
cancelButton.SetClass("btn btn-secondary block")
|
|
||||||
|
|
||||||
const splitTitle = new Title(t.splitTitle)
|
|
||||||
|
|
||||||
const mapView = new Combine([
|
|
||||||
splitTitle,
|
|
||||||
new VariableUiElement(leafletMap),
|
|
||||||
new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"),
|
|
||||||
])
|
|
||||||
mapView.SetClass("question")
|
|
||||||
super([
|
|
||||||
Toggle.If(hasBeenSplit, () =>
|
|
||||||
t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")
|
|
||||||
),
|
|
||||||
new Toggle(mapView, splitToggle, splitClicked),
|
|
||||||
])
|
|
||||||
splitClicked.addCallback((view) => {
|
|
||||||
if (view) {
|
|
||||||
initMap()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.dialogIsOpened = splitClicked
|
|
||||||
const self = this
|
|
||||||
splitButton.onClick(() => {
|
|
||||||
splitClicked.setData(true)
|
|
||||||
self.ScrollIntoView()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,6 +22,8 @@ import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||||
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
||||||
import { SummaryTileSourceRewriter } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
|
import { SummaryTileSourceRewriter } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
|
||||||
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
|
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
|
||||||
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
|
import ShowDataLayer from "./Map/ShowDataLayer"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state needed to render a special Visualisation.
|
* The state needed to render a special Visualisation.
|
||||||
|
@ -86,6 +88,8 @@ export interface SpecialVisualizationState {
|
||||||
|
|
||||||
readonly previewedImage: UIEventSource<ProvidedImage>
|
readonly previewedImage: UIEventSource<ProvidedImage>
|
||||||
readonly geolocation: GeoLocationHandler
|
readonly geolocation: GeoLocationHandler
|
||||||
|
|
||||||
|
showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
|
|
|
@ -46,8 +46,6 @@ import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
|
||||||
import UserProfile from "./BigComponents/UserProfile.svelte"
|
import UserProfile from "./BigComponents/UserProfile.svelte"
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import { WayId } from "../Models/OsmFeature"
|
|
||||||
import SplitRoadWizard from "./Popup/SplitRoadWizard"
|
|
||||||
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
||||||
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
|
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
|
||||||
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
|
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
|
||||||
|
@ -93,6 +91,7 @@ import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
||||||
import LoginButton from "./Base/LoginButton.svelte"
|
import LoginButton from "./Base/LoginButton.svelte"
|
||||||
import Toggle from "./Input/Toggle"
|
import Toggle from "./Input/Toggle"
|
||||||
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
|
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
|
||||||
|
import SplitRoadWizard from "./Popup/SplitRoadWizard.svelte"
|
||||||
|
|
||||||
class NearbyImageVis implements SpecialVisualization {
|
class NearbyImageVis implements SpecialVisualization {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||||
|
@ -432,7 +431,7 @@ export default class SpecialVisualizations {
|
||||||
.map((tags) => tags.id)
|
.map((tags) => tags.id)
|
||||||
.map((id) => {
|
.map((id) => {
|
||||||
if (id.startsWith("way/")) {
|
if (id.startsWith("way/")) {
|
||||||
return new SplitRoadWizard(<WayId>id, state)
|
return new SvelteUIElement(SplitRoadWizard, { id, state })
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
@ -741,12 +740,20 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
funcName: "import_mangrove_key",
|
funcName: "import_mangrove_key",
|
||||||
docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews",
|
docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews",
|
||||||
args: [{
|
args: [
|
||||||
name: "text",
|
{
|
||||||
doc: "The text that is shown on the button",
|
name: "text",
|
||||||
}],
|
doc: "The text that is shown on the button",
|
||||||
|
},
|
||||||
|
],
|
||||||
needsUrls: [],
|
needsUrls: [],
|
||||||
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, argument: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
|
argument: string[],
|
||||||
|
feature: Feature,
|
||||||
|
layer: LayerConfig
|
||||||
|
): BaseUIElement {
|
||||||
const [text] = argument
|
const [text] = argument
|
||||||
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
||||||
},
|
},
|
||||||
|
|
|
@ -118,7 +118,8 @@
|
||||||
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
|
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
|
||||||
let mapproperties: MapProperties = state.mapProperties
|
let mapproperties: MapProperties = state.mapProperties
|
||||||
state.mapProperties.installCustomKeyboardHandler(viewport)
|
state.mapProperties.installCustomKeyboardHandler(viewport)
|
||||||
|
let canZoomIn = mapproperties.maxzoom.map(mz => mapproperties.zoom.data < mz, [mapproperties.zoom] )
|
||||||
|
let canZoomOut = mapproperties.minzoom.map(mz => mapproperties.zoom.data > mz, [mapproperties.zoom] )
|
||||||
function updateViewport() {
|
function updateViewport() {
|
||||||
const rect = viewport.data?.getBoundingClientRect()
|
const rect = viewport.data?.getBoundingClientRect()
|
||||||
if (!rect) {
|
if (!rect) {
|
||||||
|
@ -329,12 +330,14 @@
|
||||||
</If>
|
</If>
|
||||||
<MapControlButton
|
<MapControlButton
|
||||||
arialabel={Translations.t.general.labels.zoomIn}
|
arialabel={Translations.t.general.labels.zoomIn}
|
||||||
|
enabled={canZoomIn}
|
||||||
on:click={() => mapproperties.zoom.update((z) => z + 1)}
|
on:click={() => mapproperties.zoom.update((z) => z + 1)}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
>
|
>
|
||||||
<Plus class="h-8 w-8" />
|
<Plus class="h-8 w-8" />
|
||||||
</MapControlButton>
|
</MapControlButton>
|
||||||
<MapControlButton
|
<MapControlButton
|
||||||
|
enabled={canZoomOut}
|
||||||
arialabel={Translations.t.general.labels.zoomOut}
|
arialabel={Translations.t.general.labels.zoomOut}
|
||||||
on:click={() => mapproperties.zoom.update((z) => z - 1)}
|
on:click={() => mapproperties.zoom.update((z) => z - 1)}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
|
|
|
@ -1390,7 +1390,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
d.setUTCMinutes(0)
|
d.setUTCMinutes(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static scrollIntoView(element: HTMLBaseElement | HTMLDivElement) {
|
public static scrollIntoView(element: HTMLBaseElement | HTMLDivElement): void {
|
||||||
|
if (!element) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// Is the element completely in the view?
|
// Is the element completely in the view?
|
||||||
const parentRect = Utils.findParentWithScrolling(element)?.getBoundingClientRect()
|
const parentRect = Utils.findParentWithScrolling(element)?.getBoundingClientRect()
|
||||||
if (!parentRect) {
|
if (!parentRect) {
|
||||||
|
|
Loading…
Reference in a new issue