import React, { useState, useEffect, useRef, useCallback } from 'react'; import find from 'lodash/find'; import toast from 'react-hot-toast'; import { useSelector, useDispatch } from 'react-redux'; import GraphQLRequestPane from 'components/RequestPane/GraphQLRequestPane'; import HttpRequestPane from 'components/RequestPane/HttpRequestPane'; import GrpcRequestPane from 'components/RequestPane/GrpcRequestPane/index'; import ResponsePane from 'components/ResponsePane'; import GrpcResponsePane from 'components/ResponsePane/GrpcResponsePane'; import { findItemInCollection } from 'utils/collections'; import { cancelRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import RequestNotFound from './RequestNotFound'; import QueryUrl from 'components/RequestPane/QueryUrl/index'; import GrpcQueryUrl from 'components/RequestPane/GrpcQueryUrl/index'; import NetworkError from 'components/ResponsePane/NetworkError'; import RunnerResults from 'components/RunnerResults'; import VariablesEditor from 'components/VariablesEditor'; import CollectionSettings from 'components/CollectionSettings'; import { DocExplorer } from '@usebruno/graphql-docs'; import StyledWrapper from './StyledWrapper'; import FolderSettings from 'components/FolderSettings'; import { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index'; import { produce } from 'immer'; import CollectionOverview from 'components/CollectionSettings/Overview'; import RequestNotLoaded from './RequestNotLoaded'; import RequestIsLoading from './RequestIsLoading'; import FolderNotFound from './FolderNotFound'; import ExampleNotFound from './ExampleNotFound'; import WsQueryUrl from 'components/RequestPane/WsQueryUrl'; import WSRequestPane from 'components/RequestPane/WSRequestPane'; import WSResponsePane from 'components/ResponsePane/WsResponsePane'; import { useTabPaneBoundaries } from 'hooks/useTabPaneBoundaries/index'; import ResponseExample from 'components/ResponseExample'; import WorkspaceOverview from 'components/WorkspaceHome/WorkspaceOverview'; import Preferences from 'components/Preferences'; import EnvironmentSettings from 'components/Environments/EnvironmentSettings'; import GlobalEnvironmentSettings from 'components/Environments/GlobalEnvironmentSettings'; const MIN_LEFT_PANE_WIDTH = 300; const MIN_RIGHT_PANE_WIDTH = 490; const MIN_TOP_PANE_HEIGHT = 150; const MIN_BOTTOM_PANE_HEIGHT = 150; const RequestTabPanel = () => { const dispatch = useDispatch(); const tabs = useSelector((state) => state.tabs.tabs); const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments); const _collections = useSelector((state) => state.collections.collections); const preferences = useSelector((state) => state.app.preferences); const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces); const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid); const isVerticalLayout = preferences?.layout?.responsePaneOrientation === 'vertical'; const isConsoleOpen = useSelector((state) => state.logs.isConsoleOpen); // Use ref to avoid stale closure in event handlers const isVerticalLayoutRef = useRef(isVerticalLayout); useEffect(() => { isVerticalLayoutRef.current = isVerticalLayout; }, [isVerticalLayout]); // merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object const collections = produce(_collections, (draft) => { const collection = find(draft, (c) => c.uid === focusedTab?.collectionUid); if (collection) { // add selected global env variables to the collection object const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid }); const globalEnvSecrets = getGlobalEnvironmentVariablesMasked({ globalEnvironments, activeGlobalEnvironmentUid }); collection.globalEnvironmentVariables = globalEnvironmentVariables; collection.globalEnvSecrets = globalEnvSecrets; } }); const collection = find(collections, (c) => c.uid === focusedTab?.collectionUid); const [dragging, setDragging] = useState(false); const draggingRef = useRef(false); const { left: leftPaneWidth, top: topPaneHeight, reset: resetPaneBoundaries, setTop: setTopPaneHeight, setLeft: setLeftPaneWidth } = useTabPaneBoundaries(activeTabUid); const previousTopPaneHeight = useRef(null); // Store height before devtools opens // Not a recommended pattern here to have the child component // make a callback to set state, but treating this as an exception const docExplorerRef = useRef(null); const mainSectionRef = useRef(null); const [schema, setSchema] = useState(null); const [showGqlDocs, setShowGqlDocs] = useState(false); const onSchemaLoad = useCallback((schema) => setSchema(schema), []); const toggleDocs = useCallback(() => setShowGqlDocs((prev) => !prev), []); const handleGqlClickReference = useCallback((reference) => { if (docExplorerRef.current) { docExplorerRef.current.showDocForReference(reference); } if (!showGqlDocs) { setShowGqlDocs(true); } }, []); const handleMouseMove = useCallback((e) => { if (!draggingRef.current || !mainSectionRef.current) return; e.preventDefault(); const mainRect = mainSectionRef.current.getBoundingClientRect(); if (isVerticalLayoutRef.current) { const newHeight = e.clientY - mainRect.top; const maxHeight = mainRect.height - MIN_BOTTOM_PANE_HEIGHT; // Clamp to bounds instead of returning early const clampedHeight = Math.max(MIN_TOP_PANE_HEIGHT, Math.min(newHeight, maxHeight)); setTopPaneHeight(clampedHeight); } else { const newWidth = e.clientX - mainRect.left; const maxWidth = mainRect.width - MIN_RIGHT_PANE_WIDTH; // Clamp to bounds instead of returning early const clampedWidth = Math.max(MIN_LEFT_PANE_WIDTH, Math.min(newWidth, maxWidth)); setLeftPaneWidth(clampedWidth); } }, [setTopPaneHeight, setLeftPaneWidth]); const handleMouseUp = useCallback((e) => { if (draggingRef.current) { e.preventDefault(); draggingRef.current = false; setDragging(false); } }, []); const handleDragbarMouseDown = useCallback((e) => { e.preventDefault(); draggingRef.current = true; setDragging(true); }, []); useEffect(() => { document.addEventListener('mouseup', handleMouseUp); document.addEventListener('mousemove', handleMouseMove); return () => { document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); }; }, [handleMouseUp, handleMouseMove]); useEffect(() => { if (!isVerticalLayout) return; if (isConsoleOpen) { // Store current height before reducing if (previousTopPaneHeight.current === null) { previousTopPaneHeight.current = topPaneHeight; } // Reduce request pane height to make room for response pane when devtools is open const maxHeight = 200; if (topPaneHeight > maxHeight) { setTopPaneHeight(maxHeight); } } else { // Restore previous height when devtools closes if (previousTopPaneHeight.current !== null) { setTopPaneHeight(previousTopPaneHeight.current); previousTopPaneHeight.current = null; } } }, [isConsoleOpen, isVerticalLayout]); if (typeof window == 'undefined') { return
; } if (!activeTabUid || !focusedTab) { return