优化角色列表弹出

This commit is contained in:
北枳 2025-08-18 21:42:20 +08:00
parent 541568f0f5
commit 5e2fd1fff9
3 changed files with 60 additions and 52 deletions

View File

@ -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

View File

@ -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: []
}]
});

View File

@ -342,16 +342,20 @@ 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 && (
<AnimatePresence mode="wait">
{shotData[selectedIndex]?.status === 1 && shotData[selectedIndex]?.videoUrl.length && (
<motion.div
className="aspect-video rounded-lg overflow-hidden relative group"
layoutId={`video-preview-${selectedIndex}`}
key={`video-preview-${selectedIndex}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<PersonDetectionScene
videoSrc={shotData[selectedIndex]?.videoUrl[0].video_url}
@ -387,10 +391,11 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
</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>
)}
</>