Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions src/main/SubtitleTimeline.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
import { css } from "@emotion/react";
import { SegmentsList as CuttingSegmentsList, Waveforms } from "./Timeline";
import {
Expand Down Expand Up @@ -27,6 +27,7 @@ import { useTranslation } from "react-i18next";
import { useHotkeys } from "react-hotkeys-hook";
import { KEYMAP } from "../globalKeys";
import { shallowEqual } from "react-redux";
import TimelineStamps from "./TimelineStamps";

/**
* Copy-paste of the timeline in Video.tsx, so that we can make some small adjustments,
Expand Down Expand Up @@ -57,13 +58,24 @@ const SubtitleTimeline: React.FC = () => {
paddingRight: "50%",
});

// Vars for timelineStamps
const [scrollLeft, setScrollLeft] = useState(0);
const [visibleWidth, setVisibleWidth] = useState(0);
const paddingOffset = visibleWidth / 2;
const virtualScrollLeft = scrollLeft - paddingOffset;

const setCurrentlyAtToClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const rect = e.currentTarget.getBoundingClientRect();
const offsetX = e.clientX - rect.left;
dispatch(setClickTriggered(true));
dispatch(setCurrentlyAt((offsetX / widthMiniTimeline) * (duration)));
};

// Make sure visibleWidth is set so canvas is drawn on first render
useLayoutEffect(() => {
updateScrollMetrics();
}, [width, duration]);

// Apply horizonal scrolling when scrolled from somewhere else
useEffect(() => {
if (currentlyAt !== undefined && refTop.current) {
Expand Down Expand Up @@ -114,6 +126,15 @@ const SubtitleTimeline: React.FC = () => {
{}, [currentlyAt],
);

const updateScrollMetrics = () => {
if (!refTop.current) {
return;
}
const el = refTop.current;
setScrollLeft(el.scrollLeft);
setVisibleWidth(el.clientWidth);
};

// Callback for the scroll container
const onEndScroll = (e: ScrollEvent) => {
// If scrolled by user
Expand All @@ -135,7 +156,6 @@ const SubtitleTimeline: React.FC = () => {
const subtitleTimelineStyle = css({
position: "relative",
width: "100%",
height: "250px",
paddingBottom: "15px",
});

Expand All @@ -146,25 +166,31 @@ const SubtitleTimeline: React.FC = () => {
css={{
position: "absolute",
width: "2px",
height: "200px",
height: "222px",
...(refTop.current) && { left: (refTop.current.clientWidth / 2) },
top: "10px",
background: `${theme.text}`,
zIndex: 100,
}}
/>
{/* Time codes above the timeline */}
<TimelineStamps
durationMs={duration}
zoomedWidth={width}
scrollLeft={virtualScrollLeft}
visibleWidth={visibleWidth}
height={20}
/>
{/* Scrollable timeline container. Has width of parent*/}
<ScrollContainer innerRef={refTop} css={{ overflow: "hidden", width: "100%", height: "215px" }}
vertical={false}
horizontal={true}
onEndScroll={onEndScroll}
onScroll={updateScrollMetrics}
// dom elements with this id in the container will not trigger scrolling when dragged
ignoreElements={".prevent-drag-scroll"}
>
{/* Container. Overflows. Width based on parent times zoom level*/}
<div ref={ref} css={timelineStyle}>
{/* Fake padding. TODO: Figure out a better way to pad absolutely positioned elements*/}
<div css={{ height: "10px" }} />
<TimelineSubtitleSegmentsList timelineWidth={width} />
<div css={{ position: "relative", height: "100px" }} >
<Waveforms timelineHeight={120} />
Expand Down
79 changes: 56 additions & 23 deletions src/main/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { debounce } from "lodash";
import React, { useState, useRef, useEffect, RefObject } from "react";
import React, { useState, useRef, useEffect, RefObject, useLayoutEffect } from "react";

import Draggable, { DraggableEventHandler } from "react-draggable";

Expand Down Expand Up @@ -42,6 +42,7 @@ import {
selectActiveSegmentIndex as chapterSelectActiveSegmentIndex,
moveCut as chapterMoveCut,
} from "../redux/chapterSlice";
import TimelineStamps from "./TimelineStamps";

/**
* A container for visualizing the cutting of the video, as well as for controlling
Expand Down Expand Up @@ -81,11 +82,16 @@ const Timeline: React.FC<{
const { ref, width = 1 } = useResizeObserver<HTMLDivElement>();
const scrollContainerRef = useRef<HTMLElement>(null);
const { width: scrollContainerWidth = 1 } = useResizeObserver<HTMLElement>({ ref: scrollContainerRef });
const topOffset = 20;

const currentlyScrolling = useRef(false);
const zoomCenter = useRef(0);

// Vars for timelineStamps
const timelineStampsHeight = 20;
const waveformHeight = timelineHeight - timelineStampsHeight;
const [scrollLeft, setScrollLeft] = useState(0);
const [visibleWidth, setVisibleWidth] = useState(0);

const updateScroll = () => {
if (currentlyScrolling.current) {
currentlyScrolling.current = false;
Expand All @@ -98,6 +104,16 @@ const Timeline: React.FC<{
const scrubberVisible = scrollLeft <= scrubberPosition && scrubberPosition <= scrollLeft + clientWidth;

zoomCenter.current = (scrubberVisible ? scrubberPosition : centerPosition) / width;

};

const updateScrollMetrics = () => {
if (!scrollContainerRef.current) {
return;
}
const el = scrollContainerRef.current;
setScrollLeft(el.scrollLeft);
setVisibleWidth(el.clientWidth);
};

const displayPercentage = (durationInSeconds / displayDuration);
Expand All @@ -106,6 +122,11 @@ const Timeline: React.FC<{
};
const zoomedWidth = getWaveformWidth(scrollContainerWidth);

// Make sure visibleWidth is set so canvas is drawn on first render
useLayoutEffect(() => {
updateScrollMetrics();
}, [width, duration]);

// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(updateScroll, [currentlyAt, timelineZoom, width, scrollContainerWidth]);

Expand All @@ -124,7 +145,6 @@ const Timeline: React.FC<{
position: "relative", // Need to set position for Draggable bounds to work
height: timelineHeight + "px",
width: `${zoomedWidth}px`, // Width modified by zoom
top: `${topOffset}px`,
});

// Update the current time based on the position clicked on the timeline
Expand All @@ -136,17 +156,31 @@ const Timeline: React.FC<{
};

return (
<ScrollContainer
innerRef={scrollContainerRef}
css={{ overflowY: "hidden", width: "100%", height: `${timelineHeight + topOffset}px` }}
vertical={false}
horizontal={true}
// dom elements with this id in the container will not trigger scrolling when dragged
ignoreElements={".prevent-drag-scroll"}
hideScrollbars={false} // ScrollContainer hides scrollbars per default
onEndScroll={updateScroll}
>
<CuttingActionsContextMenu>
<CuttingActionsContextMenu>
<div css={css({ position: "absolute" })}>
<TimelineStamps
durationMs={duration}
zoomedWidth={zoomedWidth}
scrollLeft={scrollLeft}
visibleWidth={visibleWidth}
height={timelineStampsHeight}
/>
</div>
<ScrollContainer
innerRef={scrollContainerRef}
css={{
overflowY: "hidden",
width: "100%",
height: `${timelineHeight}px`,
}}
vertical={false}
horizontal={true}
// dom elements with this id in the container will not trigger scrolling when dragged
ignoreElements={".prevent-drag-scroll"}
hideScrollbars={false} // ScrollContainer hides scrollbars per default
onScroll={updateScrollMetrics}
onEndScroll={updateScroll}
>
<div ref={ref} css={timelineStyle} onMouseDown={e => setCurrentlyAtToClick(e)}>
<Scrubber
ref={scrubberRef}
Expand All @@ -157,15 +191,15 @@ const Timeline: React.FC<{
setCurrentlyAt={setCurrentlyAt}
setIsPlaying={setIsPlaying}
/>
<div css={{ position: "relative", height: timelineHeight + "px" }}>
<div css={{ position: "relative", height: timelineHeight + "px", top: `${timelineStampsHeight}px` }}>
<Waveforms
timelineHeight={!isChapters ? timelineHeight : (timelineHeight / 4) * 3}
topOffset={!isChapters ? undefined : (timelineHeight / 4) * 1}
timelineHeight={!isChapters ? waveformHeight : (waveformHeight / 4) * 3}
topOffset={!isChapters ? undefined : (waveformHeight / 4) * 1}
/>
{isChapters &&
<SegmentsList
timelineWidth={width}
timelineHeight={(timelineHeight / 4) * 1}
timelineHeight={(waveformHeight / 4) * 1}
styleByActiveSegment={styleByActiveSegment}
tabable={true}
selectSegments={chapterSelectSegments}
Expand All @@ -175,7 +209,7 @@ const Timeline: React.FC<{
}
<SegmentsList
timelineWidth={width}
timelineHeight={!isChapters ? timelineHeight : (timelineHeight / 4) * 3}
timelineHeight={!isChapters ? waveformHeight : (waveformHeight / 4) * 3}
styleByActiveSegment={!isChapters ? styleByActiveSegment : false}
tabable={true}
selectSegments={selectSegments}
Expand All @@ -184,8 +218,8 @@ const Timeline: React.FC<{
/>
</div>
</div>
</CuttingActionsContextMenu>
</ScrollContainer>
</ScrollContainer>
</CuttingActionsContextMenu>
);
};

Expand Down Expand Up @@ -310,12 +344,11 @@ export const Scrubber = React.forwardRef<HTMLDivElement, ScrubberProps>((props,
height: timelineHeight + 20 + "px", // TODO: CHECK IF height: "100%",
width: "1px",
position: "absolute",
zIndex: 2,
zIndex: 20,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
top: "-20px",
});

const scrubberDragHandleStyle = css({
Expand Down
Loading
Loading