diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 50fe06d71..d798ebb8c 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -439,6 +439,19 @@ export default class ThemeViewState implements SpecialVisualizationState { this.selectedElement.setData(feature) } + public showCurrentLocationOn(map: Store): 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 */ @@ -671,6 +684,7 @@ export default class ThemeViewState implements SpecialVisualizationState { ) return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers) } + /** * Add the special layers to the map */ diff --git a/src/UI/Base/MapControlButton.svelte b/src/UI/Base/MapControlButton.svelte index 38122911f..55bc60626 100644 --- a/src/UI/Base/MapControlButton.svelte +++ b/src/UI/Base/MapControlButton.svelte @@ -3,12 +3,14 @@ import { twJoin } from "tailwind-merge" import { Translation } from "../i18n/Translation" 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 */ const dispatch = createEventDispatcher() export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1" + export let enabled : Store = new ImmutableStore(true) export let arialabel: Translation = undefined @@ -16,7 +18,7 @@ on:click={(e) => dispatch("click", e)} on:keydown 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")} > diff --git a/src/UI/Base/SubtleButton.svelte b/src/UI/Base/SubtleButton.svelte index 06431a4da..76a2fe63d 100644 --- a/src/UI/Base/SubtleButton.svelte +++ b/src/UI/Base/SubtleButton.svelte @@ -1,31 +1,17 @@ diff --git a/src/UI/BigComponents/NewPointLocationInput.svelte b/src/UI/BigComponents/NewPointLocationInput.svelte index 95d1b72cb..0996f095e 100644 --- a/src/UI/BigComponents/NewPointLocationInput.svelte +++ b/src/UI/BigComponents/NewPointLocationInput.svelte @@ -53,9 +53,6 @@ lat: number }>(undefined) - const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>() - - const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16) const map: UIEventSource = new UIEventSource(undefined) let initialMapProperties: Partial & { location } = { zoom: new UIEventSource(19), @@ -73,6 +70,7 @@ minzoom: new UIEventSource(18), rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer), } + state?.showCurrentLocationOn(map) if (targetLayer) { const featuresForLayer = state.perLayer.get(targetLayer.id) @@ -120,7 +118,7 @@ dispatch("click", data)} + on:click mapProperties={initialMapProperties} value={preciseLocation} initialCoordinate={coordinate} diff --git a/src/UI/BigComponents/WaySplitMap.svelte b/src/UI/BigComponents/WaySplitMap.svelte index d45fd2c2a..b6b4afd53 100644 --- a/src/UI/BigComponents/WaySplitMap.svelte +++ b/src/UI/BigComponents/WaySplitMap.svelte @@ -23,18 +23,20 @@ import { GeoOperations } from "../../Logic/GeoOperations" import { BBox } from "../../Logic/BBox" import type { Feature, LineString, Point } from "geojson" + import type { SpecialVisualizationState } from "../SpecialVisualization" + import SmallZoomButtons from "../Map/SmallZoomButtons.svelte" const splitpoint_style = new LayerConfig( split_point, "(BUILTIN) SplitRoadWizard.ts", - true - ) as const + true, + ) const splitroad_style = new LayerConfig( split_road, "(BUILTIN) SplitRoadWizard.ts", - true - ) as const + true, + ) /** * The way to focus on @@ -45,6 +47,7 @@ * A default is given */ export let layer: LayerConfig = splitroad_style + export let state: SpecialVisualizationState | undefined = undefined /** * Optional: use these properties to set e.g. background layer */ @@ -58,6 +61,7 @@ adaptor.bounds.setData(BBox.get(wayGeojson).pad(2)) adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2)) + state?.showCurrentLocationOn(map) new ShowDataLayer(map, { features: new StaticFeatureSource([wayGeojson]), drawMarkers: false, @@ -101,6 +105,7 @@ }) -
+
+
diff --git a/src/UI/InputElement/Helpers/LocationInput.svelte b/src/UI/InputElement/Helpers/LocationInput.svelte index 3aad6bf8f..3f4570a9c 100644 --- a/src/UI/InputElement/Helpers/LocationInput.svelte +++ b/src/UI/InputElement/Helpers/LocationInput.svelte @@ -13,6 +13,7 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { createEventDispatcher, onDestroy } from "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 @@ -95,4 +96,5 @@
+ diff --git a/src/UI/Map/SmallZoomButtons.svelte b/src/UI/Map/SmallZoomButtons.svelte new file mode 100644 index 000000000..4fc58fa88 --- /dev/null +++ b/src/UI/Map/SmallZoomButtons.svelte @@ -0,0 +1,29 @@ + +
+ adaptor.zoom.update((z) => z + 1)} + > + + + adaptor.zoom.update((z) => z - 1)} + > + + +
diff --git a/src/UI/Popup/SplitRoadWizard.svelte b/src/UI/Popup/SplitRoadWizard.svelte new file mode 100644 index 000000000..c1b464d99 --- /dev/null +++ b/src/UI/Popup/SplitRoadWizard.svelte @@ -0,0 +1,112 @@ + + + + + + + {#if step === "deleted"} + + {:else if step === "initial"} + + {:else if step === "loading_way"} + + + {:else if step === "splitting"} +
+
+ +
+
+ { + splitPoints.set([]) + step = "initial" + }}> + + + doSplit()}> + + +
+ +
+ {:else if step === "has_been_split"} + + + {/if} + +
+ + diff --git a/src/UI/Popup/SplitRoadWizard.ts b/src/UI/Popup/SplitRoadWizard.ts deleted file mode 100644 index ac4b76f7d..000000000 --- a/src/UI/Popup/SplitRoadWizard.ts +++ /dev/null @@ -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 - - /** - * 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 - } - ) { - 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[]>([]) - - const hasBeenSplit = new UIEventSource(false) - - // Toggle variable between show split button and map - const splitClicked = new UIEventSource(false) - - const leafletMap = new UIEventSource(undefined) - - function initMap() { - ;(async function ( - id: WayId, - splitPoints: UIEventSource - ): Promise { - 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]>(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() - }) - } -} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index d377fc833..ff0547eb5 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -22,6 +22,8 @@ import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import { SummaryTileSourceRewriter } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" 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. @@ -86,6 +88,8 @@ export interface SpecialVisualizationState { readonly previewedImage: UIEventSource readonly geolocation: GeoLocationHandler + + showCurrentLocationOn(map: Store): ShowDataLayer } export interface SpecialVisualization { diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index e353fcdd9..38a08f116 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -46,8 +46,6 @@ import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import UserProfile from "./BigComponents/UserProfile.svelte" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import { WayId } from "../Models/OsmFeature" -import SplitRoadWizard from "./Popup/SplitRoadWizard" import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" @@ -93,6 +91,7 @@ import SpecialVisualisationUtils from "./SpecialVisualisationUtils" import LoginButton from "./Base/LoginButton.svelte" import Toggle from "./Input/Toggle" import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte" +import SplitRoadWizard from "./Popup/SplitRoadWizard.svelte" class NearbyImageVis implements SpecialVisualization { // 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((id) => { if (id.startsWith("way/")) { - return new SplitRoadWizard(id, state) + return new SvelteUIElement(SplitRoadWizard, { id, state }) } return undefined }) @@ -741,12 +740,20 @@ export default class SpecialVisualizations { { 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", - args: [{ - name: "text", - doc: "The text that is shown on the button", - }], + args: [ + { + name: "text", + doc: "The text that is shown on the button", + }, + ], needsUrls: [], - constr(state: SpecialVisualizationState, tagSource: UIEventSource>, argument: string[], feature: Feature, layer: LayerConfig): BaseUIElement { + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { const [text] = argument return new SvelteUIElement(ImportReviewIdentity, { state, text }) }, diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 4cd7ea378..0a2b6806e 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -118,7 +118,8 @@ let viewport: UIEventSource = new UIEventSource(undefined) let mapproperties: MapProperties = state.mapProperties 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() { const rect = viewport.data?.getBoundingClientRect() if (!rect) { @@ -329,12 +330,14 @@ mapproperties.zoom.update((z) => z + 1)} on:keydown={forwardEventToMap} > mapproperties.zoom.update((z) => z - 1)} on:keydown={forwardEventToMap} diff --git a/src/Utils.ts b/src/Utils.ts index 4f099ed2e..bba793510 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1390,7 +1390,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be 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? const parentRect = Utils.findParentWithScrolling(element)?.getBoundingClientRect() if (!parentRect) {