Browse Source

删除伪代码

feature/video-erase
Luckyu_js 2 weeks ago
parent
commit
5fd4ba6c85
  1. 7
      src/components/canvas/ActiveVideoEditHost.tsx
  2. 60
      src/components/media/MediaVideoEraseOverlay.tsx
  3. 5
      src/components/media/MediaVideoEraseToolbar.tsx
  4. 9
      src/components/media/video-edit/VideoBoxEraseSession.tsx
  5. 5
      src/components/media/video-edit/VideoSmartEraseSession.tsx

7
src/components/canvas/ActiveVideoEditHost.tsx

@ -1,6 +1,6 @@
"use client";
import { useLayoutEffect, useMemo, useRef, type CSSProperties } from "react";
import { useLayoutEffect, useMemo, type CSSProperties } from "react";
import { VideoBoxEraseSession } from "@/components/media/video-edit/VideoBoxEraseSession";
import { VideoSmartEraseSession } from "@/components/media/video-edit/VideoSmartEraseSession";
import { useNodeToolStore } from "@/store/nodeToolStore";
@ -60,10 +60,6 @@ export function ActiveVideoEditHost() {
});
const videoModels = useModelStore((state) => state.byKind.video);
// 框选 overlay 用节点 dimensions letterbox 出显示框,无需真实 <video> 元素。
// 保留一个恒定为 null 的稳定 ref 供 session 类型契约使用。
const videoElRef = useRef<HTMLVideoElement | null>(null);
const videoSource = node ? getNodeVideoSource(node) : null;
const styleDims = node ? getWorkflowNodeStyleDimensions(node) : null;
const dimensions = useMemo(() => asVideoDimensions(node?.data?.dimensions), [node?.data?.dimensions]);
@ -93,7 +89,6 @@ export function ActiveVideoEditHost() {
{activeTool === "videoBoxErase" ? (
<VideoBoxEraseSession
nodeId={node.id}
videoRef={videoElRef}
dimensions={sessionDimensions}
models={videoModels}
onClose={closeTool}

60
src/components/media/MediaVideoEraseOverlay.tsx

@ -1,16 +1,8 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { RefObject } from "react";
import type { VideoEraseRatioBox } from "@/utils/videoEraseNodes";
export interface VideoEraseFrameRect {
left: number;
top: number;
width: number;
height: number;
}
export interface VideoEraseHistoryState {
canUndo: boolean;
canRedo: boolean;
@ -42,15 +34,12 @@ type DragMode = "move" | "n" | "s" | "e" | "w" | "nw" | "ne" | "sw" | "se" | "cr
const MIN_BOX_SIZE = 24;
interface MediaVideoEraseOverlayProps {
mediaElementRef: RefObject<HTMLVideoElement | null>;
naturalWidth?: number;
naturalHeight?: number;
busy?: boolean;
undoSignal?: number;
redoSignal?: number;
resetSignal?: number;
onBoxesChange?: (boxes: VideoEraseRatioBox[]) => void;
onFrameChange?: (rect: VideoEraseFrameRect | null) => void;
onHistoryStateChange?: (state: VideoEraseHistoryState) => void;
}
@ -64,11 +53,6 @@ function validDimensions(width: unknown, height: unknown): { width: number; heig
: null;
}
function readVideoNaturalDimensions(el: HTMLVideoElement | null): { width: number; height: number } | null {
if (!el) return null;
return validDimensions(el.videoWidth, el.videoHeight);
}
function cloneBoxes(boxes: EraseBox[]): EraseBox[] {
return boxes.map((box) => ({ ...box }));
}
@ -119,15 +103,12 @@ function resizeFree(box: EraseBox, bounds: DisplayRect, mode: DragMode, pointerX
* (bounds), rect/clientWidth
*/
export function MediaVideoEraseOverlay({
mediaElementRef,
naturalWidth,
naturalHeight,
busy = false,
undoSignal = 0,
redoSignal = 0,
resetSignal = 0,
onBoxesChange,
onFrameChange,
onHistoryStateChange,
}: MediaVideoEraseOverlayProps) {
const overlayRef = useRef<HTMLDivElement>(null);
@ -147,8 +128,9 @@ export function MediaVideoEraseOverlay({
const [boxes, setBoxes] = useState<EraseBox[]>([]);
const [selectedId, setSelectedId] = useState<number | null>(null);
const [historyState, setHistoryState] = useState<VideoEraseHistoryState>({ canUndo: false, canRedo: false });
const [naturalDimensions, setNaturalDimensions] = useState<{ width: number; height: number } | null>(() =>
validDimensions(naturalWidth, naturalHeight)
const naturalDimensions = useMemo(
() => validDimensions(naturalWidth, naturalHeight),
[naturalWidth, naturalHeight]
);
const updateHistoryState = useCallback(() => {
@ -193,23 +175,6 @@ export function MediaVideoEraseOverlay({
setSelectedId(next);
}, []);
useEffect(() => {
const explicit = validDimensions(naturalWidth, naturalHeight);
if (explicit) {
setNaturalDimensions(explicit);
return;
}
const el = mediaElementRef.current;
const update = () => {
const next = readVideoNaturalDimensions(el);
if (next) setNaturalDimensions(next);
};
update();
if (!el) return;
el.addEventListener("loadedmetadata", update);
return () => el.removeEventListener("loadedmetadata", update);
}, [mediaElementRef, naturalHeight, naturalWidth]);
const resolvedWidth = naturalDimensions?.width ?? 0;
const resolvedHeight = naturalDimensions?.height ?? 0;
@ -282,7 +247,7 @@ export function MediaVideoEraseOverlay({
}, []);
const handleCreatePointerDown = useCallback((event: React.PointerEvent) => {
if (busy || !bounds) return;
if (!bounds) return;
// 只处理空白区域的按下(在框上按下会被框自身的 handler 拦截并 stopPropagation)。
event.preventDefault();
const point = pointerToOverlay(event);
@ -295,10 +260,10 @@ export function MediaVideoEraseOverlay({
dragRef.current = { mode: "create", boxId: id, startX, startY, origin: newBox };
event.currentTarget.setPointerCapture(event.pointerId);
overlayRef.current?.focus();
}, [bounds, busy, pointerToOverlay, setBoxesWithRefs, setSelectedIdWithRef]);
}, [bounds, pointerToOverlay, setBoxesWithRefs, setSelectedIdWithRef]);
const handleBoxPointerDown = useCallback((event: React.PointerEvent, boxId: number, mode: DragMode) => {
if (busy || !bounds) return;
if (!bounds) return;
event.preventDefault();
event.stopPropagation();
const origin = boxes.find((box) => box.id === boxId);
@ -308,7 +273,7 @@ export function MediaVideoEraseOverlay({
dragRef.current = { mode, boxId, startX: point.x, startY: point.y, origin };
event.currentTarget.setPointerCapture(event.pointerId);
overlayRef.current?.focus();
}, [bounds, boxes, busy, pointerToOverlay, setSelectedIdWithRef]);
}, [bounds, boxes, pointerToOverlay, setSelectedIdWithRef]);
const handlePointerMove = useCallback((event: React.PointerEvent) => {
const drag = dragRef.current;
@ -350,7 +315,6 @@ export function MediaVideoEraseOverlay({
}, [commitHistory]);
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
if (busy) return;
if (event.key !== "Delete" && event.key !== "Backspace") return;
const currentSelectedId = selectedIdRef.current;
if (currentSelectedId === null) return;
@ -362,13 +326,12 @@ export function MediaVideoEraseOverlay({
setBoxes(nextBoxes);
setSelectedId(null);
commitHistory({ boxes: nextBoxes, selectedId: null });
}, [busy, commitHistory]);
}, [commitHistory]);
// 归一化擦除框 + 选中框的显示矩形回调。
// 归一化擦除框回调(erase_ratio_location)
useEffect(() => {
if (!bounds || resolvedWidth <= 0 || resolvedHeight <= 0) {
onBoxesChange?.([]);
onFrameChange?.(null);
return;
}
const ratioBoxes: VideoEraseRatioBox[] = boxes.map((box) => {
@ -384,10 +347,7 @@ export function MediaVideoEraseOverlay({
};
});
onBoxesChange?.(ratioBoxes);
const selected = boxes.find((box) => box.id === selectedId) ?? boxes[boxes.length - 1] ?? null;
onFrameChange?.(selected ? { left: selected.x, top: selected.y, width: selected.width, height: selected.height } : bounds);
}, [boxes, bounds, onBoxesChange, onFrameChange, resolvedHeight, resolvedWidth, selectedId]);
}, [boxes, bounds, onBoxesChange, resolvedHeight, resolvedWidth]);
const handleModes = useMemo(() => ["nw", "ne", "sw", "se"] as const, []);

5
src/components/media/MediaVideoEraseToolbar.tsx

@ -94,7 +94,6 @@ export function MediaVideoEraseToolbar({
}
interface MediaVideoSmartEraseToolbarProps {
busy?: boolean;
onCancel: () => void;
onConfirm: () => void;
}
@ -103,7 +102,7 @@ interface MediaVideoSmartEraseToolbarProps {
* 智能擦除工具条:锚定在媒体框下沿( NodeActionCapsuleShell),
* "AI一键去除视频字幕", 207
*/
export function MediaVideoSmartEraseToolbar({ busy = false, onCancel, onConfirm }: MediaVideoSmartEraseToolbarProps) {
export function MediaVideoSmartEraseToolbar({ onCancel, onConfirm }: MediaVideoSmartEraseToolbarProps) {
const { t } = useI18n();
return (
@ -115,7 +114,6 @@ export function MediaVideoSmartEraseToolbar({ busy = false, onCancel, onConfirm
type="button"
aria-label={t("videoErase.closeSmartErase")}
title={t("videoErase.closeSmartErase")}
disabled={busy}
onClick={onCancel}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-base text-[var(--text-secondary)] hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)] disabled:opacity-70"
>
@ -128,7 +126,6 @@ export function MediaVideoSmartEraseToolbar({ busy = false, onCancel, onConfirm
type="button"
aria-label={t("videoErase.generateSmartErase")}
title={t("videoErase.generateSmartErase")}
disabled={busy}
onClick={onConfirm}
className="ml-auto flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--text-primary)] text-[var(--text-inverse)] transition-colors hover:bg-[var(--text-secondary)] disabled:cursor-wait disabled:bg-[var(--surface-active)] disabled:text-[var(--text-disabled)]"
>

9
src/components/media/video-edit/VideoBoxEraseSession.tsx

@ -1,7 +1,6 @@
"use client";
import { useCallback, useRef, useState } from "react";
import type { RefObject } from "react";
import { useCallback, useState } from "react";
import type { ProviderModel } from "@/lib/providers/types";
import { useWorkflowStore } from "@/store/workflowStore";
import {
@ -18,7 +17,6 @@ import { defaultNodeDimensions } from "@/constants/nodeDimensions";
interface VideoBoxEraseSessionProps {
nodeId: string;
videoRef: RefObject<HTMLVideoElement | null>;
dimensions?: { width: number; height: number } | null;
models: ProviderModel[];
onClose: () => void;
@ -35,7 +33,6 @@ function getNodeWidth(node: { style?: { width?: unknown } | undefined }) {
*/
export function VideoBoxEraseSession({
nodeId,
videoRef,
dimensions,
models,
onClose,
@ -45,7 +42,6 @@ export function VideoBoxEraseSession({
const [redoSignal, setRedoSignal] = useState(0);
const [resetSignal, setResetSignal] = useState(0);
const [historyState, setHistoryState] = useState<VideoEraseHistoryState>({ canUndo: false, canRedo: false });
const busyRef = useRef(false);
const addNode = useWorkflowStore((state) => state.addNode);
const onConnect = useWorkflowStore((state) => state.onConnect);
@ -54,11 +50,9 @@ export function VideoBoxEraseSession({
const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const handleConfirm = useCallback(() => {
if (busyRef.current) return;
if (ratioBoxes.length === 0) return;
const sourceNode = getNodeById(nodeId);
if (!sourceNode) return;
busyRef.current = true;
const currentPosition = sourceNode.position ?? { x: 0, y: 0 };
createVideoBoxEraseNode({
sourceNodeId: nodeId,
@ -80,7 +74,6 @@ export function VideoBoxEraseSession({
return (
<>
<MediaVideoEraseOverlay
mediaElementRef={videoRef}
naturalWidth={dimensions?.width}
naturalHeight={dimensions?.height}
undoSignal={undoSignal}

5
src/components/media/video-edit/VideoSmartEraseSession.tsx

@ -1,6 +1,6 @@
"use client";
import { useCallback, useRef } from "react";
import { useCallback } from "react";
import type { ProviderModel } from "@/lib/providers/types";
import { useWorkflowStore } from "@/store/workflowStore";
import { MediaVideoSmartEraseToolbar } from "@/components/media/MediaVideoEraseToolbar";
@ -27,7 +27,6 @@ function getNodeWidth(node: { style?: { width?: unknown } | undefined }) {
* 207
*/
export function VideoSmartEraseSession({ nodeId, dimensions, models, onClose }: VideoSmartEraseSessionProps) {
const busyRef = useRef(false);
const addNode = useWorkflowStore((state) => state.addNode);
const onConnect = useWorkflowStore((state) => state.onConnect);
const getNodeById = useWorkflowStore((state) => state.getNodeById);
@ -35,10 +34,8 @@ export function VideoSmartEraseSession({ nodeId, dimensions, models, onClose }:
const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const handleConfirm = useCallback(() => {
if (busyRef.current) return;
const sourceNode = getNodeById(nodeId);
if (!sourceNode) return;
busyRef.current = true;
const currentPosition = sourceNode.position ?? { x: 0, y: 0 };
createVideoSmartEraseNode({
sourceNodeId: nodeId,

Loading…
Cancel
Save