- 优化 IodRelevant 组件中的加载状态和计数器动画 - 重构 PlaygroundIod 组件,使用新的 PlaygroundIodProvider 组件 - 改进 Context 提供的数据结构,使用更准确的命名
167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
import React, { createContext, useContext, useMemo, useState } from "react"
|
|
|
|
import { AnimatePresence, motion } from "framer-motion"
|
|
|
|
import { PlaygroundIodRelevant } from "@/components/Common/Playground/IodRelevant.tsx"
|
|
import { PlaygroundData } from "@/components/Common/Playground/Data.tsx"
|
|
import { PlaygroundScene } from "@/components/Common/Playground/Scene.tsx"
|
|
import { PlaygroundTeam } from "@/components/Common/Playground/Team.tsx"
|
|
import { Card } from "antd"
|
|
import { CloseOutlined } from "@ant-design/icons"
|
|
import { useMessageOption } from "@/hooks/useMessageOption.tsx"
|
|
import { Message } from "@/types/message.ts"
|
|
|
|
// 定义 Context 类型
|
|
interface IodPlaygroundContextType {
|
|
showPlayground: boolean
|
|
setShowPlayground: React.Dispatch<React.SetStateAction<boolean>>
|
|
detailHeader: React.ReactNode
|
|
setDetailHeader: React.Dispatch<React.SetStateAction<React.ReactNode>>
|
|
detailMain: React.ReactNode
|
|
setDetailMain: React.Dispatch<React.SetStateAction<React.ReactNode>>
|
|
currentIodMessage: Message | null
|
|
}
|
|
|
|
// 创建 Context
|
|
const PlaygroundContext = createContext<IodPlaygroundContextType | undefined>(
|
|
undefined
|
|
)
|
|
|
|
// 创建自定义 hook 以便子组件使用
|
|
export const useIodPlaygroundContext = () => {
|
|
const context = useContext(PlaygroundContext)
|
|
if (context === undefined) {
|
|
throw new Error(
|
|
"usePlaygroundContext must be used within a PlaygroundProvider"
|
|
)
|
|
}
|
|
return context
|
|
}
|
|
|
|
const PlaygroundIodProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
children
|
|
}) => {
|
|
const { messages, iodLoading, currentMessageId, iodSearch } =
|
|
useMessageOption()
|
|
|
|
const [showPlayground, setShowPlayground] = useState<boolean>(true)
|
|
const [detailHeader, setDetailHeader] = useState(<></>)
|
|
const [detailMain, setDetailMain] = useState(<></>)
|
|
|
|
const currentIodMessage = useMemo<Message | null>(() => {
|
|
if (iodLoading) {
|
|
return null
|
|
}
|
|
|
|
if (messages.length && iodSearch) {
|
|
// 如果不存在currentMessageId默认返回最后一个message
|
|
if (!currentMessageId) {
|
|
return messages.at(-1)
|
|
}
|
|
|
|
const currentMessage = messages?.find(
|
|
(message) => message.id === currentMessageId
|
|
)
|
|
if (currentMessage) {
|
|
return currentMessage
|
|
}
|
|
// 如果当前message不存在最后一个message
|
|
return messages.at(-1)
|
|
}
|
|
|
|
return null
|
|
}, [currentMessageId, messages, iodLoading, iodSearch])
|
|
|
|
return (
|
|
<PlaygroundContext.Provider
|
|
value={{
|
|
currentIodMessage,
|
|
showPlayground,
|
|
setShowPlayground,
|
|
detailMain,
|
|
setDetailMain,
|
|
detailHeader,
|
|
setDetailHeader
|
|
}}>
|
|
{children}
|
|
</PlaygroundContext.Provider>
|
|
)
|
|
}
|
|
|
|
// 子组件使用修改card的默认样式
|
|
const classNames =
|
|
"h-full [&_.ant-card-body]:h-full [&_.ant-card-body]:!p-[20px] overflow-y-hidden !bg-[rgba(240,245,255,0.3)] backdrop-blur-sm border border-white/30 shadow-xl rounded-2xl"
|
|
// 将原来的返回内容移到这个组件中
|
|
const PlaygroundContent = () => {
|
|
const { showPlayground, detailMain, detailHeader, setShowPlayground } =
|
|
useIodPlaygroundContext()
|
|
|
|
return (
|
|
<AnimatePresence mode="popLayout">
|
|
{showPlayground ? (
|
|
<motion.div
|
|
key="playground"
|
|
initial={{ x: "100%" }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: "100%" }}
|
|
transition={{
|
|
duration: 0.6,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="h-full grid grid-rows-12 gap-3">
|
|
<div className="w-full row-span-5">
|
|
<PlaygroundIodRelevant
|
|
className={classNames
|
|
.replace("!bg-[rgba(240,245,255,0.3)]", "")
|
|
.replace("shadow-xl", "")}
|
|
/>
|
|
</div>
|
|
<div className="w-full row-span-4 grid grid-cols-2 gap-3 custom-scrollbar">
|
|
<PlaygroundData className={classNames} />
|
|
<PlaygroundScene className={classNames} />
|
|
</div>
|
|
<div className="w-full row-span-3 pb-3">
|
|
<PlaygroundTeam className={classNames} />
|
|
</div>
|
|
</motion.div>
|
|
) : (
|
|
<motion.div
|
|
key="alternative"
|
|
initial={{ x: "100%" }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: "100%" }}
|
|
transition={{
|
|
duration: 0.6,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="h-full pb-5">
|
|
<Card className="h-full shadow-xl shadow-gray-500/20 [&_.ant-card-body]:h-full">
|
|
<div className="flex flex-col h-full">
|
|
<div className="pb-6 flex items-center justify-between">
|
|
<div>{detailHeader}</div>
|
|
<CloseOutlined
|
|
size={30}
|
|
className="hover:text-red-500 cursor-pointer transition-colors duration-200 text-xl"
|
|
onClick={() => setShowPlayground(true)}
|
|
/>
|
|
</div>
|
|
{detailMain}
|
|
</div>
|
|
</Card>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
)
|
|
}
|
|
|
|
|
|
export const PlaygroundIod = () => {
|
|
return (
|
|
<div className="w-[36%] h-full pt-16 pr-5 pb-0">
|
|
<PlaygroundIodProvider>
|
|
<PlaygroundContent />
|
|
</PlaygroundIodProvider>
|
|
</div>
|
|
)
|
|
}
|