diff options
author | Michael Foiani <sotech117@Michaels-MacBook-Pro-5.local> | 2022-06-09 16:27:27 -0400 |
---|---|---|
committer | Michael Foiani <sotech117@Michaels-MacBook-Pro-5.local> | 2022-06-09 16:27:27 -0400 |
commit | 9024621c15962f3cdfccee6fc35294b44d4cf1ee (patch) | |
tree | abb3a92981b8d224fca84e873a10c2681371d9a5 /src | |
parent | e9ae7c0482f1c9409d905bc6474bfd06abb01743 (diff) |
fix undo, add disabled ui while recording, create expanding-segment to fill the space while recording
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/RecordingBox/ProgressBar.scss | 25 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/ProgressBar.tsx | 158 | ||||
-rw-r--r-- | src/client/views/nodes/RecordingBox/RecordingView.tsx | 40 |
3 files changed, 122 insertions, 101 deletions
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.scss b/src/client/views/nodes/RecordingBox/ProgressBar.scss index 3ed7cee3e..1cf60b82b 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.scss +++ b/src/client/views/nodes/RecordingBox/ProgressBar.scss @@ -45,8 +45,31 @@ text-align: center; } -.no-transition { +// .no-transition { +// transition-duration: 0s; +// } + +.segment-expanding { +border-color: red; + background-color: white; + transition-duration: 0s; + opacity: .75; + pointer-events: none; +} + +.segment-expanding:hover { + background-color: inherit; + cursor: not-allowed; +} + +.segment-disabled { + pointer-events: none; + opacity: 0.5; transition-duration: 0s; + /* Hide the text. */ + text-indent: 100%; + white-space: nowrap; + overflow: hidden; } .segment-hide { diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx index 54bfbc792..a00a4643d 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx +++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx @@ -1,8 +1,9 @@ +import { disable } from 'colors'; import { resolveTxt } from 'dns'; import { videointelligence } from 'googleapis/build/src/apis/videointelligence'; import { isInteger } from 'lodash'; import * as React from 'react'; -import { useEffect, useState, useCallback, useRef, useMemo } from "react" +import { useEffect, useState, useCallback, useRef } from "react" import "./ProgressBar.scss" import { MediaSegment } from './RecordingView'; @@ -11,7 +12,8 @@ interface ProgressBarProps { setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>, orderVideos: boolean, progress: number, - recording: boolean, + recording: boolean, + doUndo: boolean, } interface SegmentBox { @@ -31,68 +33,73 @@ export function ProgressBar(props: ProgressBarProps) { // array for the order of video segments const [segments, setSegments] = useState<JSX.Element[]>([]); - const [ordered, setOrdered] = useState<SegmentBox[]>([]); + const [ordered, setOrdered] = useState<SegmentBox[]>([]); + + const [undoStack, setUndoStack] = useState<SegmentBox[]>([]); const [dragged, setDragged] = useState<number>(-1); - // total length of the video, in seconds*100 - // const [totalTime, setTotalTime] = useState<number>(0); - // length of the time removed from the video, in seconds*100 const [totalRemovedTime, setTotalRemovedTime] = useState<number>(0); // this holds the index of the videoc segment to be removed const [removed, setRemoved] = useState<number>(-1); - - // const totalTime = useMemo(() => props.progress*1000 - totalRemovedTime, [props.progress, totalRemovedTime]); - - // const totalTime = useMemo(() => props.videos.lastElement().endTime, [props.videos]) - - // const totalTime = () => props.videos.lastElement().endTime - // const memoTotal = useMemo(totalTime, [props.videos]) - - // useEffect that updates total time based on progress - // useEffect(() => { - // console.log("useEffect progress", props.progress, 'totalRemovedTime', totalRemovedTime) - // // setTotalTime(prev => { - // // // progress is in seconds, prev is in deciseconds? - // // const toAdd = props.progress * 10 - prev - // // return prev + toAdd - // // }) - // // setTotalTime(prev => props.progress * 10) - // }, [props.progress]) + + + // abstracted for other uses - brings back the most recently deleted segment + const handleUndo = () => { + // get the last element from the undo if it exists + if (undoStack.length === 0) return; + // get and remove the last element from the undo stack + const last = undoStack.lastElement(); + setUndoStack(prevUndo => prevUndo.slice(0, -1)); + + // update the removed time and place element back into ordered + setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime)); + setOrdered(prevOrdered => [...prevOrdered, last]); + } + useEffect(() => handleUndo(), [props.doUndo]) useEffect(() => { - console.log("useEffect recording", props.recording) - segments.forEach((seg, i) => { - const htmlId = seg.props.id; - const segmentHtml = document.getElementById(htmlId); - segmentHtml?.classList.toggle('no-transition', props.recording); - console.log(segmentHtml) - }); + console.log("useEffect recording", props.recording) + // 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)); + + if (props.recording) + setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding' style={{ width: 'auto' }}>{props.videos.length + 1}</div>]); }, [props.recording]) useEffect(() => { - console.log('useEffect dragged, ordered') const totalTime = props.progress * 1000 - totalRemovedTime; const segmentsJSX = ordered.map((seg, i) => - <div id={`segment-${i.toString()}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order}</div>); + <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) }, [dragged, ordered]); // to imporve performance, we only want to update the width, not re-render the whole thing - useEffect(() => { - // console.log('updating width on progress useEffect') - const totalTime = props.progress * 1000 - totalRemovedTime; - segments.forEach((seg, i) => { - const htmlId = seg.props.id; - const segmentHtml = document.getElementById(htmlId); - segmentHtml?.style.width = `${((ordered[i].endTime - ordered[i].startTime) / totalTime) * 100}%`; + useEffect(() => { + 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); + + // update the width for this segment + const htmlId = seg.props.id; + const segmentHtml = document.getElementById(htmlId); + if (segmentHtml) segmentHtml.style.width = `${((ordered[i].endTime - ordered[i].startTime) / totalTime) * 100}%`; + }); - // console.log('updating width on progress useEffect', htmlId, segmentHtml, segmentHtml?.style.width) - }); + // 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}%`; + }, [props.progress]); @@ -102,52 +109,36 @@ export function ProgressBar(props: ProgressBarProps) { // in this case, do nothing. if (props.orderVideos) return; - const order = props.videos.length - // in this case, a new video is added - if (order && order > ordered.length) { + const order = props.videos.length - 1; + // in this case, a new video is added -> push it onto ordered + if (order >= ordered.length) { const { endTime, startTime } = props.videos.lastElement(); - // update total time - // setTotalTime(prevTime => prevTime + (endTime - startTime)); - // add the new segment to the ordered array setOrdered(prevOrdered => { return [...prevOrdered, { endTime, startTime , order }]; }); } - // in this case, a segment is removed + // in this case, a video is removed else if (order < ordered.length) { - // update the total time - // setTotalTime(prevTime => prevTime - (ordered[removed].endTime - ordered[removed].startTime)); - - // update total removed time - setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime)); - - // remove the segment from the ordered array and decrement the order of the remaining segments - // if they are greater than the removed segment's order - const removedOrder = ordered[removed].order; - setOrdered(prevOrdered => - prevOrdered.reduce((acc, seg, i) => { - (i !== removed) && acc.push({ ...seg, order: seg.order > removedOrder ? seg.order - 1 : seg.order }); - return acc; - },[] as SegmentBox[]) - ); - // reset to default/nullish state - setRemoved(-1); + console.error('warning: video removed from parent'); } }, [props.videos]); useEffect(() => { - props.setVideos(vids => ordered.map((seg) => vids[seg.order - 1])); + props.setVideos(vids => ordered.map((seg) => vids[seg.order])); }, [props.orderVideos]); useEffect(() => { if (removed === -1) return; - - console.log('removed', removed) - - // update the videos array -> this will fire the useEffect above, where we update the orders & totalTime - const index = ordered[removed].order - 1; - props.setVideos(vids => vids.filter((vid, i) => i !== index)); + // update total removed time + setTotalRemovedTime(prevRemoved => prevRemoved + (ordered[removed].endTime - ordered[removed].startTime)); + + // put the element on the undo stack + setUndoStack(prevUndo => [...prevUndo, ordered[removed]]); + // remove the segment from the array + setOrdered(prevOrdered => prevOrdered.filter((seg, i) => i !== removed)); + // reset to default/nullish state + setRemoved(-1); }, [removed]); const updateLastHover = (segId: number): CurrentHover | null => { @@ -163,7 +154,10 @@ export function ProgressBar(props: ProgressBarProps) { const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { // don't move the videobox element - e.stopPropagation() + e.stopPropagation() + + // if recording, do nothing + if (props.recording) return; // get the segment the user clicked on to be dragged const clickedSegment = e.target as HTMLDivElement & EventTarget @@ -177,10 +171,15 @@ export function ProgressBar(props: ProgressBarProps) { // TODO: think of a way to accomodate touch -> maybe if poiuntermove isn't fired after x secs? if (e.shiftKey) { const segId = parseInt(clickedSegment.id.split('-')[1]); - console.log('removing segment', segId) setRemoved(segId); return } + + if (e.ctrlKey) { + handleUndo(); + return; + // todo: implement undo stack + } const ptrId = e.pointerId; progressBar.setPointerCapture(ptrId) @@ -209,7 +208,6 @@ export function ProgressBar(props: ProgressBarProps) { event.preventDefault(); // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged - console.log('update cursor', progressBar.hasPointerCapture(ptrId), ptrId) if (!progressBar.hasPointerCapture(ptrId)) { placeSegmentandCleanup(); return; @@ -269,9 +267,6 @@ export function ProgressBar(props: ProgressBarProps) { dot.draggable = false; document.body.append(dot) } - const cleanupDetachSegment = (dot: HTMLDivElement) => { - dot.remove() - } const followCursor = (event: PointerEvent, dot: HTMLDivElement, rect: DOMRect): void => { // event.stopPropagation() // const { width, height } = dot.getBoundingClientRect() @@ -283,11 +278,6 @@ export function ProgressBar(props: ProgressBarProps) { return ( <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}> - {/* <div - className="progressbar done" - style={{ width: `${props.progress}%` }} - // onClick={handleClick} - ></div> */} {segments} </div> ) diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 40117c41c..e131420c3 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -31,7 +31,9 @@ export function RecordingView(props: IRecordingViewProps) { const recordingTimerRef = useRef<number>(0); const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second const [playing, setPlaying] = useState(false); - const [progress, setProgress] = useState(0); + const [progress, setProgress] = useState(0); + + const [doUndo, setDoUndo] = useState(false); const [videos, setVideos] = useState<MediaSegment[]>([]); const [orderVideos, setOrderVideos] = useState<boolean>(false); @@ -225,11 +227,15 @@ export function RecordingView(props: IRecordingViewProps) { } } - const clearPrevious = () => { - const numVideos = videos.length - setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) - setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) - setVideos(videos.filter((_, idx) => idx !== numVideos - 1)); + const undoPrevious = (e: React.MouseEvent) => { + e.stopPropagation(); + console.log('undo previous', doUndo) + setDoUndo(prev => !prev); + // const numVideos = videos.length + // setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + // setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + // setVideos(videos.filter((_, idx) => idx !== numVideos - 1)); + } const handleOnTimeUpdate = () => { @@ -247,7 +253,8 @@ export function RecordingView(props: IRecordingViewProps) { return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); } - return ( + // TODO: have the undo button only appear if there is something to undo + return ( <div className="recording-container"> <div className="video-wrapper"> <video id={`video-${props.id}`} @@ -271,10 +278,10 @@ export function RecordingView(props: IRecordingViewProps) { {!recording && (videos.length > 0 ? - <div className="options-wrapper video-edit-wrapper"> - {/* <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}> - <MdBackspace onClick={clearPrevious} /> - </IconContext.Provider> */} + <div className="options-wrapper video-edit-wrapper"> + <IconContext.Provider value={{ color: "grey", className: "video-edit-buttons" }}> + <MdBackspace onClick={undoPrevious} /> + </IconContext.Provider> <IconContext.Provider value={{ color: "#cc1c08", className: "video-edit-buttons" }}> <FaCheckCircle onClick={stop} /> </IconContext.Provider> @@ -293,11 +300,12 @@ export function RecordingView(props: IRecordingViewProps) { </div> <ProgressBar - videos={videos} - setVideos={setVideos} - orderVideos={orderVideos} - progress={progress} - recording={recording} + videos={videos} + setVideos={setVideos} + orderVideos={orderVideos} + progress={progress} + recording={recording} + doUndo={doUndo} // playSegment={playSegment} /> </div> |