From 2f0de106d7ef1dfff22a34b6590f25e3080f33c4 Mon Sep 17 00:00:00 2001 From: itsllyaz Date: Wed, 4 Nov 2026 11:02:35 +0300 Subject: [PATCH] feat: add splitter and minimize/maximize for query editor --- ui/src/components/editor.tsx | 9 +++- ui/src/routes/query.tsx | 87 +++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/ui/src/components/editor.tsx b/ui/src/components/editor.tsx index 5d67e81..ed2d189 100644 --- a/ui/src/components/editor.tsx +++ b/ui/src/components/editor.tsx @@ -27,9 +27,14 @@ loader.config({ monaco }); type Props = { value: string; onChange?: (value: string) => void; + height?: number; }; -export const Editor: FunctionComponent = ({ value, onChange }) => { +export const Editor: FunctionComponent = ({ + value, + onChange, + height = 200, +}) => { const currentTheme = useTheme(); const monacoInstance = useMonaco(); const providerRef = useRef(null); @@ -141,7 +146,7 @@ export const Editor: FunctionComponent = ({ value, onChange }) => { return ( onChange?.(val ?? "")} language={ID_LANGUAGE_SQL} diff --git a/ui/src/routes/query.tsx b/ui/src/routes/query.tsx index 50feb95..ca01943 100644 --- a/ui/src/routes/query.tsx +++ b/ui/src/routes/query.tsx @@ -1,11 +1,11 @@ import "react-data-grid/lib/styles.css"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { z } from "zod"; import { DataGrid } from "react-data-grid"; import { useQuery } from "@tanstack/react-query"; import { useDebounce } from "@uidotdev/usehooks"; -import { Database, Play, ShieldX, Terminal } from "lucide-react"; +import { Database, Maximize2, Play, ShieldX, Terminal } from "lucide-react"; import { createFileRoute } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; @@ -24,6 +24,10 @@ import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { useTheme } from "@/provider/theme.provider"; +const DEFAULT_EDITOR_HEIGHT = 200; +const MIN_EDITOR_HEIGHT = 120; +const MAX_EDITOR_HEIGHT = Math.min(600, Math.floor(window.innerHeight * 0.6)); + export const Route = createFileRoute("/query")({ component: Query, validateSearch: z.object({ sql: z.string().optional() }), @@ -37,6 +41,15 @@ function Query() { const code = useDebounce(codeState, 100); const [autoExecute, setAutoExecute] = useState(true); + const [editorHeight, setEditorHeight] = useState(DEFAULT_EDITOR_HEIGHT); + const [editorMinimized, setEditorMinimized] = useState(false); + + const isResizingRef = useRef(false); + const resizeStartYRef = useRef(0); + const resizeStartHeightRef = useRef(DEFAULT_EDITOR_HEIGHT); + + const clampEditorHeight = (height: number) => + Math.min(Math.max(height, MIN_EDITOR_HEIGHT), MAX_EDITOR_HEIGHT); const { data, error, refetch } = useQuery({ queryKey: ["query", code], @@ -45,6 +58,39 @@ function Query() { retry: false, }); + const handleDividerPointerDown = ( + event: React.PointerEvent, + ) => { + event.preventDefault(); + event.currentTarget.setPointerCapture(event.pointerId); + isResizingRef.current = true; + resizeStartYRef.current = event.clientY; + resizeStartHeightRef.current = editorHeight; + }; + + const handleDividerPointerMove = ( + event: React.PointerEvent, + ) => { + if (!isResizingRef.current) return; + + const nextHeight = + resizeStartHeightRef.current + (event.clientY - resizeStartYRef.current); + + setEditorHeight(clampEditorHeight(nextHeight)); + }; + + const handleDividerPointerUp = () => { + isResizingRef.current = false; + }; + + const handleDividerKeyDown = (event: React.KeyboardEvent) => { + if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return; + + event.preventDefault(); + const delta = event.key === "ArrowDown" ? 10 : -10; + setEditorHeight((height) => clampEditorHeight(height + delta)); + }; + const grid = !data ? ( !autoExecute && code && error ? ( @@ -102,10 +148,39 @@ function Query() { return (
- setCodeState({ type: "SET_SQL", data: val })} - /> + {editorMinimized ? ( + + ) : ( + setCodeState({ type: "SET_SQL", data: val })} + height={editorHeight} + /> + )} + + {!editorMinimized && ( +
+
+
+ )}