({ sx })
| 2847 | } |
| 2848 | |
| 2849 | export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { |
| 2850 | const { t } = useTranslation(); |
| 2851 | const dispatch = useDispatch<AppDispatch>(); |
| 2852 | |
| 2853 | let tables = useSelector((state: DataFormulatorState) => state.tables); |
| 2854 | let focusedId = useSelector((state: DataFormulatorState) => state.focusedId); |
| 2855 | let charts = useSelector(dfSelectors.getAllCharts); |
| 2856 | |
| 2857 | let generatedReports = useSelector(dfSelectors.getAllGeneratedReports); |
| 2858 | |
| 2859 | // Derive focusedTableId from focusedId for scroll/highlight logic |
| 2860 | let focusedTableId = useMemo(() => { |
| 2861 | if (!focusedId) return undefined; |
| 2862 | if (focusedId.type === 'table') return focusedId.tableId; |
| 2863 | if (focusedId.type === 'chart') { |
| 2864 | const chart = charts.find(c => c.id === focusedId.chartId); |
| 2865 | return chart?.tableRef; |
| 2866 | } |
| 2867 | if (focusedId.type === 'report') { |
| 2868 | const report = generatedReports.find(r => r.id === focusedId.reportId); |
| 2869 | return report?.triggerTableId; |
| 2870 | } |
| 2871 | return undefined; |
| 2872 | }, [focusedId, charts, generatedReports]); |
| 2873 | |
| 2874 | let chartSynthesisInProgress = useSelector((state: DataFormulatorState) => state.chartSynthesisInProgress); |
| 2875 | |
| 2876 | const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); |
| 2877 | |
| 2878 | // Subscribe to draftNodes so the scroll-to-target effect re-runs when an |
| 2879 | // active clarify/explain entry appears or resolves. |
| 2880 | const draftNodes = useSelector((state: DataFormulatorState) => state.draftNodes); |
| 2881 | |
| 2882 | const containerRef = useRef<null | HTMLDivElement>(null) |
| 2883 | // Outer wrapper containing both the thread area and the chatbox. |
| 2884 | // Its height is governed by the parent Allotment pane and stays constant |
| 2885 | // when the chatbox grows/shrinks during clarification or explanation. |
| 2886 | // Used to derive a stable viewportHeight for split decisions so that |
| 2887 | // chatbox resizing doesn't trigger re-splitting of long threads. |
| 2888 | const outerRef = useRef<null | HTMLDivElement>(null) |
| 2889 | const [expandedColumns, setExpandedColumns] = useState(false); |
| 2890 | const [containerWidth, setContainerWidth] = useState(0); |
| 2891 | // Track container height so we can detect when the chatbox grows/shrinks |
| 2892 | // (which compresses/expands containerRef as a flex sibling). Used to |
| 2893 | // trigger the scroll-to-target effect below. |
| 2894 | const [containerHeight, setContainerHeight] = useState(0); |
| 2895 | // Increments every time the chat input is focused. Used to retrigger the |
| 2896 | // scroll-to-target effect even when neither focusedId nor containerHeight |
| 2897 | // changes (e.g. user just clicks into the input without typing). |
| 2898 | const [chatboxFocusTick, setChatboxFocusTick] = useState(0); |
| 2899 | const [isDragOver, setIsDragOver] = useState(false); |
| 2900 | |
| 2901 | // ── Drop handler for catalog table items from DataSourceSidebar ────── |
| 2902 | const handleDragOver = useCallback((e: React.DragEvent) => { |
| 2903 | if (e.dataTransfer.types.includes('application/json')) { |
| 2904 | e.preventDefault(); |
| 2905 | e.dataTransfer.dropEffect = 'copy'; |
| 2906 | setIsDragOver(true); |
nothing calls this directly
no test coverage detected