1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-24 03:12:41 +01:00

fix timeline zoom todo

and fix types
This commit is contained in:
Mikael Finstad 2024-10-06 16:27:31 +02:00
parent c8ed94ad62
commit 85a900e15b
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 49 additions and 25 deletions

View File

@ -182,6 +182,7 @@ function App() {
const durationSafe = isDurationValid(duration) ? duration : 1;
const zoom = Math.floor(zoomUnrounded);
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]);
@ -2526,6 +2527,8 @@ function App() {
inverseCutSegments={inverseCutSegments}
formatTimecode={formatTimecode}
formatTimeAndFrames={formatTimeAndFrames}
zoomWindowStartTime={zoomWindowStartTime}
zoomWindowEndTime={zoomWindowEndTime}
onZoomWindowStartTimeChange={setZoomWindowStartTime}
playing={playing}
isFileOpened={isFileOpened}

View File

@ -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 debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
@ -48,7 +48,11 @@ const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }: {
// eslint-disable-next-line react/display-name
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' }}>
{waveforms.map((waveform) => (
@ -93,6 +97,8 @@ function Timeline({
shouldShowWaveform,
shouldShowKeyframes,
thumbnails,
zoomWindowStartTime,
zoomWindowEndTime,
onZoomWindowStartTimeChange,
waveformEnabled,
showThumbnails,
@ -121,6 +127,8 @@ function Timeline({
shouldShowWaveform: boolean,
shouldShowKeyframes: boolean,
thumbnails: Thumbnail[],
zoomWindowStartTime: number,
zoomWindowEndTime: number | undefined,
onZoomWindowStartTimeChange: (a: number) => void,
waveformEnabled: boolean,
showThumbnails: boolean,
@ -147,14 +155,14 @@ function Timeline({
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)
// See https://github.com/mifi/lossless-cut/issues/259
// todo
// const areKeyframesTooClose = keyframes.length > zoom * 200;
const areKeyframesTooClose = false;
const areKeyframesTooClose = keyFramesInZoomWindow.length > zoom * 200;
const calculateTimelinePos = useCallback((time) => (time !== undefined ? Math.min(time / durationSafe, 1) : undefined), [durationSafe]);
const calculateTimelinePercent = useCallback((time) => {
const calculateTimelinePos = useCallback((time: number | undefined) => (time !== undefined ? Math.min(time / durationSafe, 1) : undefined), [durationSafe]);
const calculateTimelinePercent = useCallback((time: number | undefined) => {
const pos = calculateTimelinePos(time);
return pos !== undefined ? `${pos * 100}%` : undefined;
}, [calculateTimelinePos]);
@ -228,7 +236,7 @@ function Timeline({
useEffect(() => {
const cancelWheel = (event) => event.preventDefault();
const cancelWheel = (event: WheelEvent) => event.preventDefault();
const scroller = timelineScrollerRef.current;
invariant(scroller != null);
@ -253,7 +261,7 @@ function Timeline({
/ (timelineScrollerRef.current.offsetWidth * zoom)) * duration));
}, [duration, seekAbs, zoomed, zoom, zoomWindowStartTime, onZoomWindowStartTimeChange]); */
const getMouseTimelinePos = useCallback((e) => {
const getMouseTimelinePos = useCallback((e: MouseEvent) => {
const target = timelineWrapperRef.current;
invariant(target != null);
const rect = target.getBoundingClientRect();
@ -261,22 +269,22 @@ function Timeline({
return (relX / target.offsetWidth) * 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(() => {
setHoveringTime(undefined);
}, [relevantTime]);
const onMouseDown = useCallback((e) => {
const onMouseDown = useCallback<MouseEventHandler<HTMLElement>>((e) => {
if (e.nativeEvent.buttons !== 1) return; // not primary button
handleScrub(e.nativeEvent);
mouseDownRef.current = e.target;
function onMouseMove(e2) {
function onMouseMove(e2: MouseEvent) {
if (mouseDownRef.current == null) return;
seekAbs(getMouseTimelinePos(e2));
}
@ -294,7 +302,7 @@ function Timeline({
window.addEventListener('mousemove', onMouseMove);
}, [getMouseTimelinePos, handleScrub, seekAbs]);
const onMouseMove = useCallback((e) => {
const onMouseMove = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
if (!mouseDownRef.current) { // no button pressed
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' }} />
))}

View File

@ -11,7 +11,14 @@ import { ApparentCutSegment, FormatTimecode } from './types';
function TimelineSeg({
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 { getSegColor } = useSegColors();

View File

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

View File

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