forked from 77media/video-flow
优化角色列表弹出
This commit is contained in:
parent
541568f0f5
commit
5e2fd1fff9
@ -11,7 +11,7 @@ interface CharacterTokenOptions {
|
||||
|
||||
export function CharacterToken(props: ReactNodeViewProps) {
|
||||
const [showRoleList, setShowRoleList] = useState(false);
|
||||
const [listPosition, setListPosition] = useState({ top: 0, left: 0 });
|
||||
const [listPosition, setListPosition] = useState({ top: 0, left: 0, bottom: 0 });
|
||||
const { name } = props.node.attrs as ScriptRoleEntity;
|
||||
const extension = props.extension as Node<CharacterTokenOptions>;
|
||||
const roles = extension.options.roles || [];
|
||||
@ -31,6 +31,7 @@ export function CharacterToken(props: ReactNodeViewProps) {
|
||||
// 计算理想的顶部位置(在token下方)
|
||||
let top = tokenRect.bottom + 8; // 8px 间距
|
||||
let left = tokenRect.left;
|
||||
let bottom = tokenRect.top;
|
||||
|
||||
// 检查是否超出底部
|
||||
if (top + listRect.height > viewportHeight) {
|
||||
@ -44,10 +45,18 @@ export function CharacterToken(props: ReactNodeViewProps) {
|
||||
left = viewportWidth - listRect.width - 8;
|
||||
}
|
||||
|
||||
// 检查是否超出顶部
|
||||
if (bottom - listRect.height < 0) {
|
||||
// 如果超出顶部,将列表显示在token下方
|
||||
bottom = tokenRect.bottom + 8;
|
||||
}
|
||||
|
||||
// 确保不会超出左侧
|
||||
left = Math.max(8, left);
|
||||
// 确保不会超顶部
|
||||
top = Math.max(0, top);
|
||||
|
||||
setListPosition({ top, left });
|
||||
setListPosition({ top, left, bottom });
|
||||
};
|
||||
|
||||
// 监听窗口大小变化
|
||||
@ -107,7 +116,7 @@ export function CharacterToken(props: ReactNodeViewProps) {
|
||||
exit={{ opacity: 0, y: 4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
ref={listRef}
|
||||
className="fixed w-64 rounded-lg backdrop-blur-md bg-white/10 border border-white/20 p-2 z-[51]"
|
||||
className="fixed w-64 rounded-lg backdrop-blur-md bg-white/10 border border-white/20 p-2 z-[51] overflow-y-auto"
|
||||
style={{
|
||||
top: listPosition.top,
|
||||
left: listPosition.left
|
||||
|
||||
@ -26,17 +26,11 @@ const createEmptyShot = (): Shot => ({
|
||||
name: `shot${Date.now()}`,
|
||||
shotDescContent: [{
|
||||
type: 'paragraph',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Add shot description here...'
|
||||
}]
|
||||
content: []
|
||||
}],
|
||||
shotDialogsContent: [{
|
||||
type: 'paragraph',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: 'Add shot dialogue here...'
|
||||
}]
|
||||
content: []
|
||||
}]
|
||||
});
|
||||
|
||||
|
||||
@ -342,55 +342,60 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
|
||||
<div className="space-y-4 col-span-1">
|
||||
{/* 选中的视频预览 */}
|
||||
<>
|
||||
{shotData[selectedIndex]?.status === 0 && (
|
||||
{(shotData[selectedIndex]?.status === 0) && (
|
||||
<div className="w-full h-full flex items-center gap-1 justify-center rounded-lg bg-black/30">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
|
||||
<span className="text-white/50">Loading...</span>
|
||||
</div>
|
||||
)}
|
||||
{shotData[selectedIndex]?.status === 1 && (
|
||||
<motion.div
|
||||
className="aspect-video rounded-lg overflow-hidden relative group"
|
||||
layoutId={`video-preview-${selectedIndex}`}
|
||||
>
|
||||
<PersonDetectionScene
|
||||
videoSrc={shotData[selectedIndex]?.videoUrl[0].video_url}
|
||||
detections={detections}
|
||||
scanState={scanState}
|
||||
triggerScan={scanState === 'scanning'}
|
||||
triggerSuccess={scanState === 'detected'}
|
||||
onScanTimeout={handleScanTimeout}
|
||||
onScanExit={handleScanExit}
|
||||
onDetectionsChange={handleDetectionsChange}
|
||||
onPersonClick={handlePersonClick}
|
||||
/>
|
||||
<motion.div className='absolute top-4 right-4 flex gap-2'>
|
||||
{/* 人物替换按钮 */}
|
||||
<motion.button
|
||||
onClick={() => handleScan()}
|
||||
className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full
|
||||
${scanState === 'detected'
|
||||
? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white'
|
||||
: 'bg-black/50 hover:bg-black/70 text-white'
|
||||
}`}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{scanState === 'scanning' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : scanState === 'detected' ? (
|
||||
<X className="w-4 h-4" />
|
||||
) : (
|
||||
<User className="w-4 h-4" />
|
||||
)}
|
||||
</motion.button>
|
||||
<AnimatePresence mode="wait">
|
||||
{shotData[selectedIndex]?.status === 1 && shotData[selectedIndex]?.videoUrl.length && (
|
||||
<motion.div
|
||||
className="aspect-video rounded-lg overflow-hidden relative group"
|
||||
key={`video-preview-${selectedIndex}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<PersonDetectionScene
|
||||
videoSrc={shotData[selectedIndex]?.videoUrl[0].video_url}
|
||||
detections={detections}
|
||||
scanState={scanState}
|
||||
triggerScan={scanState === 'scanning'}
|
||||
triggerSuccess={scanState === 'detected'}
|
||||
onScanTimeout={handleScanTimeout}
|
||||
onScanExit={handleScanExit}
|
||||
onDetectionsChange={handleDetectionsChange}
|
||||
onPersonClick={handlePersonClick}
|
||||
/>
|
||||
<motion.div className='absolute top-4 right-4 flex gap-2'>
|
||||
{/* 人物替换按钮 */}
|
||||
<motion.button
|
||||
onClick={() => handleScan()}
|
||||
className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full
|
||||
${scanState === 'detected'
|
||||
? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white'
|
||||
: 'bg-black/50 hover:bg-black/70 text-white'
|
||||
}`}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{scanState === 'scanning' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : scanState === 'detected' ? (
|
||||
<X className="w-4 h-4" />
|
||||
) : (
|
||||
<User className="w-4 h-4" />
|
||||
)}
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
{shotData[selectedIndex]?.status === 2 && (
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{(shotData[selectedIndex]?.status === 2 || !shotData[selectedIndex]?.videoUrl.length) && (
|
||||
<div className="w-full h-full flex gap-1 items-center justify-center rounded-lg bg-red-500/10">
|
||||
<CircleX className="w-4 h-4 text-red-500" />
|
||||
<span className="text-white/50">任务失败,点击重新生成</span>
|
||||
<span className="text-white/50">Failed, click to regenerate</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user