Created
October 28, 2022 09:49
-
-
Save kristersd/7a6eb5422b2c1b1b37a9dd7638bcc403 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react"; | |
import {H_IN_MS} from "../../../../../../../common/constants/Commons"; | |
import TimeFormat from "../../../../../../../common/components/TimeFormat/TimeFormat"; | |
import {useSelector} from "react-redux"; | |
import customSelector from "./customSelector"; | |
import {Badge} from "reactstrap"; | |
import TimeService from "../../../../../../../common/services/time/timeService"; | |
const MARKER_HEIGHT = 16; | |
const CCWDragDropSelected = ({weekViewStoreRef, scrollableContainerRef, weekData, totalDayHeight, hourPositions}) => { | |
const [marker, setMarker] = useState(null); | |
const timeZone = useSelector((state) => TimeService.getViewTimeZone(state)); | |
const timeAccurateFormatValue = useSelector((state) => state.config.Global.options.timeAccurateFormat.value); | |
const isDragging = useSelector(customSelector); | |
const sidebarHeightRef = useRef(0); | |
const reversedHourPositions = useMemo(() => { | |
return hourPositions.slice().reverse(); | |
}, [hourPositions]); | |
/** | |
* Calculates marker time based on offset. | |
* Used for case, when there is no event as target, for example, the ul element. | |
* @type {function(offsetY: number): number} | |
*/ | |
const calculateMarkerTime = useCallback((offsetY) => { | |
let prev = reversedHourPositions[0]; | |
let dataHour; | |
for (let i = 1; i < reversedHourPositions.length; i++) { | |
const curr = reversedHourPositions[i]; | |
if (prev.top <= offsetY && offsetY < curr.top) { | |
dataHour = prev; | |
break; | |
} | |
prev = curr; | |
dataHour = curr; | |
} | |
const relativeHeight = 1 - (dataHour.top + dataHour.height - offsetY) / dataHour.height; | |
return dataHour.h + Math.round(relativeHeight * H_IN_MS); | |
}, [reversedHourPositions]); | |
/** | |
* Calculates marker time based on event. | |
* @type {function(number, {style: {top: number, height: number}, event: {onAirTime: number, duration: number}}): number} | |
*/ | |
const calculateMarkerTimeByEvent = useCallback((offsetY, {style: {top, height}, event: {onAirTime, duration}}) => { | |
const relativeHeight = 1 - (top + height - offsetY) / height; | |
return TimeService.getUTCWithOffset(onAirTime + Math.round(relativeHeight * duration), timeZone); | |
}, [timeZone]); | |
/** | |
* Finds event id from DOM elements recursively | |
* @type {function(element: HTMLElement): {eventId?: number, dayIndex?: number}} | |
*/ | |
const findEventFromElement = useCallback((element) => { | |
if (!element) { | |
return {}; | |
} | |
const eventId = +element.getAttribute("data-channel-event"); | |
if (eventId) { | |
return { | |
eventId, | |
dayIndex: +element.parentElement.getAttribute("data-day-index"), | |
}; | |
} | |
return findEventFromElement(element.parentElement); | |
}, []); | |
useEffect(() => { | |
setTimeout(() => { | |
const element = document.querySelector(".timeline > thead"); | |
const {height} = element.getBoundingClientRect(); | |
sidebarHeightRef.current = height; | |
}); | |
}, []); | |
useEffect(() => { | |
const element = scrollableContainerRef.current; | |
if (!element) { | |
return; | |
} | |
let initialTop = element.getBoundingClientRect().top; | |
let initialLeft = element.getBoundingClientRect().left; | |
let prevMarkerPosition = {x: 0, y: 0}; | |
let mousePosition = {clientX: 0, clientY: 0}; | |
let scrollPosition = {scrollLeft: 0, scrollTop: 0}; | |
const debouncedUpdateElementPos = _.debounce(() => { | |
const clientRect = element.getBoundingClientRect(); | |
initialTop = clientRect.top; | |
initialLeft = clientRect.left; | |
}, 100); | |
const handleEvent = (e) => { | |
if (!e) { | |
return; | |
} | |
let eventElement = e.target; | |
let xPosition, yPosition; | |
debouncedUpdateElementPos(); | |
if (e.type === "scroll") { | |
const {scrollLeft, scrollTop} = scrollPosition; | |
prevMarkerPosition.x += ((scrollLeft - e.target.scrollLeft) * -1); | |
prevMarkerPosition.y += ((scrollTop - e.target.scrollTop) * -1); | |
scrollPosition = { | |
scrollLeft: e.target.scrollLeft, | |
scrollTop: e.target.scrollTop, | |
}; | |
xPosition = prevMarkerPosition.x; | |
yPosition = prevMarkerPosition.y; | |
eventElement = document.elementFromPoint(mousePosition.clientX, mousePosition.clientY); | |
} | |
else { | |
xPosition = e.clientX - initialLeft + element.scrollLeft; | |
yPosition = e.clientY - initialTop + element.scrollTop - sidebarHeightRef.current; | |
yPosition = yPosition <= 0 ? 0 : yPosition; | |
} | |
const {eventId, dayIndex} = findEventFromElement(eventElement); | |
const currentDay = weekData.days[dayIndex]; | |
let markerTime; | |
if (eventId) { | |
const {events} = currentDay; | |
const {event, style} = events.find(({event}) => (event.id | 0) === eventId); | |
markerTime = calculateMarkerTimeByEvent(yPosition, {style, event}); | |
} | |
else { | |
markerTime = calculateMarkerTime(yPosition); | |
} | |
if (e.type !== "scroll") { | |
mousePosition.clientX = e.clientX; | |
mousePosition.clientY = e.clientY; | |
} | |
weekViewStoreRef.current = { | |
eventMarkerTime: markerTime, | |
}; | |
prevMarkerPosition.x = xPosition; | |
prevMarkerPosition.y = yPosition; | |
setMarker({ | |
top: Math.min(yPosition, totalDayHeight - MARKER_HEIGHT), | |
time: markerTime, | |
}); | |
}; | |
const eventTypes = ["mousemove", "scroll"]; | |
for (const eventType of eventTypes) { | |
element.addEventListener(eventType, handleEvent); | |
} | |
return () => { | |
for (const eventType of eventTypes) { | |
element.removeEventListener(eventType, handleEvent); | |
} | |
}; | |
}, [calculateMarkerTime, calculateMarkerTimeByEvent, findEventFromElement, scrollableContainerRef, totalDayHeight, weekData.days, weekViewStoreRef]); | |
if (!marker) { | |
return null; | |
} | |
return ( | |
<td> | |
<span className="position-absolute" | |
style={{ | |
left: 0, | |
top: marker.top, | |
height: MARKER_HEIGHT, | |
zIndex: 100, | |
}}> | |
<Badge pill color="primary"> | |
<TimeFormat time={marker.time} | |
format={timeAccurateFormatValue} /> | |
</Badge> | |
</span> | |
</td> | |
); | |
}; | |
export default React.memo(CCWDragDropSelected); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment