Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,16 @@ const GlobalSearchModal = ({ isOpen, onClose }) => {
uid: result.item.uid,
collectionUid: result.collectionUid,
requestPaneTab: getDefaultRequestPaneTab(result.item),
type: 'request'
type: result.item.type,
pathname: result.item.pathname
}));
}
} else if (result.type === SEARCH_TYPES.FOLDER) {
dispatch(addTab({
uid: result.item.uid,
collectionUid: result.collectionUid,
type: 'folder-settings'
type: 'folder-settings',
pathname: result.item.pathname
}));
} else if (result.type === SEARCH_TYPES.COLLECTION) {
dispatch(addTab({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@ const RequestNotFound = ({ itemUid }) => {
};

useEffect(() => {
setTimeout(() => {
const timer = setTimeout(() => {
setShowErrorMessage(true);
}, 300);
return () => clearTimeout(timer);
}, []);

// add a delay component in react that shows a loading spinner
// and then shows the error message after a delay
// this will prevent the error message from flashing on the screen

if (!showErrorMessage) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { IconLoader2 } from '@tabler/icons';

const RequestTabPanelLoading = ({ name }) => {
return (
<div className="flex flex-col items-center justify-center h-full gap-3 text-muted">
<IconLoader2 className="animate-spin" size={24} strokeWidth={1.5} />
<span>Loading {name ? `"${name}"` : 'request'}...</span>
</div>
);
};

export default RequestTabPanelLoading;
64 changes: 49 additions & 15 deletions packages/bruno-app/src/components/RequestTabPanel/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import find from 'lodash/find';
import toast from 'react-hot-toast';
import { useSelector, useDispatch } from 'react-redux';
Expand All @@ -7,7 +7,7 @@ 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 { findItemInCollection, findItemInCollectionByPathname, areItemsLoading } from 'utils/collections';
import { cancelRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl/index';
Expand All @@ -25,6 +25,7 @@ import { produce } from 'immer';
import CollectionOverview from 'components/CollectionSettings/Overview';
import RequestNotLoaded from './RequestNotLoaded';
import RequestIsLoading from './RequestIsLoading';
import RequestTabPanelLoading from './RequestTabPanelLoading';
import FolderNotFound from './FolderNotFound';
import ExampleNotFound from './ExampleNotFound';
import WsQueryUrl from 'components/RequestPane/WsQueryUrl';
Expand Down Expand Up @@ -80,6 +81,11 @@ const RequestTabPanel = () => {
});

const collection = find(collections, (c) => c.uid === focusedTab?.collectionUid);

const isItemsLoading = useMemo(() => {
return collection?.mountStatus === 'mounting' || areItemsLoading(collection);
}, [collection?.mountStatus, collection]);

const [dragging, setDragging] = useState(false);
const draggingRef = useRef(false);

Expand Down Expand Up @@ -205,16 +211,34 @@ const RequestTabPanel = () => {
}

if (focusedTab.type === 'response-example') {
const item = findItemInCollection(collection, focusedTab.itemUid);
const example = item?.examples?.find((ex) => ex.uid === focusedTab.uid);
let item = findItemInCollection(collection, focusedTab.itemUid);
if (!item && focusedTab.pathname) {
item = findItemInCollectionByPathname(collection, focusedTab.pathname);
}

let example = null;
if (item?.examples) {
example = item.examples.find((ex) => ex.uid === focusedTab.uid);
if (!example && focusedTab.exampleName) {
example = item.examples.find((ex) => ex.name === focusedTab.exampleName);
}
}

if (!example) {
return <ExampleNotFound itemUid={focusedTab.itemUid} exampleUid={focusedTab.uid} />;
if (example) {
return <ResponseExample item={item} collection={collection} example={example} />;
}
return <ResponseExample item={item} collection={collection} example={example} />;

const displayName = focusedTab.exampleName || focusedTab.name;
if (displayName && isItemsLoading) {
return <RequestTabPanelLoading name={displayName} />;
}
return <ExampleNotFound itemUid={focusedTab.itemUid} exampleUid={focusedTab.uid} />;
}

const item = findItemInCollection(collection, activeTabUid);
let item = findItemInCollection(collection, activeTabUid);
if (!item && focusedTab.pathname) {
item = findItemInCollectionByPathname(collection, focusedTab.pathname);
}
const isGrpcRequest = item?.type === 'grpc-request';
const isWsRequest = item?.type === 'ws-request';

Expand All @@ -235,12 +259,19 @@ const RequestTabPanel = () => {
}

if (focusedTab.type === 'folder-settings') {
const folder = findItemInCollection(collection, focusedTab.folderUid);
if (!folder) {
return <FolderNotFound folderUid={focusedTab.folderUid} />;
let folder = findItemInCollection(collection, focusedTab.folderUid);
if (!folder && focusedTab.pathname) {
folder = findItemInCollectionByPathname(collection, focusedTab.pathname);
}

return <FolderSettings collection={collection} folder={folder} />;
if (folder) {
return <FolderSettings collection={collection} folder={folder} />;
}

if (focusedTab.name && isItemsLoading) {
return <RequestTabPanelLoading name={focusedTab.name} />;
}
return <FolderNotFound folderUid={focusedTab.folderUid} />;
}

if (focusedTab.type === 'environment-settings') {
Expand All @@ -256,14 +287,17 @@ const RequestTabPanel = () => {
}

if (!item || !item.uid) {
return <RequestNotFound itemUid={activeTabUid} />;
const showLoading = focusedTab.name && isItemsLoading;
return showLoading
? <RequestTabPanelLoading name={focusedTab.name} />
: <RequestNotFound itemUid={activeTabUid} />;
}

if (item?.partial) {
if (item.partial) {
return <RequestNotLoaded item={item} collection={collection} />;
}

if (item?.loading) {
if (item.loading) {
return <RequestIsLoading item={item} />;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useState, useRef, useMemo } from 'react';
import React, { useState, useRef, useMemo, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import { makeTabPermanent, syncTabUid } from 'providers/ReduxStore/slices/tabs';
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
import { saveRequest, closeTabs } from 'providers/ReduxStore/slices/collections/actions';
import { hasExampleChanges, findItemInCollection } from 'utils/collections';
import { hasExampleChanges, findItemInCollection, findItemInCollectionByPathname, areItemsLoading } from 'utils/collections';
import ExampleIcon from 'components/Icons/ExampleIcon';
import ConfirmRequestClose from '../RequestTab/ConfirmRequestClose';
import RequestTabNotFound from '../RequestTab/RequestTabNotFound';
import RequestTabLoading from '../RequestTab/RequestTabLoading';
import StyledWrapper from '../RequestTab/StyledWrapper';
import GradientCloseButton from '../RequestTab/GradientCloseButton';

Expand All @@ -16,11 +17,32 @@ const ExampleTab = ({ tab, collection }) => {

const dropdownTippyRef = useRef();

// Get item and example data
const item = findItemInCollection(collection, tab.itemUid);
const example = useMemo(() => item?.examples?.find((ex) => ex.uid === tab.uid), [item?.examples, tab.uid]);
let item = findItemInCollection(collection, tab.itemUid);
if (!item && tab.pathname) {
item = findItemInCollectionByPathname(collection, tab.pathname);
}

const example = useMemo(() => {
if (!item?.examples) return null;
const byUid = item.examples.find((ex) => ex.uid === tab.uid);
if (byUid) return byUid;
if (tab.exampleName) {
return item.examples.find((ex) => ex.name === tab.exampleName);
}
return null;
}, [item?.examples, tab.uid, tab.exampleName]);

const hasChanges = useMemo(() => hasExampleChanges(item, tab.uid), [item, tab.uid]);
const hasChanges = useMemo(() => hasExampleChanges(item, example?.uid), [item, example?.uid]);

const isItemsLoading = useMemo(() => {
return collection?.mountStatus === 'mounting' || areItemsLoading(collection);
}, [collection?.mountStatus, collection]);

useEffect(() => {
if (example && example.uid !== tab.uid) {
dispatch(syncTabUid({ oldUid: tab.uid, newUid: example.uid }));
}
}, [example, tab.uid, dispatch]);

const handleCloseClick = (event) => {
event.stopPropagation();
Expand Down Expand Up @@ -56,6 +78,8 @@ const ExampleTab = ({ tab, collection }) => {
};

if (!item || !example) {
const displayName = tab.exampleName || tab.name;
const showLoading = displayName && isItemsLoading;
return (
<StyledWrapper
className="flex items-center justify-between tab-container"
Expand All @@ -68,7 +92,11 @@ const ExampleTab = ({ tab, collection }) => {
}
}}
>
<RequestTabNotFound handleCloseClick={handleCloseClick} />
{showLoading ? (
<RequestTabLoading handleCloseClick={handleCloseClick} name={displayName} />
) : (
<RequestTabNotFound handleCloseClick={handleCloseClick} />
)}
</StyledWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import GradientCloseButton from './GradientCloseButton';

/**
* RequestTabLoading
*
* Displays a loading placeholder for a tab while its collection is mounting
* or the item is still being loaded. Shows the stored name from the snapshot.
*/
const RequestTabLoading = ({ handleCloseClick, name }) => {
return (
<>
<div className="flex items-baseline tab-label">
<span className="tab-name" title={name}>{name}</span>
</div>
<GradientCloseButton onClick={handleCloseClick} hasChanges={false} />
</>
);
};

export default RequestTabLoading;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const StyledWrapper = styled.div`
position: relative;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 0.8125rem;

// so that the name does not cutoff when italicized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { clearGlobalEnvironmentDraft } from 'providers/ReduxStore/slices/global-
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { useTheme } from 'providers/Theme';
import { useDispatch, useSelector } from 'react-redux';
import { findItemInCollection, hasRequestChanges } from 'utils/collections';
import { findItemInCollection, findItemInCollectionByPathname, hasRequestChanges, areItemsLoading } from 'utils/collections';
import ConfirmRequestClose from './ConfirmRequestClose';
import ConfirmCollectionClose from './ConfirmCollectionClose';
import ConfirmFolderClose from './ConfirmFolderClose';
import ConfirmCloseEnvironment from 'components/Environments/ConfirmCloseEnvironment';
import RequestTabNotFound from './RequestTabNotFound';
import RequestTabLoading from './RequestTabLoading';
import SpecialTab from './SpecialTab';
import StyledWrapper from './StyledWrapper';
import MenuDropdown from 'ui/MenuDropdown';
Expand All @@ -38,7 +39,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi

const menuDropdownRef = useRef();

const item = findItemInCollection(collection, tab.uid);
let item = findItemInCollection(collection, tab.uid);
if (!item && tab.pathname) {
item = findItemInCollectionByPathname(collection, tab.pathname);
}

const method = useMemo(() => {
if (!item) return;
Expand All @@ -56,6 +60,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi

const hasChanges = useMemo(() => hasRequestChanges(item), [item]);

const isItemsLoading = useMemo(() => {
return collection?.mountStatus === 'mounting' || areItemsLoading(collection);
}, [collection?.mountStatus, collection]);

const isWS = item?.type === 'ws-request';

useEffect(() => {
Expand Down Expand Up @@ -134,7 +142,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
setShowConfirmCollectionClose(true);
};

const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
let folder = folderUid ? findItemInCollection(collection, folderUid) : null;
if (!folder && tab.type === 'folder-settings' && tab.pathname) {
folder = findItemInCollectionByPathname(collection, tab.pathname);
}

const handleCloseFolderSettings = (event) => {
if (!folder?.draft) {
Expand Down Expand Up @@ -341,7 +352,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
/>
)}
{tab.type === 'folder-settings' && !folder ? (
<RequestTabNotFound handleCloseClick={handleCloseClick} />
tab.name && isItemsLoading
? <RequestTabLoading handleCloseClick={handleCloseClick} name={tab.name} />
: <RequestTabNotFound handleCloseClick={handleCloseClick} />
) : tab.type === 'folder-settings' ? (
<SpecialTab handleCloseClick={handleCloseFolderSettings} handleDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} tabName={folder?.name} hasDraft={hasFolderDraft} />
) : tab.type === 'collection-settings' ? (
Expand Down Expand Up @@ -375,9 +388,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
}

if (!item) {
const showLoading = tab.name && isItemsLoading;
return (
<StyledWrapper
className="flex items-center justify-between tab-container"
className="flex items-center justify-between tab-container px-2"
onMouseUp={(e) => {
if (e.button === 1) {
e.preventDefault();
Expand All @@ -387,7 +401,11 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
}
}}
>
<RequestTabNotFound handleCloseClick={handleCloseClick} />
{showLoading ? (
<RequestTabLoading handleCloseClick={handleCloseClick} name={tab.name} />
) : (
<RequestTabNotFound handleCloseClick={handleCloseClick} />
)}
</StyledWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { useSidebarAccordion } from 'components/Sidebar/SidebarAccordionContext'
const ExampleItem = ({ example, item, collection }) => {
const { dropdownContainerRef } = useSidebarAccordion();
const dispatch = useDispatch();
// Check if this example is the active tab
const activeTabUid = useSelector((state) => state.tabs?.activeTabUid);
const isExampleActive = activeTabUid === example.uid;
const [editName, setEditName] = useState(example.name || '');
Expand All @@ -39,11 +38,12 @@ const ExampleItem = ({ example, item, collection }) => {

const handleExampleClick = () => {
dispatch(addTab({
uid: example.uid, // Use example.uid as the tab uid
exampleUid: example.uid,
uid: example.uid,
collectionUid: collection.uid,
type: 'response-example',
itemUid: item.uid
itemUid: item.uid,
pathname: item.pathname,
exampleName: example.name
}));
};

Expand Down
Loading
Loading