Feature: Improve tab UX (#3831)

---------
Co-authored-by: ramki-bruno <ramki@usebruno.com>
This commit is contained in:
naman-bruno
2025-02-06 19:34:10 +05:30
committed by GitHub
parent 038f2d1f0b
commit 722d9788ca
9 changed files with 189 additions and 60 deletions

View File

@@ -6,12 +6,13 @@ import collectionsReducer from './slices/collections';
import tabsReducer from './slices/tabs';
import notificationsReducer from './slices/notifications';
import globalEnvironmentsReducer from './slices/global-environments';
import { draftDetectMiddleware } from './middlewares/draft/middleware';
const isDevEnv = () => {
return import.meta.env.MODE === 'development';
};
let middleware = [tasksMiddleware.middleware];
let middleware = [tasksMiddleware.middleware, draftDetectMiddleware];
if (isDevEnv()) {
middleware = [...middleware, debugMiddleware.middleware];
}

View File

@@ -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);
};

View File

@@ -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
}

View File

@@ -1,4 +1,5 @@
import { createSlice } from '@reduxjs/toolkit';
import { findIndex } from 'lodash';
import filter from 'lodash/filter';
import find from 'lodash/find';
import last from 'lodash/last';
@@ -10,40 +11,55 @@ const initialState = {
activeTabUid: null
};
const tabTypeAlreadyExists = (tabs, collectionUid, type) => {
return find(tabs, (tab) => tab.collectionUid === collectionUid && tab.type === type);
};
export const tabsSlice = createSlice({
name: 'tabs',
initialState,
reducers: {
addTab: (state, action) => {
const alreadyExists = find(state.tabs, (tab) => tab.uid === action.payload.uid);
if (alreadyExists) {
const { uid, collectionUid, type, requestPaneTab, preview } = action.payload;
const existingTab = find(state.tabs, (tab) => tab.uid === uid);
if (existingTab) {
state.activeTabUid = existingTab.uid;
return;
}
const nonReplaceableTabTypes = [
"variables",
"collection-runner",
"security-settings",
];
if (
['variables', 'collection-settings', 'collection-overview', 'collection-runner', 'security-settings'].includes(action.payload.type)
) {
const tab = tabTypeAlreadyExists(state.tabs, action.payload.collectionUid, action.payload.type);
if (tab) {
state.activeTabUid = tab.uid;
return;
const lastTab = state.tabs[state.tabs.length - 1];
if (state.tabs.length > 0 && lastTab.preview) {
state.tabs[state.tabs.length - 1] = {
uid,
collectionUid,
requestPaneWidth: null,
requestPaneTab: requestPaneTab || 'params',
responsePaneTab: 'response',
type: type || 'request',
preview: true,
...(uid ? { folderUid: uid } : {})
}
}
state.activeTabUid = uid;
return
}
state.tabs.push({
uid: action.payload.uid,
collectionUid: action.payload.collectionUid,
uid,
collectionUid,
requestPaneWidth: null,
requestPaneTab: action.payload.requestPaneTab || 'params',
requestPaneTab: requestPaneTab || 'params',
responsePaneTab: 'response',
type: action.payload.type || 'request',
...(action.payload.uid ? { folderUid: action.payload.uid } : {})
type: type || 'request',
...(uid ? { folderUid: uid } : {}),
preview: preview !== undefined
? preview
: !nonReplaceableTabTypes.includes(type)
});
state.activeTabUid = action.payload.uid;
state.activeTabUid = uid;
},
focusTab: (state, action) => {
state.activeTabUid = action.payload.uid;
@@ -124,6 +140,15 @@ export const tabsSlice = createSlice({
const collectionUid = action.payload.collectionUid;
state.tabs = filter(state.tabs, (t) => t.collectionUid !== collectionUid);
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,
updateResponsePaneTab,
closeTabs,
closeAllCollectionTabs
closeAllCollectionTabs,
makeTabPermanent
} = tabsSlice.actions;
export default tabsSlice.reducer;
export default tabsSlice.reducer;