import { useEffect, useRef, useState, useCallback } from 'react'; import { useUIKit } from '@tencentcloud/uikit-base-component-react'; import { useChatUIState } from '../../../context/useChatUIState'; import { MessageContentType } from '../../../types/messageInput'; import classes from './AttachmentDrag.module.scss'; import { GlobalDragManager } from './DragManager'; interface AttachmentDragProps { dragAreaSelector?: string; } interface ScrollListener { element: Element; cleanup: () => void; } const useScrollListeners = (element: HTMLElement | null, isDragging: boolean, onUpdate: () => void) => { const scrollListenersRef = useRef([]); const setupScrollListeners = useCallback(() => { if (!element) { return; } // Clean up existing scroll listeners scrollListenersRef.current.forEach(({ cleanup }) => cleanup()); scrollListenersRef.current = []; // Add scroll listener to the element and all its scrollable parents let currentElement: Element | null = element; while (currentElement) { const isScrollable = (el: Element) => { const style = window.getComputedStyle(el); const { overflowY } = style; return overflowY === 'auto' || overflowY === 'scroll' || el === document.documentElement; }; if (isScrollable(currentElement)) { const el = currentElement; const handleScroll = () => { if (isDragging) { onUpdate(); } }; el.addEventListener('scroll', handleScroll); scrollListenersRef.current.push({ element: el, cleanup: () => el.removeEventListener('scroll', handleScroll), }); } currentElement = currentElement.parentElement; } }, [element, isDragging, onUpdate]); useEffect(() => { setupScrollListeners(); return () => { scrollListenersRef.current.forEach(({ cleanup }) => cleanup()); scrollListenersRef.current = []; }; }, [setupScrollListeners]); }; const useDragArea = (dragAreaSelector: string | undefined, isDragging: boolean) => { const [overlayStyle, setOverlayStyle] = useState({}); const targetElementRef = useRef(null); const updateOverlayStyle = useCallback(() => { if (!isDragging) { return; } if (dragAreaSelector && targetElementRef.current) { const rect = targetElementRef.current.getBoundingClientRect(); setOverlayStyle({ position: 'fixed', top: `${rect.top}px`, left: `${rect.left}px`, width: `${rect.width}px`, height: `${rect.height}px`, }); } else { setOverlayStyle({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, }); } }, [dragAreaSelector, isDragging]); useEffect(() => { if (!dragAreaSelector) { return; } const element = document.querySelector(dragAreaSelector); if (!(element instanceof HTMLElement)) { console.warn(`Drag area element not found: ${dragAreaSelector}`); return; } targetElementRef.current = element; // Setup resize observer to handle container size changes const resizeObserver = new ResizeObserver(() => { updateOverlayStyle(); }); resizeObserver.observe(element); // eslint-disable-next-line consistent-return return () => { resizeObserver.disconnect(); }; }, [dragAreaSelector, updateOverlayStyle]); useEffect(() => { updateOverlayStyle(); }, [isDragging, updateOverlayStyle]); return { overlayStyle, targetElementRef, updateOverlayStyle }; }; export const AttachmentDrag = ({ dragAreaSelector }: AttachmentDragProps = {}) => { const { t } = useUIKit(); const { sendInputMessage } = useChatUIState(); const [isDragging, setIsDragging] = useState(false); const dragManager = useRef(GlobalDragManager.getInstance()); const { overlayStyle, targetElementRef, updateOverlayStyle } = useDragArea(dragAreaSelector, isDragging); useScrollListeners(targetElementRef.current, isDragging, updateOverlayStyle); useEffect(() => { const unsubscribeDrag = dragManager.current.subscribe(setIsDragging); const unsubscribeDrop = dragManager.current.onDrop((files) => { const messages = files.map(file => ({ type: MessageContentType.FILE, content: file, })); sendInputMessage(messages); }); if (dragAreaSelector) { dragManager.current.setDragArea(dragAreaSelector); } return () => { unsubscribeDrag(); unsubscribeDrop(); if (dragAreaSelector) { dragManager.current.resetDragArea(); } }; }, [dragAreaSelector]); return (isDragging && (
{t('TUIChat.Drag file here')}
)); };