mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-02 17:08:32 +00:00
Feature: Improve tab UX (#3831)
--------- Co-authored-by: ramki-bruno <ramki@usebruno.com>
This commit is contained in:
@@ -2,15 +2,15 @@ import React from 'react';
|
|||||||
import CloseTabIcon from './CloseTabIcon';
|
import CloseTabIcon from './CloseTabIcon';
|
||||||
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
|
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
|
||||||
|
|
||||||
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick }) => {
|
||||||
const getTabInfo = (type, tabName) => {
|
const getTabInfo = (type, tabName) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'collection-settings': {
|
case 'collection-settings': {
|
||||||
return (
|
return (
|
||||||
<>
|
<div onDoubleClick={handleDoubleClick} className="flex items-center flex-nowrap overflow-hidden">
|
||||||
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1 leading-6">Collection</span>
|
<span className="ml-1 leading-6">Collection</span>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'collection-overview': {
|
case 'collection-overview': {
|
||||||
@@ -31,7 +31,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
|||||||
}
|
}
|
||||||
case 'folder-settings': {
|
case 'folder-settings': {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center flex-nowrap overflow-hidden">
|
<div onDoubleClick={handleDoubleClick} className="flex items-center flex-nowrap overflow-hidden">
|
||||||
<IconFolder size={18} strokeWidth={1.5} className="text-yellow-600 min-w-[18px]" />
|
<IconFolder size={18} strokeWidth={1.5} className="text-yellow-600 min-w-[18px]" />
|
||||||
<span className="ml-1 leading-6 truncate">{tabName || 'Folder'}</span>
|
<span className="ml-1 leading-6 truncate">{tabName || 'Folder'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useRef, Fragment } from 'react';
|
import React, { useState, useRef, Fragment } from 'react';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeTabs, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
@@ -73,13 +73,13 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
|||||||
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
className="flex items-center justify-between tab-container px-1"
|
className={`flex items-center justify-between tab-container px-1 ${tab.preview ? "italic" : ""}`}
|
||||||
onMouseUp={handleMouseUp} // Add middle-click behavior here
|
onMouseUp={handleMouseUp} // Add middle-click behavior here
|
||||||
>
|
>
|
||||||
{tab.type === 'folder-settings' ? (
|
{tab.type === 'folder-settings' ? (
|
||||||
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} tabName={folder?.name} />
|
<SpecialTab handleCloseClick={handleCloseClick} handleDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} tabName={folder?.name} />
|
||||||
) : (
|
) : (
|
||||||
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
|
<SpecialTab handleCloseClick={handleCloseClick} handleDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} />
|
||||||
)}
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
@@ -144,8 +144,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className="flex items-baseline tab-label pl-2"
|
className={`flex items-baseline tab-label pl-2 ${tab.preview ? "italic" : ""}`}
|
||||||
onContextMenu={handleRightClick}
|
onContextMenu={handleRightClick}
|
||||||
|
onDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))}
|
||||||
onMouseUp={(e) => {
|
onMouseUp={(e) => {
|
||||||
if (!item.draft) return handleMouseUp(e);
|
if (!item.draft) return handleMouseUp(e);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import classnames from 'classnames';
|
|||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { moveItem, showInFolder, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { moveItem, showInFolder, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
@@ -23,7 +23,9 @@ import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
||||||
import CollectionItemIcon from './CollectionItemIcon/index';
|
import { findItemInCollection } from 'utils/collections';
|
||||||
|
import CollectionItemIcon from './CollectionItemIcon';
|
||||||
|
import { scrollToTheActiveTab } from 'utils/tabs';
|
||||||
|
|
||||||
const CollectionItem = ({ item, collection, searchText }) => {
|
const CollectionItem = ({ item, collection, searchText }) => {
|
||||||
const tabs = useSelector((state) => state.tabs.tabs);
|
const tabs = useSelector((state) => state.tabs.tabs);
|
||||||
@@ -83,13 +85,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
'item-hovered': isOver
|
'item-hovered': isOver
|
||||||
});
|
});
|
||||||
|
|
||||||
const scrollToTheActiveTab = () => {
|
|
||||||
const activeTab = document.querySelector('.request-tab.active');
|
|
||||||
if (activeTab) {
|
|
||||||
activeTab.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRun = async () => {
|
const handleRun = async () => {
|
||||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
||||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||||
@@ -99,10 +94,13 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
|
if (event.detail != 1) return;
|
||||||
//scroll to the active tab
|
//scroll to the active tab
|
||||||
setTimeout(scrollToTheActiveTab, 50);
|
setTimeout(scrollToTheActiveTab, 50);
|
||||||
|
|
||||||
if (isItemARequest(item)) {
|
const isRequest = isItemARequest(item);
|
||||||
|
|
||||||
|
if (isRequest) {
|
||||||
dispatch(hideHomePage());
|
dispatch(hideHomePage());
|
||||||
if (itemIsOpenedInTabs(item, tabs)) {
|
if (itemIsOpenedInTabs(item, tabs)) {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -112,20 +110,21 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: item.uid,
|
uid: item.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collection.uid,
|
||||||
requestPaneTab: getDefaultRequestPaneTab(item)
|
requestPaneTab: getDefaultRequestPaneTab(item),
|
||||||
|
type: 'request',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: item.uid,
|
uid: item.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collection.uid,
|
||||||
type: 'folder-settings'
|
type: 'folder-settings',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -134,9 +133,12 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
collectionUid: collection.uid
|
collectionUid: collection.uid
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFolderCollapse = () => {
|
const handleFolderCollapse = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
dispatch(
|
dispatch(
|
||||||
collectionFolderClicked({
|
collectionFolderClicked({
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
@@ -156,10 +158,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDoubleClick = (event) => {
|
|
||||||
setRenameItemModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
let indents = range(item.depth);
|
let indents = range(item.depth);
|
||||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
const isFolder = isItemAFolder(item);
|
const isFolder = isItemAFolder(item);
|
||||||
@@ -180,6 +178,10 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDoubleClick = (event) => {
|
||||||
|
dispatch(makeTabPermanent({ uid: item.uid }))
|
||||||
|
};
|
||||||
|
|
||||||
// we need to sort request items by seq property
|
// we need to sort request items by seq property
|
||||||
const sortRequestItems = (items = []) => {
|
const sortRequestItems = (items = []) => {
|
||||||
return items.sort((a, b) => a.seq - b.seq);
|
return items.sort((a, b) => a.seq - b.seq);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
|
|||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
|
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
|
||||||
import { mountCollection, moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { mountCollection, moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
import NewFolder from 'components/Sidebar/NewFolder';
|
import NewFolder from 'components/Sidebar/NewFolder';
|
||||||
import CollectionItem from './CollectionItem';
|
import CollectionItem from './CollectionItem';
|
||||||
@@ -20,7 +20,8 @@ import { isItemAFolder, isItemARequest } from 'utils/collections';
|
|||||||
import RenameCollection from './RenameCollection';
|
import RenameCollection from './RenameCollection';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import CloneCollection from './CloneCollection';
|
import CloneCollection from './CloneCollection';
|
||||||
import { areItemsLoading } from 'utils/collections';
|
import { areItemsLoading, findItemInCollection } from 'utils/collections';
|
||||||
|
import { scrollToTheActiveTab } from 'utils/tabs';
|
||||||
|
|
||||||
const Collection = ({ collection, searchText }) => {
|
const Collection = ({ collection, searchText }) => {
|
||||||
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
||||||
@@ -29,6 +30,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
|
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
|
||||||
const [showExportCollectionModal, setShowExportCollectionModal] = useState(false);
|
const [showExportCollectionModal, setShowExportCollectionModal] = useState(false);
|
||||||
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
||||||
|
const tabs = useSelector((state) => state.tabs.tabs);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isLoading = areItemsLoading(collection);
|
const isLoading = areItemsLoading(collection);
|
||||||
|
|
||||||
@@ -60,8 +62,10 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
|
if (event.detail != 1) return;
|
||||||
// Check if the click came from the chevron icon
|
// Check if the click came from the chevron icon
|
||||||
const isChevronClick = event.target.closest('svg')?.classList.contains('chevron-icon');
|
const isChevronClick = event.target.closest('svg')?.classList.contains('chevron-icon');
|
||||||
|
setTimeout(scrollToTheActiveTab, 50);
|
||||||
|
|
||||||
if (collection.mountStatus === 'unmounted') {
|
if (collection.mountStatus === 'unmounted') {
|
||||||
dispatch(mountCollection({
|
dispatch(mountCollection({
|
||||||
@@ -70,20 +74,30 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
brunoConfig: collection.brunoConfig
|
brunoConfig: collection.brunoConfig
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(collapseCollection(collection.uid));
|
dispatch(collapseCollection(collection.uid));
|
||||||
|
|
||||||
// Only open collection settings if not clicking the chevron
|
|
||||||
if(!isChevronClick) {
|
if(!isChevronClick) {
|
||||||
dispatch(
|
dispatch(
|
||||||
addTab({
|
addTab({
|
||||||
uid: uuid(),
|
uid: collection.uid,
|
||||||
collectionUid: collection.uid,
|
collectionUid: collection.uid,
|
||||||
type: 'collection-settings'
|
type: 'collection-settings',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick = (event) => {
|
||||||
|
dispatch(makeTabPermanent({ uid: collection.uid }))
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCollectionCollapse = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(collapseCollection(collection.uid));
|
||||||
|
}
|
||||||
|
|
||||||
const handleRightClick = (event) => {
|
const handleRightClick = (event) => {
|
||||||
const _menuDropdown = menuDropdownTippyRef.current;
|
const _menuDropdown = menuDropdownTippyRef.current;
|
||||||
if (_menuDropdown) {
|
if (_menuDropdown) {
|
||||||
@@ -158,6 +172,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
<div
|
<div
|
||||||
className="flex flex-grow items-center overflow-hidden"
|
className="flex flex-grow items-center overflow-hidden"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
onContextMenu={handleRightClick}
|
onContextMenu={handleRightClick}
|
||||||
>
|
>
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
@@ -165,6 +180,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
className={`chevron-icon ${iconClassName}`}
|
className={`chevron-icon ${iconClassName}`}
|
||||||
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
||||||
|
onClick={handleCollectionCollapse}
|
||||||
/>
|
/>
|
||||||
<div className="ml-1" id="sidebar-collection-name">
|
<div className="ml-1" id="sidebar-collection-name">
|
||||||
{collection.name}
|
{collection.name}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import collectionsReducer from './slices/collections';
|
|||||||
import tabsReducer from './slices/tabs';
|
import tabsReducer from './slices/tabs';
|
||||||
import notificationsReducer from './slices/notifications';
|
import notificationsReducer from './slices/notifications';
|
||||||
import globalEnvironmentsReducer from './slices/global-environments';
|
import globalEnvironmentsReducer from './slices/global-environments';
|
||||||
|
import { draftDetectMiddleware } from './middlewares/draft/middleware';
|
||||||
|
|
||||||
const isDevEnv = () => {
|
const isDevEnv = () => {
|
||||||
return import.meta.env.MODE === 'development';
|
return import.meta.env.MODE === 'development';
|
||||||
};
|
};
|
||||||
|
|
||||||
let middleware = [tasksMiddleware.middleware];
|
let middleware = [tasksMiddleware.middleware, draftDetectMiddleware];
|
||||||
if (isDevEnv()) {
|
if (isDevEnv()) {
|
||||||
middleware = [...middleware, debugMiddleware.middleware];
|
middleware = [...middleware, debugMiddleware.middleware];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { handleMakeTabParmanent } from "./utils";
|
||||||
|
|
||||||
|
const actionsToIntercept = [
|
||||||
|
'collections/requestUrlChanged',
|
||||||
|
'collections/updateAuth',
|
||||||
|
'collections/addQueryParam',
|
||||||
|
'collections/moveQueryParam',
|
||||||
|
'collections/updateQueryParam',
|
||||||
|
'collections/deleteQueryParam',
|
||||||
|
'collections/updatePathParam',
|
||||||
|
'collections/addRequestHeader',
|
||||||
|
'collections/updateRequestHeader',
|
||||||
|
'collections/deleteRequestHeader',
|
||||||
|
'collections/moveRequestHeader',
|
||||||
|
'collections/addFormUrlEncodedParam',
|
||||||
|
'collections/updateFormUrlEncodedParam',
|
||||||
|
'collections/deleteFormUrlEncodedParam',
|
||||||
|
'collections/moveFormUrlEncodedParam',
|
||||||
|
'collections/addMultipartFormParam',
|
||||||
|
'collections/updateMultipartFormParam',
|
||||||
|
'collections/deleteMultipartFormParam',
|
||||||
|
'collections/moveMultipartFormParam',
|
||||||
|
'collections/updateRequestAuthMode',
|
||||||
|
'collections/updateRequestBodyMode',
|
||||||
|
'collections/updateRequestBody',
|
||||||
|
'collections/updateRequestGraphqlQuery',
|
||||||
|
'collections/updateRequestGraphqlVariables',
|
||||||
|
'collections/updateRequestScript',
|
||||||
|
'collections/updateResponseScript',
|
||||||
|
'collections/updateRequestTests',
|
||||||
|
'collections/updateRequestMethod',
|
||||||
|
'collections/addAssertion',
|
||||||
|
'collections/updateAssertion',
|
||||||
|
'collections/deleteAssertion',
|
||||||
|
'collections/moveAssertion',
|
||||||
|
'collections/addVar',
|
||||||
|
'collections/updateVar',
|
||||||
|
'collections/deleteVar',
|
||||||
|
'collections/moveVar',
|
||||||
|
'collections/addFolderHeader',
|
||||||
|
'collections/updateFolderHeader',
|
||||||
|
'collections/deleteFolderHeader',
|
||||||
|
'collections/addFolderVar',
|
||||||
|
'collections/updateFolderVar',
|
||||||
|
'collections/deleteFolderVar',
|
||||||
|
'collections/updateRequestDocs'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const draftDetectMiddleware = ({ dispatch, getState }) => (next) => (action) => {
|
||||||
|
if (actionsToIntercept.includes(action.type)) {
|
||||||
|
const state = getState();
|
||||||
|
handleMakeTabParmanent(state, action, dispatch);
|
||||||
|
}
|
||||||
|
return next(action);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { makeTabPermanent } from "providers/ReduxStore/slices/tabs";
|
||||||
|
import { findCollectionByUid, findItemInCollection } from "utils/collections/index";
|
||||||
|
import find from 'lodash/find';
|
||||||
|
|
||||||
|
function handleMakeTabParmanent(state, action, dispatch) {
|
||||||
|
const tabs = state.tabs.tabs;
|
||||||
|
const activeTabUid = state.tabs.activeTabUid;
|
||||||
|
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||||
|
const itemUid = action.payload.itemUid || action.payload.folderUid
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, action.payload.collectionUid);
|
||||||
|
if (collection) {
|
||||||
|
const item = findItemInCollection(collection, itemUid);
|
||||||
|
if (item && focusedTab.preview == true) {
|
||||||
|
dispatch(makeTabPermanent({ uid: itemUid }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
handleMakeTabParmanent
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { findIndex } from 'lodash';
|
||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import last from 'lodash/last';
|
import last from 'lodash/last';
|
||||||
@@ -10,40 +11,55 @@ const initialState = {
|
|||||||
activeTabUid: null
|
activeTabUid: null
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabTypeAlreadyExists = (tabs, collectionUid, type) => {
|
|
||||||
return find(tabs, (tab) => tab.collectionUid === collectionUid && tab.type === type);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const tabsSlice = createSlice({
|
export const tabsSlice = createSlice({
|
||||||
name: 'tabs',
|
name: 'tabs',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addTab: (state, action) => {
|
addTab: (state, action) => {
|
||||||
const alreadyExists = find(state.tabs, (tab) => tab.uid === action.payload.uid);
|
const { uid, collectionUid, type, requestPaneTab, preview } = action.payload;
|
||||||
if (alreadyExists) {
|
|
||||||
|
const existingTab = find(state.tabs, (tab) => tab.uid === uid);
|
||||||
|
|
||||||
|
if (existingTab) {
|
||||||
|
state.activeTabUid = existingTab.uid;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const nonReplaceableTabTypes = [
|
||||||
|
"variables",
|
||||||
|
"collection-runner",
|
||||||
|
"security-settings",
|
||||||
|
];
|
||||||
|
|
||||||
if (
|
const lastTab = state.tabs[state.tabs.length - 1];
|
||||||
['variables', 'collection-settings', 'collection-overview', 'collection-runner', 'security-settings'].includes(action.payload.type)
|
if (state.tabs.length > 0 && lastTab.preview) {
|
||||||
) {
|
state.tabs[state.tabs.length - 1] = {
|
||||||
const tab = tabTypeAlreadyExists(state.tabs, action.payload.collectionUid, action.payload.type);
|
uid,
|
||||||
if (tab) {
|
collectionUid,
|
||||||
state.activeTabUid = tab.uid;
|
requestPaneWidth: null,
|
||||||
return;
|
requestPaneTab: requestPaneTab || 'params',
|
||||||
|
responsePaneTab: 'response',
|
||||||
|
type: type || 'request',
|
||||||
|
preview: true,
|
||||||
|
...(uid ? { folderUid: uid } : {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.activeTabUid = uid;
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state.tabs.push({
|
state.tabs.push({
|
||||||
uid: action.payload.uid,
|
uid,
|
||||||
collectionUid: action.payload.collectionUid,
|
collectionUid,
|
||||||
requestPaneWidth: null,
|
requestPaneWidth: null,
|
||||||
requestPaneTab: action.payload.requestPaneTab || 'params',
|
requestPaneTab: requestPaneTab || 'params',
|
||||||
responsePaneTab: 'response',
|
responsePaneTab: 'response',
|
||||||
type: action.payload.type || 'request',
|
type: type || 'request',
|
||||||
...(action.payload.uid ? { folderUid: action.payload.uid } : {})
|
...(uid ? { folderUid: uid } : {}),
|
||||||
|
preview: preview !== undefined
|
||||||
|
? preview
|
||||||
|
: !nonReplaceableTabTypes.includes(type)
|
||||||
});
|
});
|
||||||
state.activeTabUid = action.payload.uid;
|
state.activeTabUid = uid;
|
||||||
},
|
},
|
||||||
focusTab: (state, action) => {
|
focusTab: (state, action) => {
|
||||||
state.activeTabUid = action.payload.uid;
|
state.activeTabUid = action.payload.uid;
|
||||||
@@ -124,6 +140,15 @@ export const tabsSlice = createSlice({
|
|||||||
const collectionUid = action.payload.collectionUid;
|
const collectionUid = action.payload.collectionUid;
|
||||||
state.tabs = filter(state.tabs, (t) => t.collectionUid !== collectionUid);
|
state.tabs = filter(state.tabs, (t) => t.collectionUid !== collectionUid);
|
||||||
state.activeTabUid = null;
|
state.activeTabUid = null;
|
||||||
|
},
|
||||||
|
makeTabPermanent: (state, action) => {
|
||||||
|
const { uid } = action.payload;
|
||||||
|
const tab = find(state.tabs, (t) => t.uid === uid);
|
||||||
|
if (tab) {
|
||||||
|
tab.preview = false;
|
||||||
|
} else{
|
||||||
|
console.error("Tab not found!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -136,7 +161,8 @@ export const {
|
|||||||
updateRequestPaneTab,
|
updateRequestPaneTab,
|
||||||
updateResponsePaneTab,
|
updateResponsePaneTab,
|
||||||
closeTabs,
|
closeTabs,
|
||||||
closeAllCollectionTabs
|
closeAllCollectionTabs,
|
||||||
|
makeTabPermanent
|
||||||
} = tabsSlice.actions;
|
} = tabsSlice.actions;
|
||||||
|
|
||||||
export default tabsSlice.reducer;
|
export default tabsSlice.reducer;
|
||||||
@@ -11,3 +11,10 @@ export const isItemAFolder = (item) => {
|
|||||||
export const itemIsOpenedInTabs = (item, tabs) => {
|
export const itemIsOpenedInTabs = (item, tabs) => {
|
||||||
return find(tabs, (t) => t.uid === item.uid);
|
return find(tabs, (t) => t.uid === item.uid);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const scrollToTheActiveTab = () => {
|
||||||
|
const activeTab = document.querySelector('.request-tab.active');
|
||||||
|
if (activeTab) {
|
||||||
|
activeTab.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user