From 6b507425aa513e578ad3e51f9302208d4551b08d Mon Sep 17 00:00:00 2001 From: eshmeshek Date: Tue, 27 Jan 2026 23:23:44 +0300 Subject: [PATCH] modified: frontend/src/pages/SqlInterface.tsx --- frontend/src/pages/SqlInterface.tsx | 136 +++++++++++++++++++--------- 1 file changed, 94 insertions(+), 42 deletions(-) diff --git a/frontend/src/pages/SqlInterface.tsx b/frontend/src/pages/SqlInterface.tsx index 30a7140..e891eb3 100644 --- a/frontend/src/pages/SqlInterface.tsx +++ b/frontend/src/pages/SqlInterface.tsx @@ -1,6 +1,6 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { Play, Plus, X, Database as DatabaseIcon, Clock, CheckCircle, XCircle, Loader2 } from 'lucide-react'; +import { Play, Plus, X, Database as DatabaseIcon, Clock, CheckCircle, XCircle, Loader2, GripHorizontal } from 'lucide-react'; import { databasesApi, sqlInterfaceApi, SqlQueryResult } from '@/services/api'; import { Database } from '@/types'; import SqlEditor from '@/components/SqlEditor'; @@ -17,6 +17,7 @@ interface SqlTab { interface SqlInterfaceState { tabs: SqlTab[]; activeTabId: string; + splitPosition: number; // percentage } const STORAGE_KEY = 'sql_interface_state'; @@ -46,7 +47,6 @@ const loadState = (): SqlInterfaceState | null => { const saveState = (state: SqlInterfaceState) => { try { - // Don't save isExecuting state and large results const stateToSave = { ...state, tabs: state.tabs.map(tab => ({ @@ -54,7 +54,7 @@ const saveState = (state: SqlInterfaceState) => { isExecuting: false, result: tab.result ? { ...tab.result, - data: tab.result.data?.slice(0, 100), // Limit saved results + data: tab.result.data?.slice(0, 100), } : null, })), }; @@ -69,7 +69,6 @@ export default function SqlInterface() { queryKey: ['databases'], queryFn: async () => { const { data } = await databasesApi.getAll(); - // Filter only SQL databases (not AQL) return data.filter((db: Database) => db.type !== 'aql'); }, }); @@ -77,21 +76,26 @@ export default function SqlInterface() { const [state, setState] = useState(() => { const saved = loadState(); if (saved && saved.tabs.length > 0) { - return saved; + return { + ...saved, + splitPosition: saved.splitPosition || 50, + }; } const initialTab = createNewTab(); return { tabs: [initialTab], activeTabId: initialTab.id, + splitPosition: 50, }; }); - // Save state to localStorage on changes + const containerRef = useRef(null); + const isDragging = useRef(false); + useEffect(() => { saveState(state); }, [state]); - // Set default database for new tabs when databases load useEffect(() => { if (databases.length > 0) { setState(prev => ({ @@ -118,6 +122,7 @@ export default function SqlInterface() { const defaultDbId = databases.length > 0 ? databases[0].id : ''; const newTab = createNewTab(defaultDbId, `Запрос ${state.tabs.length + 1}`); setState(prev => ({ + ...prev, tabs: [...prev.tabs, newTab], activeTabId: newTab.id, })); @@ -127,10 +132,10 @@ export default function SqlInterface() { e.stopPropagation(); setState(prev => { if (prev.tabs.length === 1) { - // Don't close the last tab, just reset it const defaultDbId = databases.length > 0 ? databases[0].id : ''; const newTab = createNewTab(defaultDbId); return { + ...prev, tabs: [newTab], activeTabId: newTab.id, }; @@ -142,6 +147,7 @@ export default function SqlInterface() { : prev.activeTabId; return { + ...prev, tabs: newTabs, activeTabId: newActiveId, }; @@ -175,7 +181,6 @@ export default function SqlInterface() { } }, [activeTab, updateTab]); - // Keyboard shortcut for execute (Ctrl+Enter or Cmd+Enter) useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { @@ -188,6 +193,44 @@ export default function SqlInterface() { return () => window.removeEventListener('keydown', handleKeyDown); }, [executeQuery]); + // Drag to resize + const handleMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + isDragging.current = true; + document.body.style.cursor = 'row-resize'; + document.body.style.userSelect = 'none'; + }, []); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging.current || !containerRef.current) return; + + const container = containerRef.current; + const rect = container.getBoundingClientRect(); + const y = e.clientY - rect.top; + const percentage = (y / rect.height) * 100; + const clamped = Math.min(Math.max(percentage, 20), 80); + + setState(prev => ({ ...prev, splitPosition: clamped })); + }; + + const handleMouseUp = () => { + if (isDragging.current) { + isDragging.current = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + } + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, []); + if (isLoadingDatabases) { return (
@@ -199,34 +242,32 @@ export default function SqlInterface() { return (
{/* Tabs bar */} -
-
- {state.tabs.map(tab => ( +
+ {state.tabs.map(tab => ( + + - ))} -
+ + ))}
{/* Toolbar */} -
- {/* Database selector */} +
- {/* Main content area */} -
+ {/* Main content area with resizable split */} +
{/* SQL Editor */} -
+
updateTab(activeTab.id, { query: value })} @@ -289,11 +330,22 @@ export default function SqlInterface() { />
+ {/* Resize handle */} +
+ +
+ {/* Results panel */} -
+
{/* Result header */} {activeTab?.result && ( -
+
{activeTab.result.success ? ( <>