From 754fa8728e067df50eee33520845489ae88aab8b Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Thu, 29 Aug 2024 14:16:10 +0300 Subject: [PATCH] Fixed Schedules modals dragging/initial positioning (#4953) # What this PR does Closes https://github.com/grafana/oncall/issues/4944 --- .../RotationForm/RotationForm.helpers.ts | 18 ++++--- .../containers/RotationForm/RotationForm.tsx | 3 +- .../containers/RotationForm/ShiftSwapForm.tsx | 47 +++++++++++++++++-- .../containers/Rotations/Rotations.helpers.ts | 2 +- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.helpers.ts b/grafana-plugin/src/containers/RotationForm/RotationForm.helpers.ts index 98597fcb..37671a36 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.helpers.ts +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.helpers.ts @@ -191,26 +191,24 @@ export function getDraggableModalCoordinatesOnInit( return undefined; } - const scrollBarReferenceElements = document.querySelectorAll('.scrollbar-view'); - // top navbar display has 2 scrollbar-view elements (navbar & content) - const baseReferenceElRect = ( - scrollBarReferenceElements.length === 1 ? scrollBarReferenceElements[0] : scrollBarReferenceElements[1] - ).getBoundingClientRect(); + const body = document.body; + const baseReferenceElRect = body.getBoundingClientRect(); + const { innerHeight } = window; const { right, bottom } = baseReferenceElRect; return isTopNavbar() ? { // values are adjusted by any padding/margin differences - left: -data.node.offsetLeft + 4, + left: -data.node.offsetLeft + 12, right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12, - top: -offsetTop + GRAFANA_HEADER_HEIGHT + 4, - bottom: bottom - data.node.offsetHeight - offsetTop - 12, + top: -offsetTop + GRAFANA_HEADER_HEIGHT + 12, + bottom: innerHeight - data.node.offsetHeight - offsetTop - 12, } : { - left: -data.node.offsetLeft + 4 + GRAFANA_LEGACY_SIDEBAR_WIDTH, + left: -data.node.offsetLeft + 12 + GRAFANA_LEGACY_SIDEBAR_WIDTH, right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12, - top: -offsetTop + 4, + top: -offsetTop + 12, bottom: bottom - data.node.offsetHeight - offsetTop - 12, }; } diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index 086d4743..8e5d38c6 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -809,7 +809,8 @@ export const RotationForm = observer((props: RotationFormProps) => { return; } - setDraggableBounds(getDraggableModalCoordinatesOnInit(data, offsetTop)); + const bounds = getDraggableModalCoordinatesOnInit(data, offsetTop); + setDraggableBounds(bounds); } }); diff --git a/grafana-plugin/src/containers/RotationForm/ShiftSwapForm.tsx b/grafana-plugin/src/containers/RotationForm/ShiftSwapForm.tsx index b8ab15f3..12176718 100644 --- a/grafana-plugin/src/containers/RotationForm/ShiftSwapForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ShiftSwapForm.tsx @@ -3,20 +3,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button, Field, IconButton, Input, TextArea, Stack } from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; -import Draggable from 'react-draggable'; +import Draggable, { DraggableData, DraggableEvent } from 'react-draggable'; import { Modal } from 'components/Modal/Modal'; import { Tag } from 'components/Tag/Tag'; import { Text } from 'components/Text/Text'; import { WithConfirm } from 'components/WithConfirm/WithConfirm'; +import { calculateScheduleFormOffset } from 'containers/Rotations/Rotations.helpers'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { SHIFT_SWAP_COLOR } from 'models/schedule/schedule.helpers'; import { Schedule, ShiftSwap } from 'models/schedule/schedule.types'; import { getUTCString } from 'pages/schedule/Schedule.helpers'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization/authorization'; -import { StackSize } from 'utils/consts'; +import { GRAFANA_HEADER_HEIGHT, StackSize } from 'utils/consts'; +import { useDebouncedCallback, useResize } from 'utils/hooks'; +import { getDraggableModalCoordinatesOnInit } from './RotationForm.helpers'; import { DateTimePicker } from './parts/DateTimePicker'; import { UserItem } from './parts/UserItem'; @@ -36,6 +39,15 @@ export const ShiftSwapForm = (props: ShiftSwapFormProps) => { const { onUpdate, onHide, id, scheduleId, params: defaultParams } = props; const [shiftSwap, setShiftSwap] = useState({ ...defaultParams }); + const [offsetTop, setOffsetTop] = useState(GRAFANA_HEADER_HEIGHT + 10); + const [draggablePosition, setDraggablePosition] = useState<{ x: number; y: number }>(undefined); + const [bounds, setDraggableBounds] = useState<{ left: number; right: number; top: number; bottom: number }>( + undefined + ); + + const debouncedOnResize = useDebouncedCallback(onResize, 250); + + useResize(debouncedOnResize); const store = useStore(); const { @@ -44,6 +56,12 @@ export const ShiftSwapForm = (props: ShiftSwapFormProps) => { timezoneStore: { selectedTimezoneOffset }, } = store; + useEffect(() => { + (async () => { + setOffsetTop(await calculateScheduleFormOffset(`.${cx('draggable')}`)); + })(); + }, []); + useEffect(() => { (async () => { if (id !== 'new') { @@ -131,7 +149,15 @@ export const ShiftSwapForm = (props: ShiftSwapFormProps) => { width="430px" onDismiss={handleHide} contentElement={(props, children) => ( - + setDraggablePosition({ x: data.x, y: data.y })} + >
{children}
)} @@ -235,4 +261,19 @@ export const ShiftSwapForm = (props: ShiftSwapFormProps) => { ); + + async function onResize() { + setOffsetTop(await calculateScheduleFormOffset(`.${cx('draggable')}`)); + + setDraggablePosition({ x: 0, y: 0 }); + } + + function onDraggableInit(_e: DraggableEvent, data: DraggableData) { + if (!data) { + return; + } + + const bounds = getDraggableModalCoordinatesOnInit(data, offsetTop); + setDraggableBounds(bounds); + } }; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.helpers.ts b/grafana-plugin/src/containers/Rotations/Rotations.helpers.ts index 6bb9e398..0c6dfb18 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.helpers.ts +++ b/grafana-plugin/src/containers/Rotations/Rotations.helpers.ts @@ -10,7 +10,7 @@ export const calculateScheduleFormOffset = async (queryClassName: string) => { const modal = await waitForElement(queryClassName); const modalHeight = modal.clientHeight; - return document.documentElement.scrollHeight / 2 - modalHeight / 2; + return window.innerHeight / 2 - modalHeight / 2; }; // DatePickers will convert the date passed to local timezone, instead we want to use the date in the given timezone