mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
refactor: enhance WSRequestPane and WSResponsePane with ResponsiveTabs component (#6650)
* refactor: enhance WSRequestPane and WSResponsePane with ResponsiveTabs component - Replaced custom tab implementation with ResponsiveTabs for better consistency and usability. - Utilized useMemo and useCallback for performance optimizations in tab management. - Cleaned up unused styles and improved error handling in both components. - Updated StyledWrapper to remove legacy tab styles, streamlining the component structure. * refactor: streamline authentication components and enhance WSRequestPane layout - Removed unnecessary margin from StyledWrapper in ApiKeyAuth, BasicAuth, and BearerAuth components for a cleaner layout. - Introduced a new right content area in WSRequestPane for better organization of authentication modes. - Added a 'No Auth' view in WSAuth for improved user feedback when no authentication is selected. - Cleaned up unused imports and optimized component structure for maintainability.
This commit is contained in:
@@ -1,34 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.tabs {
|
||||
div.tab {
|
||||
padding: 6px 0px;
|
||||
border: none;
|
||||
border-bottom: solid 2px transparent;
|
||||
margin-right: ${(props) => props.theme.tabs.marginRight};
|
||||
color: ${(props) => props.theme.colors.text.subtext0};
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||
}
|
||||
|
||||
.content-indicator {
|
||||
color: ${(props) => props.theme.text}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import WSAuthMode from './WSAuthMode';
|
||||
import BearerAuth from '../../Auth/BearerAuth';
|
||||
import BasicAuth from '../../Auth/BasicAuth';
|
||||
import ApiKeyAuth from '../../Auth/ApiKeyAuth';
|
||||
@@ -68,6 +67,9 @@ const WSAuth = ({ item, collection }) => {
|
||||
|
||||
const getAuthView = () => {
|
||||
switch (authMode) {
|
||||
case 'none': {
|
||||
return <div>No Auth</div>;
|
||||
}
|
||||
case 'basic': {
|
||||
return <BasicAuth collection={collection} item={item} updateAuth={updateAuth} request={request} save={save} />;
|
||||
}
|
||||
@@ -80,7 +82,7 @@ const WSAuth = ({ item, collection }) => {
|
||||
case 'oauth2': {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
<div className="flex flex-row w-full gap-2">
|
||||
<div>
|
||||
OAuth 2 not <strong>yet</strong> supported by WebSockets. Using no auth instead.
|
||||
</div>
|
||||
@@ -95,7 +97,7 @@ const WSAuth = ({ item, collection }) => {
|
||||
if (source && supportedAuthModes.includes(source.auth?.mode)) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
<div className="flex flex-row w-full gap-2">
|
||||
<div> Auth inherited from {source.name}: </div>
|
||||
<div className="inherit-mode-text">{humanizeRequestAuthMode(source.auth?.mode)}</div>
|
||||
</div>
|
||||
@@ -104,7 +106,7 @@ const WSAuth = ({ item, collection }) => {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
<div className="flex flex-row w-full gap-2">
|
||||
<div>Inherited auth not supported by WebSockets. Using no auth instead.</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -119,9 +121,6 @@ const WSAuth = ({ item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full overflow-y-scroll">
|
||||
<div className="flex flex-grow justify-start items-center">
|
||||
<WSAuthMode item={item} collection={collection} />
|
||||
</div>
|
||||
{getAuthView()}
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { useMemo, useCallback, useRef } from 'react';
|
||||
import Documentation from 'components/Documentation/index';
|
||||
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
||||
import StatusDot from 'components/StatusDot/index';
|
||||
import { find } from 'lodash';
|
||||
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import HeightBoundContainer from 'ui/HeightBoundContainer';
|
||||
import ResponsiveTabs from 'ui/ResponsiveTabs';
|
||||
import { getPropertyFromDraftOrRequest } from 'utils/collections/index';
|
||||
import WsBody from '../WsBody/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import WSAuth from './WSAuth';
|
||||
import WSAuthMode from './WSAuth/WSAuthMode';
|
||||
import WSSettingsPane from '../WSSettingsPane/index';
|
||||
|
||||
const WSRequestPane = ({ item, collection, handleRun }) => {
|
||||
@@ -18,15 +19,59 @@ const WSRequestPane = ({ item, collection, handleRun }) => {
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
|
||||
const selectTab = (tab) => {
|
||||
dispatch(updateRequestPaneTab({
|
||||
uid: item.uid,
|
||||
requestPaneTab: tab
|
||||
}));
|
||||
};
|
||||
const rightContentRef = useRef(null);
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const requestPaneTab = focusedTab?.requestPaneTab;
|
||||
|
||||
const selectTab = useCallback(
|
||||
(tab) => {
|
||||
dispatch(updateRequestPaneTab({
|
||||
uid: item.uid,
|
||||
requestPaneTab: tab
|
||||
}));
|
||||
},
|
||||
[dispatch, item.uid]
|
||||
);
|
||||
|
||||
const headers = getPropertyFromDraftOrRequest(item, 'request.headers');
|
||||
const docs = getPropertyFromDraftOrRequest(item, 'request.docs');
|
||||
const auth = getPropertyFromDraftOrRequest(item, 'request.auth');
|
||||
|
||||
const activeHeadersLength = headers.filter((header) => header.enabled).length;
|
||||
|
||||
const allTabs = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'body',
|
||||
label: 'Message',
|
||||
indicator: null
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
label: 'Headers',
|
||||
indicator: activeHeadersLength > 0 ? <sup className="ml-[.125rem] font-medium">{activeHeadersLength}</sup> : null
|
||||
},
|
||||
{
|
||||
key: 'auth',
|
||||
label: 'Auth',
|
||||
indicator: auth.mode !== 'none' ? <StatusDot type="default" /> : null
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
label: 'Settings',
|
||||
indicator: null
|
||||
},
|
||||
{
|
||||
key: 'docs',
|
||||
label: 'Docs',
|
||||
indicator: docs && docs.length > 0 ? <StatusDot type="default" /> : null
|
||||
}
|
||||
];
|
||||
}, [activeHeadersLength, auth.mode, docs]);
|
||||
|
||||
const tabPanel = useMemo(() => {
|
||||
switch (requestPaneTab) {
|
||||
case 'body': {
|
||||
return (
|
||||
<WsBody
|
||||
@@ -54,61 +99,30 @@ const WSRequestPane = ({ item, collection, handleRun }) => {
|
||||
return <div className="mt-4">404 | Not found</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [requestPaneTab, item, collection, handleRun]);
|
||||
|
||||
if (!activeTabUid) {
|
||||
return <div>Something went wrong</div>;
|
||||
}
|
||||
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
|
||||
if (!activeTabUid || !focusedTab?.uid || !requestPaneTab) {
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
active: tabName === focusedTab.requestPaneTab
|
||||
});
|
||||
};
|
||||
|
||||
const headers = getPropertyFromDraftOrRequest(item, 'request.headers');
|
||||
const docs = getPropertyFromDraftOrRequest(item, 'request.docs');
|
||||
const auth = getPropertyFromDraftOrRequest(item, 'request.auth');
|
||||
|
||||
const activeHeadersLength = headers.filter((header) => header.enabled).length;
|
||||
|
||||
useEffect(() => {
|
||||
if (!focusedTab?.requestPaneTab) {
|
||||
selectTab('body');
|
||||
}
|
||||
}, []);
|
||||
const rightContent = requestPaneTab === 'auth' ? (
|
||||
<div ref={rightContentRef} className="flex flex-grow justify-start items-center">
|
||||
<WSAuthMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
|
||||
Message
|
||||
</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
{activeHeadersLength > 0 && <sup className="ml-[.125rem] font-medium">{activeHeadersLength}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
|
||||
Auth
|
||||
{auth.mode !== 'none' && <StatusDot type="default" />}
|
||||
</div>
|
||||
<div className={getTabClassname('settings')} role="tab" onClick={() => selectTab('settings')}>
|
||||
Settings
|
||||
</div>
|
||||
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
|
||||
Docs
|
||||
{docs && docs.length > 0 && <StatusDot type="default" />}
|
||||
</div>
|
||||
</div>
|
||||
<section
|
||||
className={classnames('flex w-full flex-1 h-full mt-4')}
|
||||
>
|
||||
<HeightBoundContainer>{getTabPanel(focusedTab.requestPaneTab)}</HeightBoundContainer>
|
||||
<ResponsiveTabs
|
||||
tabs={allTabs}
|
||||
activeTab={requestPaneTab}
|
||||
onTabSelect={selectTab}
|
||||
rightContent={rightContent}
|
||||
rightContentRef={rightContent ? rightContentRef : null}
|
||||
/>
|
||||
|
||||
<section className="flex w-full flex-1 h-full mt-4">
|
||||
<HeightBoundContainer>{tabPanel}</HeightBoundContainer>
|
||||
</section>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { updateResponsePaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||
@@ -11,7 +11,7 @@ import ClearTimeline from '../ClearTimeline';
|
||||
import ResponseClear from '../ResponseClear';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ResponseLayoutToggle from '../ResponseLayoutToggle';
|
||||
import Tab from 'components/Tab';
|
||||
import ResponsiveTabs from 'ui/ResponsiveTabs';
|
||||
import WSMessagesList from './WSMessagesList';
|
||||
import WSResponseSortOrder from './WSResponseSortOrder';
|
||||
import WSResponseHeaders from './WSResponseHeaders';
|
||||
@@ -25,6 +25,7 @@ const WSResponsePane = ({ item, collection }) => {
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const isLoading = ['queued', 'sending'].includes(item.requestState);
|
||||
const rightContentRef = useRef(null);
|
||||
|
||||
const requestTimeline = [...(collection?.timeline || [])].filter((obj) => {
|
||||
if (obj.itemUid === item.uid) return true;
|
||||
@@ -39,6 +40,29 @@ const WSResponsePane = ({ item, collection }) => {
|
||||
|
||||
const response = item.response || {};
|
||||
|
||||
const messagesCount = Array.isArray(response.responses) ? response.responses.length : 0;
|
||||
const headersCount = response.headers ? Object.keys(response.headers).length : 0;
|
||||
|
||||
const allTabs = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'response',
|
||||
label: 'Messages',
|
||||
indicator: messagesCount > 0 ? <sup className="ml-1 font-medium">{messagesCount}</sup> : null
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
label: 'Headers',
|
||||
indicator: headersCount > 0 ? <sup className="ml-1 font-medium">{headersCount}</sup> : null
|
||||
},
|
||||
{
|
||||
key: 'timeline',
|
||||
label: 'Timeline',
|
||||
indicator: null
|
||||
}
|
||||
];
|
||||
}, [messagesCount, headersCount]);
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'response': {
|
||||
@@ -81,62 +105,41 @@ const WSResponsePane = ({ item, collection }) => {
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
const tabConfig = [
|
||||
{
|
||||
name: 'response',
|
||||
label: 'Messages',
|
||||
count: Array.isArray(response.responses) ? response.responses.length : 0
|
||||
},
|
||||
{
|
||||
name: 'headers',
|
||||
label: 'Headers',
|
||||
count: response.headers ? Object.keys(response.headers).length : 0
|
||||
},
|
||||
{
|
||||
name: 'timeline',
|
||||
label: 'Timeline'
|
||||
}
|
||||
];
|
||||
const rightContent = !isLoading ? (
|
||||
<div ref={rightContentRef} className="flex items-center">
|
||||
{focusedTab?.responsePaneTab === 'timeline' ? (
|
||||
<>
|
||||
<ResponseLayoutToggle />
|
||||
<ClearTimeline item={item} collection={collection} />
|
||||
</>
|
||||
) : item?.response ? (
|
||||
<>
|
||||
<ResponseLayoutToggle />
|
||||
<ResponseClear item={item} collection={collection} />
|
||||
<WSResponseSortOrder item={item} collection={collection} />
|
||||
<WSStatusCode
|
||||
status={response.statusCode}
|
||||
text={response.statusText}
|
||||
details={response.statusDescription}
|
||||
/>
|
||||
<ResponseTime duration={response.duration} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex flex-wrap items-center pl-3 pr-4 tabs" role="tablist">
|
||||
{tabConfig.map((tab) => (
|
||||
<Tab
|
||||
key={tab.name}
|
||||
name={tab.name}
|
||||
label={tab.label}
|
||||
isActive={focusedTab.responsePaneTab === tab.name}
|
||||
onClick={selectTab}
|
||||
count={tab.count}
|
||||
/>
|
||||
))}
|
||||
{!isLoading ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
{focusedTab?.responsePaneTab === 'timeline' ? (
|
||||
<>
|
||||
<ResponseLayoutToggle />
|
||||
<ClearTimeline item={item} collection={collection} />
|
||||
</>
|
||||
) : item?.response ? (
|
||||
<>
|
||||
<ResponseLayoutToggle />
|
||||
<ResponseClear item={item} collection={collection} />
|
||||
<WSResponseSortOrder item={item} collection={collection} />
|
||||
<WSStatusCode
|
||||
status={response.statusCode}
|
||||
text={response.statusText}
|
||||
details={response.statusDescription}
|
||||
/>
|
||||
<ResponseTime duration={response.duration} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="px-4">
|
||||
<ResponsiveTabs
|
||||
tabs={allTabs}
|
||||
activeTab={focusedTab.responsePaneTab}
|
||||
onTabSelect={selectTab}
|
||||
rightContent={rightContent}
|
||||
rightContentRef={rightContentRef}
|
||||
/>
|
||||
</div>
|
||||
<section
|
||||
className="flex flex-col flex-grow pl-3 pr-4 h-0 mt-4"
|
||||
>
|
||||
<section className="flex flex-col flex-grow px-4 h-0 mt-4">
|
||||
{isLoading ? <Overlay item={item} collection={collection} /> : null}
|
||||
{!item?.response ? (
|
||||
focusedTab?.responsePaneTab === 'timeline' && requestTimeline?.length ? (
|
||||
|
||||
Reference in New Issue
Block a user