import React, { useCallback, useMemo, useRef } from 'react'; import { Button, Tooltip } from 'antd'; import { CloseOutlined, CompressOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import { t } from '../i18n'; import DataGrid from './DataGrid'; import { clamp, DEFAULT_DETACHED_WINDOW_MIN_HEIGHT, DEFAULT_DETACHED_WINDOW_MIN_WIDTH, DETACHED_WINDOW_VIEWPORT_PADDING, } from '../utils/detachedWindow'; type DragMode = 'move' | 'resize-e' | 'resize-s' | 'resize-se'; const isAffectedRowsResult = (columns: string[]): boolean => columns.length === 1 && columns[0] === 'affectedRows'; const FloatingQueryResultWindows: React.FC = () => { const theme = useStore((state) => state.theme); const detachedQueryResultWindows = useStore((state) => state.detachedQueryResultWindows); const attachQueryResultWindow = useStore((state) => state.attachQueryResultWindow); const closeDetachedQueryResultWindow = useStore((state) => state.closeDetachedQueryResultWindow); const updateDetachedQueryResultBounds = useStore((state) => state.updateDetachedQueryResultBounds); const focusDetachedQueryResultWindow = useStore((state) => state.focusDetachedQueryResultWindow); const dragRef = useRef<{ id: string; mode: DragMode; startX: number; startY: number; originX: number; originY: number; originW: number; originH: number; } | null>(null); const startInteraction = useCallback(( event: React.PointerEvent, id: string, mode: DragMode, bounds: { x: number; y: number; width: number; height: number }, ) => { if (event.button !== 0) return; event.preventDefault(); event.stopPropagation(); focusDetachedQueryResultWindow(id); dragRef.current = { id, mode, startX: event.clientX, startY: event.clientY, originX: bounds.x, originY: bounds.y, originW: bounds.width, originH: bounds.height, }; const handleMove = (moveEvent: PointerEvent) => { const drag = dragRef.current; if (!drag) return; const dx = moveEvent.clientX - drag.startX; const dy = moveEvent.clientY - drag.startY; if (drag.mode === 'move') { const maxX = Math.max( DETACHED_WINDOW_VIEWPORT_PADDING, window.innerWidth - drag.originW - DETACHED_WINDOW_VIEWPORT_PADDING, ); const maxY = Math.max( DETACHED_WINDOW_VIEWPORT_PADDING, window.innerHeight - drag.originH - DETACHED_WINDOW_VIEWPORT_PADDING, ); updateDetachedQueryResultBounds(drag.id, { x: clamp(drag.originX + dx, DETACHED_WINDOW_VIEWPORT_PADDING, maxX), y: clamp(drag.originY + dy, DETACHED_WINDOW_VIEWPORT_PADDING, maxY), }); return; } let nextW = drag.originW; let nextH = drag.originH; if (drag.mode === 'resize-e' || drag.mode === 'resize-se') { nextW = clamp( drag.originW + dx, DEFAULT_DETACHED_WINDOW_MIN_WIDTH, window.innerWidth - drag.originX - DETACHED_WINDOW_VIEWPORT_PADDING, ); } if (drag.mode === 'resize-s' || drag.mode === 'resize-se') { nextH = clamp( drag.originH + dy, DEFAULT_DETACHED_WINDOW_MIN_HEIGHT, window.innerHeight - drag.originY - DETACHED_WINDOW_VIEWPORT_PADDING, ); } updateDetachedQueryResultBounds(drag.id, { width: nextW, height: nextH }); }; const stop = () => { dragRef.current = null; window.removeEventListener('pointermove', handleMove); window.removeEventListener('pointerup', stop); window.removeEventListener('pointercancel', stop); }; window.addEventListener('pointermove', handleMove); window.addEventListener('pointerup', stop); window.addEventListener('pointercancel', stop); }, [focusDetachedQueryResultWindow, updateDetachedQueryResultBounds]); const handleRestore = useCallback((id: string) => { const restored = attachQueryResultWindow(id); if (!restored) return; window.dispatchEvent(new CustomEvent('gonavi:restore-query-result', { detail: { sourceQueryTabId: restored.sourceQueryTabId, result: restored.result, }, })); }, [attachQueryResultWindow]); const windows = useMemo(() => detachedQueryResultWindows, [detachedQueryResultWindows]); if (windows.length === 0) { return null; } const isDark = theme === 'dark'; return (
{windows.map((windowState) => { const isMessage = windowState.result.resultType === 'message' || isAffectedRowsResult(windowState.result.columns || []); const messageText = (windowState.result.messages || []).join('\n') || (isAffectedRowsResult(windowState.result.columns || []) ? String(windowState.result.rows?.[0]?.affectedRows ?? '') : ''); return (
focusDetachedQueryResultWindow(windowState.id)} >
startInteraction(event, windowState.id, 'move', windowState)} >
{windowState.title}
event.stopPropagation()} style={{ display: 'inline-flex', gap: 4 }}>
{isMessage ? (