From 2bb227fd0458fd6df30b94a9a4d03c15482c83f9 Mon Sep 17 00:00:00 2001 From: Arek Date: Tue, 25 Jun 2024 14:21:27 +0200 Subject: [PATCH] fix: SCHEDULER-111: improved scrolling --- src/components/Calendar/Grid/Grid.tsx | 15 +++-- src/components/Calendar/Header/Header.tsx | 11 +++- src/constants.ts | 1 - .../CalendarProvider/CalendarProvider.tsx | 59 ++++++++----------- src/utils/getCanvasWidth.ts | 7 +++ src/utils/getCols.ts | 22 +++++-- src/utils/getDatesRange.ts | 13 ++-- 7 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/utils/getCanvasWidth.ts diff --git a/src/components/Calendar/Grid/Grid.tsx b/src/components/Calendar/Grid/Grid.tsx index 1aaf68f7..ce035e0f 100644 --- a/src/components/Calendar/Grid/Grid.tsx +++ b/src/components/Calendar/Grid/Grid.tsx @@ -1,9 +1,10 @@ import { forwardRef, useCallback, useEffect, useRef } from "react"; import { drawGrid } from "@/utils/drawGrid/drawGrid"; -import { boxHeight, canvasWrapperId, leftColumnWidth, screenWidthMultiplier } from "@/constants"; +import { boxHeight, canvasWrapperId, leftColumnWidth, outsideWrapperId } from "@/constants"; import { Loader, Tiles } from "@/components"; import { useCalendar } from "@/context/CalendarProvider"; import { resizeCanvas } from "@/utils/resizeCanvas"; +import { getCanvasWidth } from "@/utils/getCanvasWidth"; import { GridProps } from "./types"; import { StyledCanvas, StyledInnerWrapper, StyledSpan, StyledWrapper } from "./styles"; @@ -18,7 +19,7 @@ const Grid = forwardRef(function Grid( const handleResize = useCallback( (ctx: CanvasRenderingContext2D) => { - const width = window.innerWidth * screenWidthMultiplier; + const width = getCanvasWidth(); const height = rows * boxHeight + 1; resizeCanvas(ctx, width, height); drawGrid(ctx, zoom, rows, cols, startDate); @@ -50,8 +51,9 @@ const Grid = forwardRef(function Grid( useEffect(() => { if (!refRight.current) return; - const observerRight = new IntersectionObserver((e) => - e[0].isIntersecting ? handleScrollNext() : null + const observerRight = new IntersectionObserver( + (e) => (e[0].isIntersecting ? handleScrollNext() : null), + { root: document.getElementById(outsideWrapperId) } ); observerRight.observe(refRight.current); @@ -62,7 +64,10 @@ const Grid = forwardRef(function Grid( if (!refLeft.current) return; const observerLeft = new IntersectionObserver( (e) => (e[0].isIntersecting ? handleScrollPrev() : null), - { rootMargin: `0px 0px 0px -${leftColumnWidth}px` } + { + root: document.getElementById(outsideWrapperId), + rootMargin: `0px 0px 0px -${leftColumnWidth}px` + } ); observerLeft.observe(refLeft.current); diff --git a/src/components/Calendar/Header/Header.tsx b/src/components/Calendar/Header/Header.tsx index 53520a85..80614d0a 100644 --- a/src/components/Calendar/Header/Header.tsx +++ b/src/components/Calendar/Header/Header.tsx @@ -1,9 +1,16 @@ import { FC, useCallback, useEffect, useRef } from "react"; -import { headerHeight, screenWidthMultiplier, canvasHeaderWrapperId } from "@/constants"; +import { + headerHeight, + screenWidthMultiplier, + canvasHeaderWrapperId, + outsideWrapperId, + leftColumnWidth +} from "@/constants"; import { useCalendar } from "@/context/CalendarProvider"; import { useLanguage } from "@/context/LocaleProvider"; import { drawHeader } from "@/utils/drawHeader/drawHeader"; import { resizeCanvas } from "@/utils/resizeCanvas"; +import { getCanvasWidth } from "@/utils/getCanvasWidth"; import { HeaderProps } from "./types"; import { StyledCanvas, StyledOuterWrapper, StyledWrapper } from "./styles"; import Topbar from "./Topbar"; @@ -15,7 +22,7 @@ const Header: FC = ({ zoom, topBarWidth }) => { const handleResize = useCallback( (ctx: CanvasRenderingContext2D) => { - const width = window.innerWidth * screenWidthMultiplier; + const width = getCanvasWidth(); const height = headerHeight + 1; resizeCanvas(ctx, width, height); diff --git a/src/constants.ts b/src/constants.ts index d666a827..76570523 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -43,6 +43,5 @@ export const maxHoursPerDay = 8; export const topRowTextYPos = headerMonthHeight / 2 + 2; export const middleRowTextYPos = headerWeekHeight / 2 + headerMonthHeight + 1; export const buttonWeeksJump = 2; -export const scrollWeeksJump = 4; export const minutesInHour = 60; export const tileDefaultBgColor = "rgb(114,141,226)"; diff --git a/src/context/CalendarProvider/CalendarProvider.tsx b/src/context/CalendarProvider/CalendarProvider.tsx index ff1cb7e9..8ec29788 100644 --- a/src/context/CalendarProvider/CalendarProvider.tsx +++ b/src/context/CalendarProvider/CalendarProvider.tsx @@ -10,15 +10,9 @@ import { Coords, ZoomLevel, allZoomLevel } from "@/types/global"; import { isAvailableZoom } from "@/types/guards"; import { getDatesRange, getParsedDatesRange } from "@/utils/getDatesRange"; import { parseDay } from "@/utils/dates"; -import { getCols } from "@/utils/getCols"; -import { - buttonWeeksJump, - dayWidth, - outsideWrapperId, - screenWidthMultiplier, - scrollWeeksJump, - weekWidth -} from "@/constants"; +import { getCols, getVisibleCols } from "@/utils/getCols"; +import { buttonWeeksJump, outsideWrapperId } from "@/constants"; +import { getCanvasWidth } from "@/utils/getCanvasWidth"; import { calendarContext } from "./calendarContext"; import { CalendarProviderProps } from "./types"; dayjs.extend(weekOfYear); @@ -52,48 +46,37 @@ const CalendarProvider = ({ const parsedStartDate = parseDay(startDate); const outsideWrapper = useRef(null); const [tilesCoords, setTilesCoords] = useState([{ x: 0, y: 0 }]); - const scrollForwardOffsetModifier = 2; const moveHorizontalScroll = useCallback( - (direction: Direction, behavior: ScrollBehavior = "smooth") => { + (direction: Direction, behavior: ScrollBehavior = "auto") => { + const canvasWidth = getCanvasWidth(); switch (direction) { case "back": return outsideWrapper.current?.scrollTo({ behavior, - left: zoom === 0 ? weekWidth * screenWidthMultiplier : dayWidth * screenWidthMultiplier + left: canvasWidth / 3 }); case "forward": return outsideWrapper.current?.scrollTo({ behavior, - left: - zoom === 0 - ? window.innerWidth + - (cols / screenWidthMultiplier - - screenWidthMultiplier + - scrollForwardOffsetModifier) * - weekWidth - : window.innerWidth + - (cols / screenWidthMultiplier - - screenWidthMultiplier + - scrollForwardOffsetModifier) * - dayWidth + left: canvasWidth / 3 }); case "middle": return outsideWrapper.current?.scrollTo({ behavior, - left: window.innerWidth + left: canvasWidth / 2 }); default: return outsideWrapper.current?.scrollTo({ behavior, - left: window.innerWidth + left: canvasWidth / 2 }); } }, - [cols, zoom] + [] ); const updateTilesCoords = (coords: Coords[]) => { @@ -102,13 +85,15 @@ const CalendarProvider = ({ const loadMore = useCallback( (direction: Direction) => { + const cols = getVisibleCols(zoom); + const offset = zoom === 0 ? cols * 7 : cols; const load = debounce(() => { switch (direction) { case "back": - setDate((prev) => prev.subtract(scrollWeeksJump, "weeks")); + setDate((prev) => prev.subtract(offset, "days")); break; case "forward": - setDate((prev) => prev.add(scrollWeeksJump, "weeks")); + setDate((prev) => prev.add(offset, "days")); break; case "middle": setDate(dayjs()); @@ -123,6 +108,7 @@ const CalendarProvider = ({ useEffect(() => { outsideWrapper.current = document.getElementById(outsideWrapperId); + setCols(getCols(zoom)); }, []); useEffect(() => { @@ -143,7 +129,7 @@ const CalendarProvider = ({ useEffect(() => { if (isInitialized) return; - moveHorizontalScroll("middle", "auto"); + moveHorizontalScroll("middle"); setIsInitialized(true); setDate(defaultStartDate); }, [defaultStartDate, isInitialized, moveHorizontalScroll]); @@ -159,7 +145,9 @@ const CalendarProvider = ({ if (isLoading) return; loadMore("forward"); - moveHorizontalScroll("forward"); + debounce(() => { + moveHorizontalScroll("forward"); + }, 300)(); }, [isLoading, loadMore, moveHorizontalScroll]); const handleGoPrev = () => { @@ -171,16 +159,19 @@ const CalendarProvider = ({ const handleScrollPrev = useCallback(() => { if (!isInitialized || isLoading) return; - loadMore("back"); - moveHorizontalScroll("back"); + debounce(() => { + moveHorizontalScroll("back"); + }, 300)(); }, [isInitialized, isLoading, loadMore, moveHorizontalScroll]); const handleGoToday = useCallback(() => { if (isLoading) return; loadMore("middle"); - moveHorizontalScroll("middle"); + debounce(() => { + moveHorizontalScroll("middle"); + }, 300)(); }, [isLoading, loadMore, moveHorizontalScroll]); const zoomIn = () => changeZoom(zoom + 1); diff --git a/src/utils/getCanvasWidth.ts b/src/utils/getCanvasWidth.ts new file mode 100644 index 00000000..8bd03b41 --- /dev/null +++ b/src/utils/getCanvasWidth.ts @@ -0,0 +1,7 @@ +import { outsideWrapperId, leftColumnWidth, screenWidthMultiplier } from "@/constants"; + +export const getCanvasWidth = () => { + const wrapperWidth = document.getElementById(outsideWrapperId)?.clientWidth || 0; + const width = (wrapperWidth - leftColumnWidth) * screenWidthMultiplier; + return width; +}; diff --git a/src/utils/getCols.ts b/src/utils/getCols.ts index 70f86579..fa002b25 100644 --- a/src/utils/getCols.ts +++ b/src/utils/getCols.ts @@ -1,6 +1,18 @@ -import { weekWidth, screenWidthMultiplier, dayWidth } from "@/constants"; +import { + weekWidth, + dayWidth, + outsideWrapperId, + leftColumnWidth, + screenWidthMultiplier +} from "@/constants"; -export const getCols = (zoom: number) => - zoom === 0 - ? Math.ceil(window.innerWidth / weekWidth) * screenWidthMultiplier - : Math.ceil(window.innerWidth / dayWidth) * screenWidthMultiplier; +export const getCols = (zoom: number) => { + const wrapperWidth = document.getElementById(outsideWrapperId)?.clientWidth || 0; + const componentWidth = wrapperWidth - leftColumnWidth; + const visibleCols = + zoom === 0 ? Math.ceil(componentWidth / weekWidth) : Math.ceil(componentWidth / dayWidth); + + return visibleCols * screenWidthMultiplier; +}; + +export const getVisibleCols = (zoom: number) => getCols(zoom) / screenWidthMultiplier; diff --git a/src/utils/getDatesRange.ts b/src/utils/getDatesRange.ts index 161ce1fe..1af8967c 100644 --- a/src/utils/getDatesRange.ts +++ b/src/utils/getDatesRange.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { weekWidth, dayWidth } from "@/constants"; +import { getCols } from "./getCols"; export type DatesRange = { startDate: dayjs.Dayjs; @@ -12,16 +12,11 @@ export type ParsedDatesRange = { }; export const getDatesRange = (date: dayjs.Dayjs, zoom: number): DatesRange => { - const cols = - zoom === 0 - ? Math.ceil(window.innerWidth / weekWidth) * 3 - : Math.ceil(window.innerWidth / dayWidth) * 3; + const colsOffset = getCols(zoom) / 2; const startDate = - zoom === 0 - ? date.subtract(cols / 3 + 3, "weeks").set("day", 1) - : date.subtract(cols / 3 + 3, "days"); + zoom === 0 ? date.subtract(colsOffset, "weeks") : date.subtract(colsOffset, "days"); - const endDate = zoom === 0 ? startDate.add(cols, "weeks") : startDate.add(cols, "days"); + const endDate = zoom === 0 ? date.add(colsOffset, "weeks") : date.add(colsOffset, "days"); return { startDate,