diff options
Diffstat (limited to 'src/client/views/nodes/RecordingBox/ProgressBar.tsx')
-rw-r--r-- | src/client/views/nodes/RecordingBox/ProgressBar.tsx | 205 |
1 files changed, 105 insertions, 100 deletions
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx index 1bb2b7c84..62798bc2f 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx +++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx @@ -1,31 +1,33 @@ +/* eslint-disable react/no-array-index-key */ +/* eslint-disable react/require-default-props */ import * as React from 'react'; -import { useEffect, useState, useCallback, useRef } from "react" -import "./ProgressBar.scss" +import { useEffect, useState, useRef } from 'react'; +import './ProgressBar.scss'; import { MediaSegment } from './RecordingView'; interface ProgressBarProps { - videos: MediaSegment[], - setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>, - orderVideos: boolean, - progress: number, - recording: boolean, - doUndo: boolean, - setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>, + videos: MediaSegment[]; + setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>; + orderVideos: boolean; + progress: number; + recording: boolean; + doUndo: boolean; + setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>; } interface SegmentBox { - endTime: number, - startTime: number, - order: number, + endTime: number; + startTime: number; + order: number; } interface CurrentHover { - index: number, - minX: number, - maxX: number + index: number; + minX: number; + maxX: number; } export function ProgressBar(props: ProgressBarProps) { - const progressBarRef = useRef<HTMLDivElement | null>(null) + const progressBarRef = useRef<HTMLDivElement | null>(null); // the actual list of JSX elements rendered as segments const [segments, setSegments] = useState<JSX.Element[]>([]); @@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) { // update the canUndo props based on undo stack useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]); - // useEffect for undo - brings back the most recently deleted segment - useEffect(() => handleUndo(), [props.doUndo]) const handleUndo = () => { // get the last element from the undo if it exists if (undoStack.length === 0) return; @@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) { // update the removed time and place element back into ordered setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime)); setOrdered(prevOrdered => [...prevOrdered, last]); - } + }; + // useEffect for undo - brings back the most recently deleted segment + useEffect(() => handleUndo(), [props.doUndo]); // useEffect for recording changes - changes style to disabled and adds the "expanding-segment" useEffect(() => { // get segments segment's html using it's id -> make them appeared disabled (or enabled) - segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording)); + segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording)); progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording); if (props.recording) - setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]); - }, [props.recording]) - + setSegments(prevSegments => [ + ...prevSegments, + <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}> + {props.videos.length + 1} + </div>, + ]); + }, [props.recording]); // useEffect that updates the segmentsJSX, which is rendered // only updated when ordered is updated or if the user is dragging around a segment useEffect(() => { const totalTime = props.progress * 1000 - totalRemovedTime; - const segmentsJSX = ordered.map((seg, i) => - <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>); + const segmentsJSX = ordered.map((seg, i) => ( + <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}> + {seg.order + 1} + </div> + )); - setSegments(segmentsJSX) + setSegments(segmentsJSX); }, [dragged, ordered]); // useEffect for dragged - update the cursor to be grabbing while grabbing @@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) { // to imporve performance, only want to update the CSS width, not re-render the whole JSXList useEffect(() => { - if (!props.recording) return + if (!props.recording) return; const totalTime = props.progress * 1000 - totalRemovedTime; let remainingTime = totalTime; segments.forEach((seg, i) => { // for the last segment, we need to set that directly if (i === segments.length - 1) return; // update remaining time - remainingTime -= (ordered[i].endTime - ordered[i].startTime); + remainingTime -= ordered[i].endTime - ordered[i].startTime; // update the width for this segment const htmlId = seg.props.id; @@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) { // update the width of the expanding segment using the remaining time const segExapandHtml = document.getElementById('segment-expanding'); - if (segExapandHtml) - segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`; + if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`; }, [props.progress]); // useEffect for props.videos - update the ordered array when a new video is added @@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) { // in this case, a new video is added -> push it onto ordered if (order >= ordered.length) { const { endTime, startTime } = props.videos.lastElement(); - setOrdered(prevOrdered => { - return [...prevOrdered, { endTime, startTime, order }]; - }); + setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]); } // in this case, a video is removed @@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) { }, [props.videos]); // useEffect for props.orderVideos - matched the order array with the videos array before the export - useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]); + useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]); // useEffect for removed - handles logic for removing a segment useEffect(() => { @@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) { // returns the new currentHover based on the new index const updateCurrentHover = (segId: number): CurrentHover | null => { // get the segId of the segment that will become the new bounding area - const rect = progressBarRef.current?.children[segId].getBoundingClientRect() - if (rect == null) return null + const rect = progressBarRef.current?.children[segId].getBoundingClientRect(); + if (rect == null) return null; return { index: segId, minX: rect.x, maxX: rect.x + rect.width, - } - } + }; + }; + + const swapSegments = (oldIndex: number, newIndex: number) => { + if (newIndex == null) return; + setOrdered(prevOrdered => { + const temp = { ...prevOrdered[oldIndex] }; + prevOrdered[oldIndex] = prevOrdered[newIndex]; + prevOrdered[newIndex] = temp; + return prevOrdered; + }); + // update visually where the segment is hovering over + setDragged(newIndex); + }; + + // functions for the floating segment that tracks the cursor while grabbing it + const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => { + dot.classList.add('segment-selected'); + dot.style.transitionDuration = '0s'; + dot.style.position = 'absolute'; + dot.style.zIndex = '999'; + dot.style.width = `${rect.width}px`; + dot.style.height = `${rect.height}px`; + dot.style.left = `${rect.x}px`; + dot.style.top = `${rect.y}px`; + dot.draggable = false; + document.body.append(dot); + }; + const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => { + // event.stopPropagation() + const { width, height } = dot.getBoundingClientRect(); + dot.style.left = `${event.clientX - width / 2}px`; + dot.style.top = `${event.clientY - height / 2}px`; + }; // pointerdown event for the progress bar - const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { - // don't move the videobox element - e.stopPropagation(); + const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { + // don't move the videobox element + e.stopPropagation(); // if recording, do nothing - if (props.recording) return; + if (props.recording) return; // get the segment the user clicked on to be dragged - const clickedSegment = e.target as HTMLDivElement & EventTarget + const clickedSegment = e.target as HTMLDivElement & EventTarget; // get the profess bar ro add event listeners // don't do anything if null - const progressBar = progressBarRef.current - if (progressBar == null || clickedSegment.id === progressBar.id) return + const progressBar = progressBarRef.current; + if (progressBar == null || clickedSegment.id === progressBar.id) return; // if holding shift key, let's remove that segment if (e.shiftKey) { const segId = parseInt(clickedSegment.id.split('-')[1]); setRemoved(segId); - return + return; } // if holding ctrl key and click, let's undo that segment #hiddenfeature lol @@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) { // if we're here, the user is dragging a segment around // let the progress bar capture all the pointer events until the user releases (pointerUp) const ptrId = e.pointerId; - progressBar.setPointerCapture(ptrId) + progressBar.setPointerCapture(ptrId); - const rect = clickedSegment.getBoundingClientRect() - // id for segment is like 'segment-1' or 'segment-10', + const rect = clickedSegment.getBoundingClientRect(); + // id for segment is like 'segment-1' or 'segment-10', // so this works to get the id - const segId = parseInt(clickedSegment.id.split('-')[1]) + const segId = parseInt(clickedSegment.id.split('-')[1]); // set the selected segment to be the one dragged - setDragged(segId) + setDragged(segId); - // this is the logic for storing the lower X bound and upper X bound to know + // this is the logic for storing the lower X bound and upper X bound to know // whether a swap is needed between two segments let currentHover: CurrentHover = { index: segId, minX: rect.x, maxX: rect.x + rect.width, - } + }; // create the floating segment that tracks the cursor - const detchedSegment = document.createElement("div") - initDeatchSegment(detchedSegment, rect); + const detchedSegment = document.createElement('div'); + initDetachSegment(detchedSegment, rect); const updateSegmentOrder = (event: PointerEvent): void => { event.stopPropagation(); @@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) { // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged if (!progressBar.hasPointerCapture(ptrId)) { + // eslint-disable-next-line no-use-before-define placeSegmentandCleanup(); return; } @@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) { const curX = event.clientX; // handle the left bound if (curX < currentHover.minX && currentHover.index > 0) { - swapSegments(currentHover.index, currentHover.index - 1) - currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover + swapSegments(currentHover.index, currentHover.index - 1); + currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover; } // handle the right bound else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) { - swapSegments(currentHover.index, currentHover.index + 1) - currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover + swapSegments(currentHover.index, currentHover.index + 1); + currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover; } - } + }; // handles when the user is done dragging the segment (pointerUp) const placeSegmentandCleanup = (event?: PointerEvent): void => { event?.stopPropagation(); event?.preventDefault(); // if they put the segment outside of the bounds, remove it - if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) - setRemoved(currentHover.index); - + if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index); + // remove the update event listener for pointermove progressBar.removeEventListener('pointermove', updateSegmentOrder); // remove the floating segment from the DOM @@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) { // dragged is -1 is equiv to nothing being dragged, so the normal state // so this will place the segment in it's location and update the segment bar setDragged(-1); - } + }; // event listeners that allow the user to drag and release the floating segment progressBar.addEventListener('pointermove', updateSegmentOrder); progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true }); - } - - const swapSegments = (oldIndex: number, newIndex: number) => { - if (newIndex == null) return; - setOrdered(prevOrdered => { - const temp = { ...prevOrdered[oldIndex] } - prevOrdered[oldIndex] = prevOrdered[newIndex] - prevOrdered[newIndex] = temp - return prevOrdered - }); - // update visually where the segment is hovering over - setDragged(newIndex); - } - - // functions for the floating segment that tracks the cursor while grabbing it - const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => { - dot.classList.add("segment-selected"); - dot.style.transitionDuration = '0s'; - dot.style.position = 'absolute'; - dot.style.zIndex = '999'; - dot.style.width = `${rect.width}px`; - dot.style.height = `${rect.height}px`; - dot.style.left = `${rect.x}px`; - dot.style.top = `${rect.y}px`; - dot.draggable = false; - document.body.append(dot); - } - const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => { - // event.stopPropagation() - const { width, height } = dot.getBoundingClientRect(); - dot.style.left = `${event.clientX - width / 2}px`; - dot.style.top = `${event.clientY - height / 2}px`; - } - + }; return ( <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}> {segments} </div> - ) -}
\ No newline at end of file + ); +} |