fix timeline zoom todo

and fix types
pull/2119/head
Mikael Finstad 2 years ago
parent c8ed94ad62
commit 85a900e15b
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -182,6 +182,7 @@ function App() {
const durationSafe = isDurationValid(duration) ? duration : 1; const durationSafe = isDurationValid(duration) ? duration : 1;
const zoom = Math.floor(zoomUnrounded); const zoom = Math.floor(zoomUnrounded);
const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined; const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined;
const zoomWindowEndTime = useMemo(() => (zoomedDuration != null ? zoomWindowStartTime + zoomedDuration : undefined), [zoomedDuration, zoomWindowStartTime]);
useEffect(() => setDocumentTitle({ filePath, working: working?.text, progress }), [progress, filePath, working?.text]); useEffect(() => setDocumentTitle({ filePath, working: working?.text, progress }), [progress, filePath, working?.text]);
@ -2526,6 +2527,8 @@ function App() {
inverseCutSegments={inverseCutSegments} inverseCutSegments={inverseCutSegments}
formatTimecode={formatTimecode} formatTimecode={formatTimecode}
formatTimeAndFrames={formatTimeAndFrames} formatTimeAndFrames={formatTimeAndFrames}
zoomWindowStartTime={zoomWindowStartTime}
zoomWindowEndTime={zoomWindowEndTime}
onZoomWindowStartTimeChange={setZoomWindowStartTime} onZoomWindowStartTimeChange={setZoomWindowStartTime}
playing={playing} playing={playing}
isFileOpened={isFileOpened} isFileOpened={isFileOpened}

@ -1,4 +1,4 @@
import { memo, useRef, useMemo, useCallback, useEffect, useState, MutableRefObject, CSSProperties, WheelEventHandler } from 'react'; import { memo, useRef, useMemo, useCallback, useEffect, useState, MutableRefObject, CSSProperties, WheelEventHandler, MouseEventHandler } from 'react';
import { motion, useMotionValue, useSpring } from 'framer-motion'; import { motion, useMotionValue, useSpring } from 'framer-motion';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -48,7 +48,11 @@ const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }: {
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const Waveforms = memo(({ calculateTimelinePercent, durationSafe, waveforms, zoom, height }: { const Waveforms = memo(({ calculateTimelinePercent, durationSafe, waveforms, zoom, height }: {
calculateTimelinePercent: CalculateTimelinePercent, durationSafe: number, waveforms: RenderableWaveform[], zoom: number, height: number, calculateTimelinePercent: CalculateTimelinePercent,
durationSafe: number,
waveforms: RenderableWaveform[],
zoom: number,
height: number,
}) => ( }) => (
<div style={{ height, width: `${zoom * 100}%`, position: 'relative' }}> <div style={{ height, width: `${zoom * 100}%`, position: 'relative' }}>
{waveforms.map((waveform) => ( {waveforms.map((waveform) => (
@ -93,6 +97,8 @@ function Timeline({
shouldShowWaveform, shouldShowWaveform,
shouldShowKeyframes, shouldShowKeyframes,
thumbnails, thumbnails,
zoomWindowStartTime,
zoomWindowEndTime,
onZoomWindowStartTimeChange, onZoomWindowStartTimeChange,
waveformEnabled, waveformEnabled,
showThumbnails, showThumbnails,
@ -121,6 +127,8 @@ function Timeline({
shouldShowWaveform: boolean, shouldShowWaveform: boolean,
shouldShowKeyframes: boolean, shouldShowKeyframes: boolean,
thumbnails: Thumbnail[], thumbnails: Thumbnail[],
zoomWindowStartTime: number,
zoomWindowEndTime: number | undefined,
onZoomWindowStartTimeChange: (a: number) => void, onZoomWindowStartTimeChange: (a: number) => void,
waveformEnabled: boolean, waveformEnabled: boolean,
showThumbnails: boolean, showThumbnails: boolean,
@ -147,14 +155,14 @@ function Timeline({
const isZoomed = zoom > 1; const isZoomed = zoom > 1;
const keyFramesInZoomWindow = useMemo(() => (zoomWindowEndTime == null ? [] : neighbouringKeyFrames.filter((f) => f.time >= zoomWindowStartTime && f.time <= zoomWindowEndTime)), [neighbouringKeyFrames, zoomWindowEndTime, zoomWindowStartTime]);
// Don't show keyframes if too packed together (at current zoom) // Don't show keyframes if too packed together (at current zoom)
// See https://github.com/mifi/lossless-cut/issues/259 // See https://github.com/mifi/lossless-cut/issues/259
// todo const areKeyframesTooClose = keyFramesInZoomWindow.length > zoom * 200;
// const areKeyframesTooClose = keyframes.length > zoom * 200;
const areKeyframesTooClose = false;
const calculateTimelinePos = useCallback((time) => (time !== undefined ? Math.min(time / durationSafe, 1) : undefined), [durationSafe]); const calculateTimelinePos = useCallback((time: number | undefined) => (time !== undefined ? Math.min(time / durationSafe, 1) : undefined), [durationSafe]);
const calculateTimelinePercent = useCallback((time) => { const calculateTimelinePercent = useCallback((time: number | undefined) => {
const pos = calculateTimelinePos(time); const pos = calculateTimelinePos(time);
return pos !== undefined ? `${pos * 100}%` : undefined; return pos !== undefined ? `${pos * 100}%` : undefined;
}, [calculateTimelinePos]); }, [calculateTimelinePos]);
@ -228,7 +236,7 @@ function Timeline({
useEffect(() => { useEffect(() => {
const cancelWheel = (event) => event.preventDefault(); const cancelWheel = (event: WheelEvent) => event.preventDefault();
const scroller = timelineScrollerRef.current; const scroller = timelineScrollerRef.current;
invariant(scroller != null); invariant(scroller != null);
@ -253,7 +261,7 @@ function Timeline({
/ (timelineScrollerRef.current.offsetWidth * zoom)) * duration)); / (timelineScrollerRef.current.offsetWidth * zoom)) * duration));
}, [duration, seekAbs, zoomed, zoom, zoomWindowStartTime, onZoomWindowStartTimeChange]); */ }, [duration, seekAbs, zoomed, zoom, zoomWindowStartTime, onZoomWindowStartTimeChange]); */
const getMouseTimelinePos = useCallback((e) => { const getMouseTimelinePos = useCallback((e: MouseEvent) => {
const target = timelineWrapperRef.current; const target = timelineWrapperRef.current;
invariant(target != null); invariant(target != null);
const rect = target.getBoundingClientRect(); const rect = target.getBoundingClientRect();
@ -261,22 +269,22 @@ function Timeline({
return (relX / target.offsetWidth) * durationSafe; return (relX / target.offsetWidth) * durationSafe;
}, [durationSafe]); }, [durationSafe]);
const mouseDownRef = useRef(); const mouseDownRef = useRef<unknown>();
const handleScrub = useCallback((e) => seekAbs((getMouseTimelinePos(e))), [seekAbs, getMouseTimelinePos]); const handleScrub = useCallback((e: MouseEvent) => seekAbs((getMouseTimelinePos(e))), [seekAbs, getMouseTimelinePos]);
useEffect(() => { useEffect(() => {
setHoveringTime(undefined); setHoveringTime(undefined);
}, [relevantTime]); }, [relevantTime]);
const onMouseDown = useCallback((e) => { const onMouseDown = useCallback<MouseEventHandler<HTMLElement>>((e) => {
if (e.nativeEvent.buttons !== 1) return; // not primary button if (e.nativeEvent.buttons !== 1) return; // not primary button
handleScrub(e.nativeEvent); handleScrub(e.nativeEvent);
mouseDownRef.current = e.target; mouseDownRef.current = e.target;
function onMouseMove(e2) { function onMouseMove(e2: MouseEvent) {
if (mouseDownRef.current == null) return; if (mouseDownRef.current == null) return;
seekAbs(getMouseTimelinePos(e2)); seekAbs(getMouseTimelinePos(e2));
} }
@ -294,7 +302,7 @@ function Timeline({
window.addEventListener('mousemove', onMouseMove); window.addEventListener('mousemove', onMouseMove);
}, [getMouseTimelinePos, handleScrub, seekAbs]); }, [getMouseTimelinePos, handleScrub, seekAbs]);
const onMouseMove = useCallback((e) => { const onMouseMove = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
if (!mouseDownRef.current) { // no button pressed if (!mouseDownRef.current) { // no button pressed
setHoveringTime(getMouseTimelinePos(e.nativeEvent)); setHoveringTime(getMouseTimelinePos(e.nativeEvent));
} }
@ -389,7 +397,7 @@ function Timeline({
/> />
))} ))}
{shouldShowKeyframes && !areKeyframesTooClose && neighbouringKeyFrames.map((f) => ( {shouldShowKeyframes && !areKeyframesTooClose && keyFramesInZoomWindow.map((f) => (
<div key={f.time} style={{ position: 'absolute', top: 0, bottom: 0, left: `${(f.time / durationSafe) * 100}%`, marginLeft: -1, width: 1, background: 'var(--gray11)', pointerEvents: 'none' }} /> <div key={f.time} style={{ position: 'absolute', top: 0, bottom: 0, left: `${(f.time / durationSafe) * 100}%`, marginLeft: -1, width: 1, background: 'var(--gray11)', pointerEvents: 'none' }} />
))} ))}

@ -11,7 +11,14 @@ import { ApparentCutSegment, FormatTimecode } from './types';
function TimelineSeg({ function TimelineSeg({
seg, duration, isActive, segNum, onSegClick, invertCutSegments, formatTimecode, selected, seg, duration, isActive, segNum, onSegClick, invertCutSegments, formatTimecode, selected,
} : { } : {
seg: ApparentCutSegment, duration: number, isActive: boolean, segNum: number, onSegClick: (a: number) => void, invertCutSegments: boolean, formatTimecode: FormatTimecode, selected: boolean, seg: ApparentCutSegment,
duration: number,
isActive: boolean,
segNum: number,
onSegClick: (a: number) => void,
invertCutSegments: boolean,
formatTimecode: FormatTimecode,
selected: boolean,
}) { }) {
const { darkMode } = useUserSettings(); const { darkMode } = useUserSettings();
const { getSegColor } = useSegColors(); const { getSegColor } = useSegColors();

@ -1,12 +1,11 @@
import { CSSProperties } from 'react'; import { RefAttributes } from 'react';
import * as RadixSwitch from '@radix-ui/react-switch'; import * as RadixSwitch from '@radix-ui/react-switch';
import classes from './Switch.module.css'; import classes from './Switch.module.css';
const Switch = ({ checked, disabled, onCheckedChange, title, style }: { const Switch = (props: RadixSwitch.SwitchProps & RefAttributes<HTMLButtonElement>) => (
checked: boolean, disabled?: boolean, onCheckedChange: (v: boolean) => void, title?: string, style?: CSSProperties, // eslint-disable-next-line react/jsx-props-no-spreading
}) => ( <RadixSwitch.Root className={classes['SwitchRoot']} {...props}>
<RadixSwitch.Root disabled={disabled} className={classes['SwitchRoot']} checked={checked} onCheckedChange={onCheckedChange} style={style} title={title}>
<RadixSwitch.Thumb className={classes['SwitchThumb']} /> <RadixSwitch.Thumb className={classes['SwitchThumb']} />
</RadixSwitch.Root> </RadixSwitch.Root>
); );

@ -8,9 +8,14 @@ import { FFprobeStream } from '../../../../ffprobe';
const maxKeyframes = 1000; const maxKeyframes = 1000;
// const maxKeyframes = 100; // const maxKeyframes = 100;
export default ({ keyframesEnabled, filePath, commandedTime, videoStream, detectedFps, ffmpegExtractWindow }: { function useKeyframes({ keyframesEnabled, filePath, commandedTime, videoStream, detectedFps, ffmpegExtractWindow }: {
keyframesEnabled: boolean, filePath: string | undefined, commandedTime: number, videoStream: FFprobeStream | undefined, detectedFps: number | undefined, ffmpegExtractWindow: number, keyframesEnabled: boolean,
}) => { filePath: string | undefined,
commandedTime: number,
videoStream: FFprobeStream | undefined,
detectedFps: number | undefined,
ffmpegExtractWindow: number,
}) {
const readingKeyframesPromise = useRef<Promise<unknown>>(); const readingKeyframesPromise = useRef<Promise<unknown>>();
const [neighbouringKeyFramesMap, setNeighbouringKeyFrames] = useState<Record<string, Frame>>({}); const [neighbouringKeyFramesMap, setNeighbouringKeyFrames] = useState<Record<string, Frame>>({});
const neighbouringKeyFrames = useMemo(() => Object.values(neighbouringKeyFramesMap), [neighbouringKeyFramesMap]); const neighbouringKeyFrames = useMemo(() => Object.values(neighbouringKeyFramesMap), [neighbouringKeyFramesMap]);
@ -61,4 +66,6 @@ export default ({ keyframesEnabled, filePath, commandedTime, videoStream, detect
return { return {
neighbouringKeyFrames, findNearestKeyFrameTime, neighbouringKeyFrames, findNearestKeyFrameTime,
}; };
}; }
export default useKeyframes;

Loading…
Cancel
Save