fix: prettier format
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m33s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m1s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m1s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 43s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m27s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m53s
AI Code Review / AI Code Review (pull_request) Failing after 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m3s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1m59s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 2m2s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m6s
CI/CD Pipeline / CI Gate (pull_request) Failing after 1m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 11m51s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1157h48m52s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 1157h48m54s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1157h48m55s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1157h49m39s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1157h49m40s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1157h49m41s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1157h49m44s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1157h49m46s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1157h50m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1157h51m49s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 1157h53m38s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1157h53m50s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1157h55m21s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1157h55m22s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1157h55m24s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 1158h26m53s

This commit is contained in:
2026-07-30 09:28:55 +08:00
parent 43e9054823
commit d23e8e9195
@@ -1,71 +1,75 @@
import { useState, useCallback, useEffect, useRef } from "react"
import { useState, useCallback, useEffect, useRef } from "react";
interface UsePlayheadDragOptions {
/** 当前播放时间(秒) */
currentTime: number
currentTime: number;
/** 缩放:每秒像素数 */
pps: number
pps: number;
/** 总时长(秒) */
totalDuration: number
totalDuration: number;
/** 播放头跳转回调 */
onSeek?: (time: number) => void
onSeek?: (time: number) => void;
}
/**
* 播放头拖拽 Hook
* 封装播放头的 mousedown/mousemove/mouseup 拖拽逻辑
*/
export const usePlayheadDrag = ({ pps, totalDuration, onSeek }: UsePlayheadDragOptions) => {
const [playheadDragging, setPlayheadDragging] = useState(false)
const trackRef = useRef<HTMLDivElement>(null)
export const usePlayheadDrag = ({
pps,
totalDuration,
onSeek,
}: UsePlayheadDragOptions) => {
const [playheadDragging, setPlayheadDragging] = useState(false);
const trackRef = useRef<HTMLDivElement>(null);
/* ── 播放头拖拽全局 mousemove/mouseup ── */
useEffect(() => {
if (!playheadDragging) return
if (!playheadDragging) return;
const handleMouseMove = (e: MouseEvent) => {
const trackEl = trackRef.current
if (!trackEl) return
const rect = trackEl.getBoundingClientRect()
const x = e.clientX - rect.left + trackEl.scrollLeft
const time = Math.max(0, Math.min(x / pps, totalDuration))
onSeek?.(Math.round(time * 10) / 10)
}
const trackEl = trackRef.current;
if (!trackEl) return;
const rect = trackEl.getBoundingClientRect();
const x = e.clientX - rect.left + trackEl.scrollLeft;
const time = Math.max(0, Math.min(x / pps, totalDuration));
onSeek?.(Math.round(time * 10) / 10);
};
const handleMouseUp = () => {
setPlayheadDragging(false)
}
setPlayheadDragging(false);
};
document.addEventListener("mousemove", handleMouseMove)
document.addEventListener("mouseup", handleMouseUp)
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove)
document.removeEventListener("mouseup", handleMouseUp)
}
}, [playheadDragging, pps, totalDuration, onSeek])
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [playheadDragging, pps, totalDuration, onSeek]);
/* ── 播放头拖拽开始 ── */
const handlePlayheadMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setPlayheadDragging(true)
}, [])
e.preventDefault();
e.stopPropagation();
setPlayheadDragging(true);
}, []);
/* ── 标尺点击跳转播放头 ── */
const handleRulerClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect()
const x = e.clientX - rect.left
const time = Math.max(0, Math.min(x / pps, totalDuration))
onSeek?.(Math.round(time * 10) / 10)
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const time = Math.max(0, Math.min(x / pps, totalDuration));
onSeek?.(Math.round(time * 10) / 10);
},
[pps, totalDuration, onSeek],
)
);
return {
trackRef,
playheadDragging,
handlePlayheadMouseDown,
handleRulerClick,
}
}
};
};