mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
fix(chat): portalize standalone jump calendar to avoid translucent compositing
This commit is contained in:
@@ -6,6 +6,8 @@ interface JumpToDatePopoverProps {
|
|||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSelect: (date: Date) => void
|
onSelect: (date: Date) => void
|
||||||
|
className?: string
|
||||||
|
style?: React.CSSProperties
|
||||||
currentDate?: Date
|
currentDate?: Date
|
||||||
messageDates?: Set<string>
|
messageDates?: Set<string>
|
||||||
hasLoadedMessageDates?: boolean
|
hasLoadedMessageDates?: boolean
|
||||||
@@ -18,6 +20,8 @@ const JumpToDatePopover: React.FC<JumpToDatePopoverProps> = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
currentDate = new Date(),
|
currentDate = new Date(),
|
||||||
messageDates,
|
messageDates,
|
||||||
hasLoadedMessageDates = false,
|
hasLoadedMessageDates = false,
|
||||||
@@ -107,9 +111,10 @@ const JumpToDatePopover: React.FC<JumpToDatePopoverProps> = ({
|
|||||||
|
|
||||||
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
||||||
const days = generateCalendar()
|
const days = generateCalendar()
|
||||||
|
const mergedClassName = ['jump-date-popover', className || ''].join(' ').trim()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="jump-date-popover" role="dialog" aria-label="跳转日期">
|
<div className={mergedClassName} style={style} role="dialog" aria-label="跳转日期">
|
||||||
<div className="calendar-nav">
|
<div className="calendar-nav">
|
||||||
<button
|
<button
|
||||||
className="nav-btn"
|
className="nav-btn"
|
||||||
|
|||||||
@@ -453,11 +453,13 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
const initialRevealTimerRef = useRef<number | null>(null)
|
const initialRevealTimerRef = useRef<number | null>(null)
|
||||||
const sessionListRef = useRef<HTMLDivElement>(null)
|
const sessionListRef = useRef<HTMLDivElement>(null)
|
||||||
const jumpCalendarWrapRef = useRef<HTMLDivElement>(null)
|
const jumpCalendarWrapRef = useRef<HTMLDivElement>(null)
|
||||||
|
const jumpPopoverPortalRef = useRef<HTMLDivElement>(null)
|
||||||
const [currentOffset, setCurrentOffset] = useState(0)
|
const [currentOffset, setCurrentOffset] = useState(0)
|
||||||
const [jumpStartTime, setJumpStartTime] = useState(0)
|
const [jumpStartTime, setJumpStartTime] = useState(0)
|
||||||
const [jumpEndTime, setJumpEndTime] = useState(0)
|
const [jumpEndTime, setJumpEndTime] = useState(0)
|
||||||
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
const [showJumpPopover, setShowJumpPopover] = useState(false)
|
||||||
const [jumpPopoverDate, setJumpPopoverDate] = useState<Date>(new Date())
|
const [jumpPopoverDate, setJumpPopoverDate] = useState<Date>(new Date())
|
||||||
|
const [jumpPopoverPosition, setJumpPopoverPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 })
|
||||||
const isDateJumpRef = useRef(false)
|
const isDateJumpRef = useRef(false)
|
||||||
const [messageDates, setMessageDates] = useState<Set<string>>(new Set())
|
const [messageDates, setMessageDates] = useState<Set<string>>(new Set())
|
||||||
const [hasLoadedMessageDates, setHasLoadedMessageDates] = useState(false)
|
const [hasLoadedMessageDates, setHasLoadedMessageDates] = useState(false)
|
||||||
@@ -669,6 +671,31 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const updateJumpPopoverPosition = useCallback(() => {
|
||||||
|
if (!standaloneSessionWindow) return
|
||||||
|
const anchor = jumpCalendarWrapRef.current
|
||||||
|
if (!anchor) return
|
||||||
|
|
||||||
|
const popoverWidth = 312
|
||||||
|
const viewportGap = 8
|
||||||
|
const anchorRect = anchor.getBoundingClientRect()
|
||||||
|
|
||||||
|
let left = anchorRect.right - popoverWidth
|
||||||
|
left = Math.max(viewportGap, Math.min(left, window.innerWidth - popoverWidth - viewportGap))
|
||||||
|
|
||||||
|
const portalHeight = jumpPopoverPortalRef.current?.offsetHeight || 0
|
||||||
|
const belowTop = anchorRect.bottom + 10
|
||||||
|
let top = belowTop
|
||||||
|
if (portalHeight > 0 && belowTop + portalHeight > window.innerHeight - viewportGap) {
|
||||||
|
top = Math.max(viewportGap, anchorRect.top - portalHeight - 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
setJumpPopoverPosition(prev => {
|
||||||
|
if (prev.top === top && prev.left === left) return prev
|
||||||
|
return { top, left }
|
||||||
|
})
|
||||||
|
}, [standaloneSessionWindow])
|
||||||
|
|
||||||
const handleToggleJumpPopover = useCallback(() => {
|
const handleToggleJumpPopover = useCallback(() => {
|
||||||
if (!currentSessionId) return
|
if (!currentSessionId) return
|
||||||
if (showJumpPopover) {
|
if (showJumpPopover) {
|
||||||
@@ -676,9 +703,15 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setJumpPopoverDate(resolveCurrentViewDate())
|
setJumpPopoverDate(resolveCurrentViewDate())
|
||||||
|
if (standaloneSessionWindow) {
|
||||||
|
updateJumpPopoverPosition()
|
||||||
|
}
|
||||||
setShowJumpPopover(true)
|
setShowJumpPopover(true)
|
||||||
|
if (standaloneSessionWindow) {
|
||||||
|
requestAnimationFrame(() => updateJumpPopoverPosition())
|
||||||
|
}
|
||||||
void loadJumpCalendarData(currentSessionId)
|
void loadJumpCalendarData(currentSessionId)
|
||||||
}, [currentSessionId, loadJumpCalendarData, resolveCurrentViewDate, showJumpPopover])
|
}, [currentSessionId, loadJumpCalendarData, resolveCurrentViewDate, showJumpPopover, standaloneSessionWindow, updateJumpPopoverPosition])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = onExportSessionStatus((payload) => {
|
const unsubscribe = onExportSessionStatus((payload) => {
|
||||||
@@ -2742,13 +2775,29 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
const target = event.target as Node | null
|
const target = event.target as Node | null
|
||||||
if (!target) return
|
if (!target) return
|
||||||
if (jumpCalendarWrapRef.current?.contains(target)) return
|
if (jumpCalendarWrapRef.current?.contains(target)) return
|
||||||
|
if (standaloneSessionWindow && jumpPopoverPortalRef.current?.contains(target)) return
|
||||||
setShowJumpPopover(false)
|
setShowJumpPopover(false)
|
||||||
}
|
}
|
||||||
document.addEventListener('mousedown', handleGlobalPointerDown)
|
document.addEventListener('mousedown', handleGlobalPointerDown)
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleGlobalPointerDown)
|
document.removeEventListener('mousedown', handleGlobalPointerDown)
|
||||||
}
|
}
|
||||||
}, [showJumpPopover])
|
}, [showJumpPopover, standaloneSessionWindow])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showJumpPopover || !standaloneSessionWindow) return
|
||||||
|
const syncPosition = () => {
|
||||||
|
requestAnimationFrame(() => updateJumpPopoverPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
syncPosition()
|
||||||
|
window.addEventListener('resize', syncPosition)
|
||||||
|
window.addEventListener('scroll', syncPosition, true)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', syncPosition)
|
||||||
|
window.removeEventListener('scroll', syncPosition, true)
|
||||||
|
}
|
||||||
|
}, [showJumpPopover, standaloneSessionWindow, updateJumpPopoverPosition])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowJumpPopover(false)
|
setShowJumpPopover(false)
|
||||||
@@ -3785,6 +3834,7 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
>
|
>
|
||||||
<Calendar size={18} />
|
<Calendar size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
{!standaloneSessionWindow && (
|
||||||
<JumpToDatePopover
|
<JumpToDatePopover
|
||||||
isOpen={showJumpPopover}
|
isOpen={showJumpPopover}
|
||||||
currentDate={jumpPopoverDate}
|
currentDate={jumpPopoverDate}
|
||||||
@@ -3796,7 +3846,33 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
loadingDates={loadingDates}
|
loadingDates={loadingDates}
|
||||||
loadingDateCounts={loadingDateCounts}
|
loadingDateCounts={loadingDateCounts}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{standaloneSessionWindow && showJumpPopover && createPortal(
|
||||||
|
<div
|
||||||
|
ref={jumpPopoverPortalRef}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: jumpPopoverPosition.top,
|
||||||
|
left: jumpPopoverPosition.left,
|
||||||
|
zIndex: 3600
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<JumpToDatePopover
|
||||||
|
isOpen={showJumpPopover}
|
||||||
|
currentDate={jumpPopoverDate}
|
||||||
|
onClose={() => setShowJumpPopover(false)}
|
||||||
|
onSelect={handleJumpDateSelect}
|
||||||
|
messageDates={messageDates}
|
||||||
|
hasLoadedMessageDates={hasLoadedMessageDates}
|
||||||
|
messageDateCounts={messageDateCounts}
|
||||||
|
loadingDates={loadingDates}
|
||||||
|
loadingDateCounts={loadingDateCounts}
|
||||||
|
style={{ position: 'static', top: 'auto', right: 'auto' }}
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
className="icon-btn refresh-messages-btn"
|
className="icon-btn refresh-messages-btn"
|
||||||
onClick={handleRefreshMessages}
|
onClick={handleRefreshMessages}
|
||||||
|
|||||||
Reference in New Issue
Block a user