From ba5ecd123b5ef754b5904841b5bfa0193a7e6082 Mon Sep 17 00:00:00 2001 From: aarish mansur Date: Tue, 19 May 2026 12:28:04 +0530 Subject: [PATCH 1/3] added a trim controller --- src/components/TrimControl.tsx | 200 ++++++++++++++++++++++++--------- 1 file changed, 147 insertions(+), 53 deletions(-) diff --git a/src/components/TrimControl.tsx b/src/components/TrimControl.tsx index 5ba6d682..cc729c15 100644 --- a/src/components/TrimControl.tsx +++ b/src/components/TrimControl.tsx @@ -1,7 +1,7 @@ "use client"; import { EditRecipe } from "@/lib/types"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { AlertCircle } from "lucide-react"; import { formatDuration } from "@/lib/utils"; @@ -17,7 +17,7 @@ export default function TrimControl({ recipe, onChange, duration }: Props) { const [startErrorMsg, setStartErrorMsg] = useState(""); const [endErrorMsg, setEndErrorMsg] = useState(""); const [startInput, setStartInput] = useState( - recipe.trimStart.toString() + recipe.trimStart.toString() ); useEffect(() => { @@ -25,49 +25,89 @@ export default function TrimControl({ recipe, onChange, duration }: Props) { }, [recipe.trimStart]); const clipLength = - (recipe.trimEnd ?? duration) - recipe.trimStart; + (recipe.trimEnd ?? duration) - recipe.trimStart; + + const trackRef = useRef(null); + const dragging = useRef<"start" | "end" | null>(null); + + const xToSeconds = useCallback((clientX: number) => { + const track = trackRef.current; + if (!track || duration <= 0) return 0; + const { left, width } = track.getBoundingClientRect(); + const ratio = Math.max(0, Math.min(1, (clientX - left) / width)); + return parseFloat((ratio * duration).toFixed(1)); + }, [duration]); + + const applyDrag = useCallback((clientX: number) => { + const seconds = xToSeconds(clientX); + if (dragging.current === "start") { + const clamped = Math.min(seconds, (recipe.trimEnd ?? duration) - 0.1); + onChange({ trimStart: Math.max(0, clamped) }); + } else if (dragging.current === "end") { + const clamped = Math.max(seconds, recipe.trimStart + 0.1); + onChange({ trimEnd: Math.min(duration, clamped) }); + } + }, [xToSeconds, duration, recipe.trimStart, recipe.trimEnd, onChange]); + + useEffect(() => { + const onMove = (e: MouseEvent | TouchEvent) => { + const clientX = "touches" in e ? e.touches[0].clientX : e.clientX; + applyDrag(clientX); + }; + const onUp = () => { dragging.current = null; }; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + document.addEventListener("touchmove", onMove); + document.addEventListener("touchend", onUp); + return () => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + document.removeEventListener("touchmove", onMove); + document.removeEventListener("touchend", onUp); + }; + }, [applyDrag]); const handleStart = (val: string) => { - setStartInput(val); + setStartInput(val); + + if (val === "") { + setStart(false); + setStartErrorMsg(""); + return; + } + + const n = parseFloat(val); + + if (isNaN(n)) { + setStart(true); + setStartErrorMsg("Enter a valid number."); + return; + } + + if (n < 0) { + setStart(true); + setStartErrorMsg("Start time must be 0 or greater."); + return; + } + + if (duration > 0 && n >= duration) { + setStart(true); + setStartErrorMsg( + `Start time must be less than duration (${duration.toFixed(1)}s).` + ); + return; + } + + if (recipe.trimEnd !== null && n >= recipe.trimEnd) { + setStart(true); + setStartErrorMsg("Start time must be less than the end time."); + return; + } - if (val === "") { setStart(false); setStartErrorMsg(""); - return; - } - - const n = parseFloat(val); - - if (isNaN(n)) { - setStart(true); - setStartErrorMsg("Enter a valid number."); - return; - } - - if (n < 0) { - setStart(true); - setStartErrorMsg("Start time must be 0 or greater."); - return; - } - - if (duration > 0 && n >= duration) { - setStart(true); - setStartErrorMsg( - `Start time must be less than duration (${duration.toFixed(1)}s).` - ); - return; - } - - if (recipe.trimEnd !== null && n >= recipe.trimEnd) { - setStart(true); - setStartErrorMsg("Start time must be less than the end time."); - return; - } - - setStart(false); - setStartErrorMsg(""); - - onChange({ trimStart: n }); + + onChange({ trimStart: n }); }; const handleEnd = (val: string) => { @@ -118,6 +158,64 @@ export default function TrimControl({ recipe, onChange, duration }: Props) { return (
+ {duration > 0 && ( +
{ + if (dragging.current) return; + const s = xToSeconds(e.clientX); + onChange({ trimStart: s }); + }} + onKeyDown={(e) => { + if (e.key === "ArrowLeft") onChange({ trimStart: Math.max(0, recipe.trimStart - 0.1) }); + if (e.key === "ArrowRight") onChange({ trimStart: Math.min((recipe.trimEnd ?? duration) - 0.1, recipe.trimStart + 0.1) }); + }} + > +
+
+
{ dragging.current = "start"; }} + onTouchStart={() => { dragging.current = "start"; }} + onKeyDown={(e) => { + if (e.key === "ArrowLeft") onChange({ trimStart: Math.max(0, recipe.trimStart - 0.1) }); + if (e.key === "ArrowRight") onChange({ trimStart: Math.min((recipe.trimEnd ?? duration) - 0.1, recipe.trimStart + 0.1) }); + }} + /> +
{ dragging.current = "end"; }} + onTouchStart={() => { dragging.current = "end"; }} + onKeyDown={(e) => { + if (e.key === "ArrowLeft") onChange({ trimEnd: Math.max(recipe.trimStart + 0.1, (recipe.trimEnd ?? duration) - 0.1) }); + if (e.key === "ArrowRight") onChange({ trimEnd: Math.min(duration, (recipe.trimEnd ?? duration) + 0.1) }); + }} + /> +
+ )}
-
{duration > 0 && ( -

- Clip: {formatDuration(clipLength)} of{" "} - {formatDuration(duration)} -

- )} -
- +

+ Clip: {formatDuration(clipLength)} of{" "} + {formatDuration(duration)} +

+ )} +
); - } + From c93e1d39b1bd2cd201789090540bfe91bd486b99 Mon Sep 17 00:00:00 2001 From: aarish mansur Date: Tue, 19 May 2026 12:35:40 +0530 Subject: [PATCH 2/3] fix(trim): change role to toolbar to satisfy jsx-a11y lint rule --- src/components/TrimControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TrimControl.tsx b/src/components/TrimControl.tsx index cc729c15..92e78bc1 100644 --- a/src/components/TrimControl.tsx +++ b/src/components/TrimControl.tsx @@ -160,7 +160,7 @@ export default function TrimControl({ recipe, onChange, duration }: Props) {
{duration > 0 && (
Date: Wed, 20 May 2026 09:28:58 +0530 Subject: [PATCH 3/3] ci: trigger CI rerun