forked from 77media/video-flow
Merge branch 'dev' of https://git.qikongjian.com/77media/video-flow into dev
This commit is contained in:
commit
84a5e9ab58
@ -103,15 +103,13 @@ export function useWorkflowData() {
|
|||||||
} = useScriptService();
|
} = useScriptService();
|
||||||
// 初始化剧本
|
// 初始化剧本
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
if (currentStep !== '0') {
|
console.log('开始初始化剧本', originalText,episodeId);
|
||||||
console.log('开始初始化剧本', originalText,episodeId);
|
// TODO 为什么一开始没项目id
|
||||||
// TODO 为什么一开始没项目id
|
originalText && initializeFromProject(episodeId, originalText).then(() => {
|
||||||
originalText && initializeFromProject(episodeId, originalText).then(() => {
|
console.log('应用剧本');
|
||||||
console.log('应用剧本');
|
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
|
||||||
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
|
mode.includes('auto') && applyScript();
|
||||||
mode.includes('auto') && applyScript();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [originalText], {mode: 'none'});
|
}, [originalText], {mode: 'none'});
|
||||||
// 监听剧本加载完毕
|
// 监听剧本加载完毕
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -101,7 +101,7 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
if (activeTab === '0') {
|
if (activeTab === '0') {
|
||||||
const scriptTabContent = scriptTabContentRef.current;
|
const scriptTabContent = scriptTabContentRef.current;
|
||||||
if (scriptTabContent) {
|
if (scriptTabContent) {
|
||||||
return scriptTabContent.checkUpdate();
|
return scriptTabContent.switchBefore(tabId);
|
||||||
}
|
}
|
||||||
} else if (activeTab === '1') {
|
} else if (activeTab === '1') {
|
||||||
const characterTabContent = characterTabContentRef.current;
|
const characterTabContent = characterTabContentRef.current;
|
||||||
@ -183,6 +183,8 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
originalText={originalText}
|
originalText={originalText}
|
||||||
|
onApply={handleApply}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case '1':
|
case '1':
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export type PersonDetection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
scanState: 'idle' | 'scanning' | 'detected' | 'failed' | 'timeout';
|
||||||
backgroundImage?: string;
|
backgroundImage?: string;
|
||||||
videoSrc?: string;
|
videoSrc?: string;
|
||||||
detections: PersonDetection[];
|
detections: PersonDetection[];
|
||||||
@ -56,6 +57,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PersonDetectionScene: React.FC<Props> = ({
|
export const PersonDetectionScene: React.FC<Props> = ({
|
||||||
|
scanState,
|
||||||
backgroundImage,
|
backgroundImage,
|
||||||
videoSrc,
|
videoSrc,
|
||||||
detections,
|
detections,
|
||||||
@ -201,210 +203,214 @@ export const PersonDetectionScene: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 暗化层 */}
|
{scanState !== 'idle' && (
|
||||||
<AnimatePresence>
|
<>
|
||||||
{isShowScan && (
|
{/* 暗化层 */}
|
||||||
<motion.div
|
<AnimatePresence>
|
||||||
initial={{ opacity: 0 }}
|
{isShowScan && (
|
||||||
animate={{ opacity: 1 }}
|
<motion.div
|
||||||
exit={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
className="absolute inset-0 bg-black/40 backdrop-blur-sm z-10"
|
animate={{ opacity: 1 }}
|
||||||
/>
|
exit={{ opacity: 0 }}
|
||||||
)}
|
className="absolute inset-0 bg-black/40 backdrop-blur-sm z-10"
|
||||||
</AnimatePresence>
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 扫描状态提示 */}
|
{/* 扫描状态提示 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isShowScan && (
|
{isShowScan && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
className="absolute top-1/2 left-1/2 z-50 flex flex-col items-center -translate-y-1/2 -translate-x-1/2"
|
className="absolute top-1/2 left-1/2 z-50 flex flex-col items-center -translate-y-1/2 -translate-x-1/2"
|
||||||
>
|
>
|
||||||
{/* 状态图标 */}
|
{/* 状态图标 */}
|
||||||
<div className={`relative w-12 h-12 mb-2 ${isShowError ? 'scale-110' : ''}`}>
|
<div className={`relative w-12 h-12 mb-2 ${isShowError ? 'scale-110' : ''}`}>
|
||||||
{!isShowError ? (
|
{!isShowError ? (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 rounded-full"
|
className="absolute inset-0 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
border: '2px solid rgba(6, 182, 212, 0.5)',
|
border: '2px solid rgba(6, 182, 212, 0.5)',
|
||||||
borderTopColor: 'rgb(6, 182, 212)',
|
borderTopColor: 'rgb(6, 182, 212)',
|
||||||
}}
|
}}
|
||||||
animate={{ rotate: 360 }}
|
animate={{ rotate: 360 }}
|
||||||
transition={{ duration: 1.5, repeat: Infinity, ease: "linear" }}
|
transition={{ duration: 1.5, repeat: Infinity, ease: "linear" }}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-2 rounded-full"
|
className="absolute inset-2 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
border: '2px solid rgba(6, 182, 212, 0.3)',
|
border: '2px solid rgba(6, 182, 212, 0.3)',
|
||||||
borderRightColor: 'rgb(6, 182, 212)',
|
borderRightColor: 'rgb(6, 182, 212)',
|
||||||
}}
|
}}
|
||||||
animate={{ rotate: -360 }}
|
animate={{ rotate: -360 }}
|
||||||
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-4 bg-cyan-400 rounded-full" />
|
<div className="absolute inset-4 bg-cyan-400 rounded-full" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0.8, 1.1, 1],
|
||||||
|
borderColor: ['rgb(239, 68, 68)', 'rgb(239, 68, 68)', 'rgb(239, 68, 68)']
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="w-full h-full rounded-full flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<TriangleAlert className="w-12 h-12 text-red-500" />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 状态文字 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0.8 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
scale: [0.8, 1.1, 1],
|
transition={{ delay: 0.1 }}
|
||||||
borderColor: ['rgb(239, 68, 68)', 'rgb(239, 68, 68)', 'rgb(239, 68, 68)']
|
|
||||||
}}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
className="w-full h-full rounded-full flex items-center justify-center"
|
|
||||||
>
|
>
|
||||||
<TriangleAlert className="w-12 h-12 text-red-500" />
|
<div className="flex items-center gap-2">
|
||||||
|
{!isShowError ? (
|
||||||
|
<span className="text-cyan-400 text-sm font-medium tracking-wider whitespace-nowrap">
|
||||||
|
Intelligenting portrait recognition
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-red-400 text-sm font-medium tracking-wider whitespace-nowrap">
|
||||||
|
{scanStatus === 'timeout' ? 'Timeout, please try again' : 'Failed, please try again'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
</motion.div>
|
||||||
</div>
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 状态文字 */}
|
{/* 扫描动画效果 */}
|
||||||
<motion.div
|
<AnimatePresence>
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
{isShowScan && scanState === 'scanning' && (
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
transition={{ delay: 0.1 }}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{!isShowError ? (
|
|
||||||
<span className="text-cyan-400 text-sm font-medium tracking-wider whitespace-nowrap">
|
|
||||||
Intelligenting portrait recognition
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-red-400 text-sm font-medium tracking-wider whitespace-nowrap">
|
|
||||||
{scanStatus === 'timeout' ? 'Timeout, please try again' : 'Failed, please try again'}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* 扫描动画效果 */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{isShowScan && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className="absolute inset-0"
|
|
||||||
>
|
|
||||||
{/* 主扫描线 */}
|
|
||||||
<motion.div
|
|
||||||
className="absolute left-0 w-full h-[150px] z-20"
|
|
||||||
animate={scanControls}
|
|
||||||
initial={{ y: "-120%" }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
style={{
|
|
||||||
background: `
|
|
||||||
linear-gradient(
|
|
||||||
180deg,
|
|
||||||
transparent 0%,
|
|
||||||
rgba(6, 182, 212, 0.1) 20%,
|
|
||||||
rgba(6, 182, 212, 0.3) 50%,
|
|
||||||
rgba(6, 182, 212, 0.1) 80%,
|
|
||||||
transparent 100%
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
boxShadow: '0 0 30px rgba(6, 182, 212, 0.3)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 扫描线中心光束 */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute top-1/2 left-0 w-full h-[2px]"
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(90deg, transparent 0%, rgb(6, 182, 212) 50%, transparent 100%)',
|
|
||||||
boxShadow: '0 0 20px rgb(6, 182, 212)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* 扫描网格 */}
|
|
||||||
<div className="absolute inset-0 z-15">
|
|
||||||
<motion.div
|
|
||||||
className="w-full h-full"
|
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
style={{
|
|
||||||
backgroundImage: `
|
|
||||||
linear-gradient(90deg, rgba(6, 182, 212, 0.1) 1px, transparent 1px),
|
|
||||||
linear-gradient(0deg, rgba(6, 182, 212, 0.1) 1px, transparent 1px)
|
|
||||||
`,
|
|
||||||
backgroundSize: '40px 40px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<motion.div
|
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 0.5 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
style={{
|
|
||||||
backgroundImage: `
|
|
||||||
linear-gradient(90deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px),
|
|
||||||
linear-gradient(0deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px)
|
|
||||||
`,
|
|
||||||
backgroundSize: '20px 20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 四角扫描框 */}
|
|
||||||
<div className="absolute inset-4 z-30 pointer-events-none">
|
|
||||||
<motion.div
|
|
||||||
className="w-full h-full"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
>
|
>
|
||||||
{/* 左上角 */}
|
{/* 主扫描线 */}
|
||||||
<div className="absolute top-0 left-0 w-8 h-8 border-l-2 border-t-2 border-cyan-400/70" />
|
<motion.div
|
||||||
{/* 右上角 */}
|
className="absolute left-0 w-full h-[150px] z-20"
|
||||||
<div className="absolute top-0 right-0 w-8 h-8 border-r-2 border-t-2 border-cyan-400/70" />
|
animate={scanControls}
|
||||||
{/* 左下角 */}
|
initial={{ y: "-120%" }}
|
||||||
<div className="absolute bottom-0 left-0 w-8 h-8 border-l-2 border-b-2 border-cyan-400/70" />
|
exit={{ opacity: 0 }}
|
||||||
{/* 右下角 */}
|
style={{
|
||||||
<div className="absolute bottom-0 right-0 w-8 h-8 border-r-2 border-b-2 border-cyan-400/70" />
|
background: `
|
||||||
</motion.div>
|
linear-gradient(
|
||||||
</div>
|
180deg,
|
||||||
</motion.div>
|
transparent 0%,
|
||||||
)}
|
rgba(6, 182, 212, 0.1) 20%,
|
||||||
</AnimatePresence>
|
rgba(6, 182, 212, 0.3) 50%,
|
||||||
|
rgba(6, 182, 212, 0.1) 80%,
|
||||||
|
transparent 100%
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
boxShadow: '0 0 30px rgba(6, 182, 212, 0.3)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 扫描线中心光束 */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute top-1/2 left-0 w-full h-[2px]"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(90deg, transparent 0%, rgb(6, 182, 212) 50%, transparent 100%)',
|
||||||
|
boxShadow: '0 0 20px rgb(6, 182, 212)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
{/* 人物识别框和浮签 */}
|
{/* 扫描网格 */}
|
||||||
<AnimatePresence>
|
<div className="absolute inset-0 z-15">
|
||||||
{detections.length === 0 && triggerSuccess && (
|
<motion.div
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
className="w-full h-full"
|
||||||
<span className="text-white text-sm">No portrait detected</span>
|
initial={{ opacity: 0 }}
|
||||||
</div>
|
animate={{ opacity: 1 }}
|
||||||
)}
|
exit={{ opacity: 0 }}
|
||||||
{detections.map((person, index) => {
|
transition={{ duration: 0.5 }}
|
||||||
return (
|
style={{
|
||||||
<div key={person.id} className="cursor-pointer" onClick={() => {
|
backgroundImage: `
|
||||||
onPersonClick?.(person);
|
linear-gradient(90deg, rgba(6, 182, 212, 0.1) 1px, transparent 1px),
|
||||||
}}>
|
linear-gradient(0deg, rgba(6, 182, 212, 0.1) 1px, transparent 1px)
|
||||||
<PersonBox person={person} />
|
`,
|
||||||
<motion.div
|
backgroundSize: '40px 40px'
|
||||||
className="absolute z-50 px-3 py-1 text-white text-xs bg-cyan-500/20 border border-cyan-400/30 rounded-md backdrop-blur-md whitespace-nowrap"
|
}}
|
||||||
style={{
|
/>
|
||||||
top: `${person.position.top}px`,
|
<motion.div
|
||||||
left: `${person.position.left}px`
|
className="absolute inset-0"
|
||||||
}}
|
initial={{ opacity: 0 }}
|
||||||
initial={{ opacity: 0, y: -10 }}
|
animate={{ opacity: 0.5 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
exit={{ opacity: 0, y: -10 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
style={{
|
||||||
{person.name}
|
backgroundImage: `
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px),
|
||||||
|
linear-gradient(0deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '20px 20px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 四角扫描框 */}
|
||||||
|
<div className="absolute inset-4 z-30 pointer-events-none">
|
||||||
|
<motion.div
|
||||||
|
className="w-full h-full"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
{/* 左上角 */}
|
||||||
|
<div className="absolute top-0 left-0 w-8 h-8 border-l-2 border-t-2 border-cyan-400/70" />
|
||||||
|
{/* 右上角 */}
|
||||||
|
<div className="absolute top-0 right-0 w-8 h-8 border-r-2 border-t-2 border-cyan-400/70" />
|
||||||
|
{/* 左下角 */}
|
||||||
|
<div className="absolute bottom-0 left-0 w-8 h-8 border-l-2 border-b-2 border-cyan-400/70" />
|
||||||
|
{/* 右下角 */}
|
||||||
|
<div className="absolute bottom-0 right-0 w-8 h-8 border-r-2 border-b-2 border-cyan-400/70" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
)}
|
||||||
);
|
</AnimatePresence>
|
||||||
})}
|
|
||||||
</AnimatePresence>
|
{/* 人物识别框和浮签 */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{detections.length === 0 && triggerSuccess && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||||
|
<span className="text-white text-sm">No portrait detected</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{detections.map((person, index) => {
|
||||||
|
return (
|
||||||
|
<div key={person.id} className="cursor-pointer" onClick={() => {
|
||||||
|
onPersonClick?.(person);
|
||||||
|
}}>
|
||||||
|
<PersonBox person={person} />
|
||||||
|
<motion.div
|
||||||
|
className="absolute z-50 px-3 py-1 text-white text-xs bg-cyan-500/20 border border-cyan-400/30 rounded-md backdrop-blur-md whitespace-nowrap"
|
||||||
|
style={{
|
||||||
|
top: `${person.position.top}px`,
|
||||||
|
left: `${person.position.left}px`
|
||||||
|
}}
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -10 }}
|
||||||
|
>
|
||||||
|
{person.name}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,29 @@
|
|||||||
import React, { useState, useCallback, useEffect, SetStateAction, forwardRef } from 'react';
|
import React, { useState, useCallback, useEffect, SetStateAction, forwardRef } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { FileText } from 'lucide-react';
|
import { FileText, Undo2, X, TriangleAlert } from 'lucide-react';
|
||||||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||||||
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
||||||
|
import FloatingGlassPanel from './FloatingGlassPanel';
|
||||||
|
|
||||||
|
|
||||||
interface ScriptTabContentProps {
|
interface ScriptTabContentProps {
|
||||||
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
|
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
|
||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
originalText?: string;
|
originalText?: string;
|
||||||
|
onApply: () => void;
|
||||||
|
setActiveTab: (tabId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScriptTabContent = forwardRef<
|
export const ScriptTabContent = forwardRef<
|
||||||
{ checkUpdate: () => boolean },
|
{ switchBefore: (tabId: string) => boolean },
|
||||||
ScriptTabContentProps
|
ScriptTabContentProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { setIsPauseWorkFlow, isPauseWorkFlow, originalText } = props;
|
const { setIsPauseWorkFlow, isPauseWorkFlow, originalText, onApply, setActiveTab } = props;
|
||||||
const { loading, scriptData, setAnyAttribute, applyScript } = useEditData('script', originalText);
|
const { loading, scriptData, setAnyAttribute, applyScript } = useEditData('script', originalText);
|
||||||
|
|
||||||
const [isUpdate, setIsUpdate] = useState(false);
|
const [isUpdate, setIsUpdate] = useState(false);
|
||||||
|
const [isRemindApplyUpdate, setIsRemindApplyUpdate] = useState(false);
|
||||||
|
const [nextToTabId, setNextToTabId] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('contentEditableRef---scriptTabContentIsChange', isUpdate);
|
console.log('contentEditableRef---scriptTabContentIsChange', isUpdate);
|
||||||
@ -26,9 +31,24 @@ export const ScriptTabContent = forwardRef<
|
|||||||
|
|
||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
checkUpdate: () => isUpdate
|
switchBefore: (tabId: string) => {
|
||||||
|
setNextToTabId(tabId);
|
||||||
|
console.log('switchBefore', isUpdate);
|
||||||
|
if (isUpdate) {
|
||||||
|
setIsRemindApplyUpdate(true);
|
||||||
|
}
|
||||||
|
return isUpdate;
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const handleApply = () => {
|
||||||
|
onApply();
|
||||||
|
}
|
||||||
|
const handleCancel = () => {
|
||||||
|
setIsRemindApplyUpdate(false);
|
||||||
|
setActiveTab(nextToTabId);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果loading 显示loading状态
|
// 如果loading 显示loading状态
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -66,6 +86,39 @@ export const ScriptTabContent = forwardRef<
|
|||||||
setIsUpdate={setIsUpdate}
|
setIsUpdate={setIsUpdate}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
<FloatingGlassPanel
|
||||||
|
open={isRemindApplyUpdate}
|
||||||
|
width='500px'
|
||||||
|
clickMaskClose={false}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center gap-4 text-white py-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<TriangleAlert className="w-6 h-6 text-yellow-400" />
|
||||||
|
<p className="text-lg font-medium">剧本内容已修改,是否需要应用?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleApply()}
|
||||||
|
data-alt="confirm-replace-button"
|
||||||
|
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Undo2 className="w-4 h-4" />
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleCancel()}
|
||||||
|
data-alt="ignore-button"
|
||||||
|
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md transition-colors duration-200 flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FloatingGlassPanel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -329,6 +329,7 @@ export function ShotTabContent({
|
|||||||
<PersonDetectionScene
|
<PersonDetectionScene
|
||||||
videoSrc={shotData[selectedIndex]?.videoUrl[0]}
|
videoSrc={shotData[selectedIndex]?.videoUrl[0]}
|
||||||
detections={detections}
|
detections={detections}
|
||||||
|
scanState={scanState}
|
||||||
triggerScan={scanState === 'scanning'}
|
triggerScan={scanState === 'scanning'}
|
||||||
triggerSuccess={scanState === 'detected'}
|
triggerSuccess={scanState === 'detected'}
|
||||||
onScanTimeout={handleScanTimeout}
|
onScanTimeout={handleScanTimeout}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user