UX: fix #1805, disable zoom-in and zoom-out buttons when maxrange reached

This commit is contained in:
Pieter Vander Vennet 2024-03-04 15:31:09 +01:00
parent 346f45cff8
commit 48159b25f7
13 changed files with 202 additions and 184 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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>

View 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>

View file

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

View file

@ -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 {

View file

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

View file

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

View file

@ -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) {