mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
collapsible request/response split in request tab (#7566)
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: ${(props) => props.theme.bg};
|
||||
transition: background-color 0.15s ease;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid ${(props) => props.theme.requestTabPanel.dragbar.activeBorder};
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.panel-label {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
opacity: 0.6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover .expand-icon {
|
||||
opacity: 1;
|
||||
color: ${(props) => props.theme.text};
|
||||
}
|
||||
|
||||
&:hover .panel-label {
|
||||
color: ${(props) => props.theme.text};
|
||||
}
|
||||
|
||||
/* Horizontal layout - panels stacked on left or right */
|
||||
&.horizontal {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
border-left: 1px solid ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
border-right: 1px solid ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.indicator-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(-90deg);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.request {
|
||||
border-left: none;
|
||||
&::before { right: -4px; }
|
||||
}
|
||||
|
||||
&.response {
|
||||
border-right: none;
|
||||
&::before { left: -4px; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Vertical layout - panels stacked on top or bottom */
|
||||
&.vertical {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
min-height: 28px;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
cursor: row-resize;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.indicator-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&.request {
|
||||
border-top: none;
|
||||
&::before { bottom: -4px; }
|
||||
}
|
||||
|
||||
&.response {
|
||||
border-bottom: none;
|
||||
&::before { top: -4px; }
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,80 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { IconChevronDown, IconChevronUp } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const CollapsedPanelIndicator = ({
|
||||
panelType, // 'request' or 'response'
|
||||
isVertical,
|
||||
onExpand,
|
||||
onDragStart,
|
||||
dragThresholdPx
|
||||
}) => {
|
||||
const dragThresholdSq = dragThresholdPx * dragThresholdPx; // to use in distance check
|
||||
const label = panelType === 'request' ? 'Request' : 'Response';
|
||||
|
||||
const ChevronIcon = panelType === 'request' ? IconChevronDown : IconChevronUp;
|
||||
|
||||
const pointerDownRef = useRef(null);
|
||||
|
||||
const handlePointerDown = useCallback((e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
e.currentTarget.style.cursor = isVertical ? 'row-resize' : 'col-resize';
|
||||
pointerDownRef.current = { x: e.clientX, y: e.clientY };
|
||||
}, [isVertical]);
|
||||
|
||||
const handlePointerMove = useCallback((e) => {
|
||||
if (!pointerDownRef.current) return;
|
||||
const dx = e.clientX - pointerDownRef.current.x;
|
||||
const dy = e.clientY - pointerDownRef.current.y;
|
||||
if (dx * dx + dy * dy > dragThresholdSq) {
|
||||
pointerDownRef.current = null;
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
onDragStart?.(e);
|
||||
}
|
||||
}, [onDragStart, dragThresholdSq]);
|
||||
|
||||
const handlePointerUp = useCallback((e) => {
|
||||
if (!pointerDownRef.current) return;
|
||||
pointerDownRef.current = null;
|
||||
e.currentTarget.style.cursor = '';
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
onExpand();
|
||||
}, [onExpand]);
|
||||
|
||||
const handlePointerCancel = useCallback((e) => {
|
||||
if (!pointerDownRef.current) return;
|
||||
pointerDownRef.current = null;
|
||||
e.currentTarget.style.cursor = '';
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = useCallback((e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onExpand();
|
||||
}
|
||||
}, [onExpand]);
|
||||
|
||||
return (
|
||||
<StyledWrapper
|
||||
className={`collapsed-panel-indicator ${isVertical ? 'vertical' : 'horizontal'} ${panelType}`}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPointerCancel={handlePointerCancel}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-label={`Expand ${label} pane`}
|
||||
title={`Click to expand ${label} pane, or drag to resize`}
|
||||
>
|
||||
<div className="indicator-content">
|
||||
<ChevronIcon size={14} strokeWidth={2} className="expand-icon" />
|
||||
<span className="panel-label">{label}</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapsedPanelIndicator;
|
||||
@@ -17,56 +17,89 @@ const StyledWrapper = styled.div`
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
&.request-collapsed .query-url-wrapper,
|
||||
&.response-collapsed .query-url-wrapper {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.request-collapsed .main,
|
||||
&.response-collapsed .main {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.request-collapsed .response-pane,
|
||||
&.response-collapsed .request-pane {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
div.dragbar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 10px;
|
||||
min-width: 10px;
|
||||
width: 12px;
|
||||
min-width: 12px;
|
||||
padding: 0;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
div.dragbar-handle {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
border-left: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover div.dragbar-handle {
|
||||
border-left: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.activeBorder};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.vertical-layout {
|
||||
.request-pane {
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
}
|
||||
|
||||
.response-pane {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
&.request-collapsed .response-pane {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&.response-collapsed .request-pane {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
div.dragbar-wrapper {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
height: 12px;
|
||||
cursor: row-resize;
|
||||
padding: 0 1rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
div.dragbar-handle {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-left: none;
|
||||
border-top: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.border};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover div.dragbar-handle {
|
||||
border-left: none;
|
||||
border-top: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.activeBorder};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,14 @@ import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
|
||||
import GlobalEnvironmentSettings from 'components/Environments/GlobalEnvironmentSettings';
|
||||
import OpenAPISyncTab from 'components/OpenAPISyncTab';
|
||||
import OpenAPISpecTab from 'components/OpenAPISpecTab';
|
||||
import CollapsedPanelIndicator from './CollapsedPanelIndicator';
|
||||
|
||||
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 COLLAPSE_EDGE_THRESHOLD = 80;
|
||||
const EXPAND_EDGE_THRESHOLD = 100;
|
||||
|
||||
const RequestTabPanel = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -94,7 +97,19 @@ const RequestTabPanel = () => {
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const draggingRef = useRef(false);
|
||||
|
||||
const { left: leftPaneWidth, top: topPaneHeight, reset: resetPaneBoundaries, setTop: setTopPaneHeight, setLeft: setLeftPaneWidth } = useTabPaneBoundaries(activeTabUid);
|
||||
const {
|
||||
left: leftPaneWidth,
|
||||
top: topPaneHeight,
|
||||
reset: resetPaneBoundaries,
|
||||
setTop: setTopPaneHeight,
|
||||
setLeft: setLeftPaneWidth,
|
||||
requestPaneCollapsed,
|
||||
responsePaneCollapsed,
|
||||
collapseRequest,
|
||||
expandRequest,
|
||||
collapseResponse,
|
||||
expandResponse
|
||||
} = useTabPaneBoundaries(activeTabUid);
|
||||
const previousTopPaneHeight = useRef(null); // Store height before devtools opens
|
||||
|
||||
// Not a recommended pattern here to have the child component
|
||||
@@ -122,6 +137,27 @@ const RequestTabPanel = () => {
|
||||
}
|
||||
}, [dispatch, activeTabUid, showGqlDocs]);
|
||||
|
||||
// Refs for panel collapse/expand functions and current collapsed state
|
||||
const collapseRequestRef = useRef(collapseRequest);
|
||||
const collapseResponseRef = useRef(collapseResponse);
|
||||
const expandRequestRef = useRef(expandRequest);
|
||||
const expandResponseRef = useRef(expandResponse);
|
||||
const requestPaneCollapsedRef = useRef(requestPaneCollapsed);
|
||||
const responsePaneCollapsedRef = useRef(responsePaneCollapsed);
|
||||
useEffect(() => {
|
||||
collapseRequestRef.current = collapseRequest;
|
||||
collapseResponseRef.current = collapseResponse;
|
||||
expandRequestRef.current = expandRequest;
|
||||
expandResponseRef.current = expandResponse;
|
||||
requestPaneCollapsedRef.current = requestPaneCollapsed;
|
||||
responsePaneCollapsedRef.current = responsePaneCollapsed;
|
||||
}, [collapseRequest, collapseResponse, expandRequest, expandResponse, requestPaneCollapsed, responsePaneCollapsed]);
|
||||
|
||||
const stopDragging = useCallback(() => {
|
||||
draggingRef.current = false;
|
||||
setDragging(false);
|
||||
}, []);
|
||||
|
||||
const handleMouseMove = useCallback((e) => {
|
||||
if (!draggingRef.current || !mainSectionRef.current) return;
|
||||
|
||||
@@ -131,13 +167,47 @@ const RequestTabPanel = () => {
|
||||
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 distanceFromBottom = mainRect.bottom - e.clientY;
|
||||
|
||||
if (newHeight < COLLAPSE_EDGE_THRESHOLD) {
|
||||
if (!requestPaneCollapsedRef.current) collapseRequestRef.current();
|
||||
return;
|
||||
}
|
||||
|
||||
if (distanceFromBottom < COLLAPSE_EDGE_THRESHOLD) {
|
||||
if (!responsePaneCollapsedRef.current) collapseResponseRef.current();
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestPaneCollapsedRef.current && newHeight < EXPAND_EDGE_THRESHOLD) return;
|
||||
if (responsePaneCollapsedRef.current && distanceFromBottom < EXPAND_EDGE_THRESHOLD) return;
|
||||
|
||||
if (requestPaneCollapsedRef.current) expandRequestRef.current();
|
||||
if (responsePaneCollapsedRef.current) expandResponseRef.current();
|
||||
|
||||
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 distanceFromRight = mainRect.right - e.clientX;
|
||||
|
||||
if (newWidth < COLLAPSE_EDGE_THRESHOLD) {
|
||||
if (!requestPaneCollapsedRef.current) collapseRequestRef.current();
|
||||
return;
|
||||
}
|
||||
|
||||
if (distanceFromRight < COLLAPSE_EDGE_THRESHOLD) {
|
||||
if (!responsePaneCollapsedRef.current) collapseResponseRef.current();
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestPaneCollapsedRef.current && newWidth < EXPAND_EDGE_THRESHOLD) return;
|
||||
if (responsePaneCollapsedRef.current && distanceFromRight < EXPAND_EDGE_THRESHOLD) return;
|
||||
|
||||
if (requestPaneCollapsedRef.current) expandRequestRef.current();
|
||||
if (responsePaneCollapsedRef.current) expandResponseRef.current();
|
||||
|
||||
const clampedWidth = Math.max(MIN_LEFT_PANE_WIDTH, Math.min(newWidth, maxWidth));
|
||||
setLeftPaneWidth(clampedWidth);
|
||||
}
|
||||
@@ -146,17 +216,45 @@ const RequestTabPanel = () => {
|
||||
const handleMouseUp = useCallback((e) => {
|
||||
if (draggingRef.current) {
|
||||
e.preventDefault();
|
||||
draggingRef.current = false;
|
||||
setDragging(false);
|
||||
stopDragging();
|
||||
}
|
||||
}, []);
|
||||
}, [stopDragging]);
|
||||
|
||||
const handleDragbarMouseDown = useCallback((e) => {
|
||||
const startDragging = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
draggingRef.current = true;
|
||||
setDragging(true);
|
||||
}, []);
|
||||
|
||||
const applyPointerResize = useCallback((e) => {
|
||||
if (!mainSectionRef.current) return;
|
||||
const mainRect = mainSectionRef.current.getBoundingClientRect();
|
||||
|
||||
if (isVerticalLayoutRef.current) {
|
||||
const newHeight = e.clientY - mainRect.top;
|
||||
const maxHeight = mainRect.height - MIN_BOTTOM_PANE_HEIGHT;
|
||||
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;
|
||||
const clampedWidth = Math.max(MIN_LEFT_PANE_WIDTH, Math.min(newWidth, maxWidth));
|
||||
setLeftPaneWidth(clampedWidth);
|
||||
}
|
||||
}, [setTopPaneHeight, setLeftPaneWidth]);
|
||||
|
||||
const handleRequestIndicatorDragStart = useCallback((e) => {
|
||||
expandRequest();
|
||||
applyPointerResize(e);
|
||||
startDragging(e);
|
||||
}, [expandRequest, applyPointerResize, startDragging]);
|
||||
|
||||
const handleResponseIndicatorDragStart = useCallback((e) => {
|
||||
expandResponse();
|
||||
applyPointerResize(e);
|
||||
startDragging(e);
|
||||
}, [expandResponse, applyPointerResize, startDragging]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
@@ -169,6 +267,7 @@ const RequestTabPanel = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVerticalLayout) return;
|
||||
if (responsePaneCollapsed) return;
|
||||
|
||||
if (isConsoleOpen) {
|
||||
// Store current height before reducing
|
||||
@@ -187,7 +286,7 @@ const RequestTabPanel = () => {
|
||||
previousTopPaneHeight.current = null;
|
||||
}
|
||||
}
|
||||
}, [isConsoleOpen, isVerticalLayout]);
|
||||
}, [isConsoleOpen, isVerticalLayout, responsePaneCollapsed]);
|
||||
|
||||
if (typeof window == 'undefined') {
|
||||
return <div></div>;
|
||||
@@ -360,50 +459,76 @@ const RequestTabPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const requestPaneStyle = isVerticalLayout
|
||||
? {
|
||||
height: `${Math.max(topPaneHeight, MIN_TOP_PANE_HEIGHT)}px`,
|
||||
minHeight: `${MIN_TOP_PANE_HEIGHT}px`,
|
||||
width: '100%'
|
||||
}
|
||||
: {
|
||||
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`
|
||||
};
|
||||
const getRequestPaneStyle = () => {
|
||||
if (responsePaneCollapsed) {
|
||||
return isVerticalLayout
|
||||
? { flex: 1, width: '100%' }
|
||||
: { flex: 1 };
|
||||
}
|
||||
|
||||
return isVerticalLayout
|
||||
? {
|
||||
height: `${Math.max(topPaneHeight, MIN_TOP_PANE_HEIGHT)}px`,
|
||||
minHeight: `${MIN_TOP_PANE_HEIGHT}px`,
|
||||
width: '100%'
|
||||
}
|
||||
: {
|
||||
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<ScopedPersistenceProvider scope={focusedTab.uid}>
|
||||
<StyledWrapper
|
||||
className={`flex flex-col flex-grow relative ${dragging ? 'dragging' : ''} ${
|
||||
isVerticalLayout ? 'vertical-layout' : ''
|
||||
}`}
|
||||
className={`flex flex-col flex-grow relative ${dragging ? 'dragging' : ''} ${isVerticalLayout ? 'vertical-layout' : ''
|
||||
} ${requestPaneCollapsed ? 'request-collapsed' : ''} ${responsePaneCollapsed ? 'response-collapsed' : ''}`}
|
||||
>
|
||||
<div className="pt-3 pb-3 px-4">
|
||||
<div className="query-url-wrapper pt-3 pb-4 px-4">
|
||||
{renderQueryUrl()}
|
||||
</div>
|
||||
<section ref={mainSectionRef} className={`main flex ${isVerticalLayout ? 'flex-col' : ''} flex-grow pb-4 relative overflow-auto`}>
|
||||
<section className="request-pane" data-testid="request-pane">
|
||||
<section ref={mainSectionRef} className={`main flex ${isVerticalLayout ? 'flex-col' : ''} flex-grow relative overflow-auto`}>
|
||||
{requestPaneCollapsed ? (
|
||||
<CollapsedPanelIndicator
|
||||
panelType="request"
|
||||
isVertical={isVerticalLayout}
|
||||
onExpand={expandRequest}
|
||||
onDragStart={handleRequestIndicatorDragStart}
|
||||
dragThresholdPx={isVerticalLayout ? MIN_TOP_PANE_HEIGHT / 2 : MIN_LEFT_PANE_WIDTH / 2}
|
||||
/>
|
||||
) : (
|
||||
<section className="request-pane" data-testid="request-pane" style={getRequestPaneStyle()}>
|
||||
<div className="px-4 h-full">
|
||||
{renderRequestPane()}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{!requestPaneCollapsed && !responsePaneCollapsed && (
|
||||
<div
|
||||
className="px-4 h-full"
|
||||
style={requestPaneStyle}
|
||||
className="dragbar-wrapper"
|
||||
onDoubleClick={(e) => {
|
||||
e.preventDefault();
|
||||
resetPaneBoundaries();
|
||||
}}
|
||||
onMouseDown={startDragging}
|
||||
>
|
||||
{renderRequestPane()}
|
||||
<div className="dragbar-handle" />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="dragbar-wrapper"
|
||||
onDoubleClick={(e) => {
|
||||
e.preventDefault();
|
||||
resetPaneBoundaries();
|
||||
}}
|
||||
onMouseDown={handleDragbarMouseDown}
|
||||
>
|
||||
<div className="dragbar-handle" />
|
||||
</div>
|
||||
|
||||
<section className="response-pane flex-grow overflow-x-auto" data-testid="response-pane">
|
||||
{renderResponsePane()}
|
||||
</section>
|
||||
{responsePaneCollapsed ? (
|
||||
<CollapsedPanelIndicator
|
||||
panelType="response"
|
||||
isVertical={isVerticalLayout}
|
||||
onExpand={expandResponse}
|
||||
onDragStart={handleResponseIndicatorDragStart}
|
||||
dragThresholdPx={isVerticalLayout ? MIN_BOTTOM_PANE_HEIGHT / 2 : MIN_RIGHT_PANE_WIDTH / 2}
|
||||
/>
|
||||
) : (
|
||||
<section className="response-pane flex-grow overflow-x-auto" data-testid="response-pane" style={requestPaneCollapsed ? { flex: 1 } : undefined}>
|
||||
{renderResponsePane()}
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{item.type === 'graphql-request' ? (
|
||||
@@ -415,6 +540,17 @@ const RequestTabPanel = () => {
|
||||
</DocExplorer>
|
||||
</div>
|
||||
) : null}
|
||||
{dragging ? (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: 9999,
|
||||
cursor: isVerticalLayout ? 'row-resize' : 'col-resize',
|
||||
userSelect: 'none'
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</StyledWrapper>
|
||||
</ScopedPersistenceProvider>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import find from 'lodash/find';
|
||||
import { updateRequestPaneTabHeight, updateRequestPaneTabWidth } from 'providers/ReduxStore/slices/tabs';
|
||||
import {
|
||||
updateRequestPaneTabHeight,
|
||||
updateRequestPaneTabWidth,
|
||||
collapseRequestPane,
|
||||
collapseResponsePane,
|
||||
expandRequestPane,
|
||||
expandResponsePane
|
||||
} from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
const MIN_TOP_PANE_HEIGHT = 380;
|
||||
@@ -13,11 +20,16 @@ export function useTabPaneBoundaries(activeTabUid) {
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const left = focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / DEFAULT_PANE_WIDTH_DIVISOR;
|
||||
const top = focusedTab?.requestPaneHeight || MIN_TOP_PANE_HEIGHT;
|
||||
const requestPaneCollapsed = focusedTab?.requestPaneCollapsed || false;
|
||||
const responsePaneCollapsed = focusedTab?.responsePaneCollapsed || false;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
requestPaneCollapsed,
|
||||
responsePaneCollapsed,
|
||||
setLeft(value) {
|
||||
dispatch(updateRequestPaneTabWidth({
|
||||
uid: activeTabUid,
|
||||
@@ -30,7 +42,21 @@ export function useTabPaneBoundaries(activeTabUid) {
|
||||
requestPaneHeight: value
|
||||
}));
|
||||
},
|
||||
collapseRequest() {
|
||||
dispatch(collapseRequestPane({ uid: activeTabUid }));
|
||||
},
|
||||
expandRequest() {
|
||||
dispatch(expandRequestPane({ uid: activeTabUid }));
|
||||
},
|
||||
collapseResponse() {
|
||||
dispatch(collapseResponsePane({ uid: activeTabUid }));
|
||||
},
|
||||
expandResponse() {
|
||||
dispatch(expandResponsePane({ uid: activeTabUid }));
|
||||
},
|
||||
reset() {
|
||||
dispatch(expandRequestPane({ uid: activeTabUid }));
|
||||
dispatch(expandResponsePane({ uid: activeTabUid }));
|
||||
dispatch(updateRequestPaneTabHeight({
|
||||
uid: activeTabUid,
|
||||
requestPaneHeight: MIN_TOP_PANE_HEIGHT
|
||||
|
||||
@@ -64,6 +64,11 @@ export const tabsSlice = createSlice({
|
||||
uid,
|
||||
collectionUid,
|
||||
requestPaneWidth: null,
|
||||
requestPaneHeight: null,
|
||||
requestPaneCollapsed: false,
|
||||
responsePaneCollapsed: false,
|
||||
requestPaneWidthBeforeCollapse: null,
|
||||
requestPaneHeightBeforeCollapse: null,
|
||||
requestPaneTab: requestPaneTab || defaultRequestPaneTab,
|
||||
responsePaneTab: 'response',
|
||||
responseFormat: null,
|
||||
@@ -87,6 +92,11 @@ export const tabsSlice = createSlice({
|
||||
uid,
|
||||
collectionUid,
|
||||
requestPaneWidth: null,
|
||||
requestPaneHeight: null,
|
||||
requestPaneCollapsed: false,
|
||||
responsePaneCollapsed: false,
|
||||
requestPaneWidthBeforeCollapse: null,
|
||||
requestPaneHeightBeforeCollapse: null,
|
||||
requestPaneTab: requestPaneTab || defaultRequestPaneTab,
|
||||
responsePaneTab: 'response',
|
||||
responseFormat: null,
|
||||
@@ -316,6 +326,42 @@ export const tabsSlice = createSlice({
|
||||
console.error('Tab not found!');
|
||||
}
|
||||
},
|
||||
collapseRequestPane: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
if (tab) {
|
||||
tab.requestPaneCollapsed = true;
|
||||
tab.responsePaneCollapsed = false;
|
||||
tab.requestPaneWidthBeforeCollapse = tab.requestPaneWidth;
|
||||
tab.requestPaneHeightBeforeCollapse = tab.requestPaneHeight;
|
||||
}
|
||||
},
|
||||
collapseResponsePane: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
if (tab) {
|
||||
tab.responsePaneCollapsed = true;
|
||||
tab.requestPaneCollapsed = false;
|
||||
}
|
||||
},
|
||||
expandRequestPane: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
if (tab) {
|
||||
tab.requestPaneCollapsed = false;
|
||||
if (tab.requestPaneWidthBeforeCollapse != null) {
|
||||
tab.requestPaneWidth = tab.requestPaneWidthBeforeCollapse;
|
||||
}
|
||||
if (tab.requestPaneHeightBeforeCollapse != null) {
|
||||
tab.requestPaneHeight = tab.requestPaneHeightBeforeCollapse;
|
||||
}
|
||||
tab.requestPaneWidthBeforeCollapse = null;
|
||||
tab.requestPaneHeightBeforeCollapse = null;
|
||||
}
|
||||
},
|
||||
expandResponsePane: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
if (tab) {
|
||||
tab.responsePaneCollapsed = false;
|
||||
}
|
||||
},
|
||||
reorderTabs: (state, action) => {
|
||||
const { direction, sourceUid, targetUid } = action.payload;
|
||||
const tabs = state.tabs;
|
||||
@@ -383,6 +429,10 @@ export const {
|
||||
closeTabs,
|
||||
closeAllCollectionTabs,
|
||||
makeTabPermanent,
|
||||
collapseRequestPane,
|
||||
collapseResponsePane,
|
||||
expandRequestPane,
|
||||
expandResponsePane,
|
||||
reorderTabs,
|
||||
reopenLastClosedTab,
|
||||
updateQueryBuilderOpen,
|
||||
|
||||
Reference in New Issue
Block a user