refactor: redux migration - request tabs

This commit is contained in:
Anoop M D 2022-03-18 04:43:35 +05:30
parent c8e679b7e0
commit 914927bbfd
10 changed files with 147 additions and 184 deletions

View File

@ -3,24 +3,22 @@ import find from 'lodash/find';
import filter from 'lodash/filter'; import filter from 'lodash/filter';
import classnames from 'classnames'; import classnames from 'classnames';
import { IconHome2 } from '@tabler/icons'; import { IconHome2 } from '@tabler/icons';
import { useStore } from 'providers/Store'; import { useSelector, useDispatch } from 'react-redux';
import actions from 'providers/Store/actions'; import { focusTab, closeTab } from 'providers/ReduxStore/slices/tabs';
import { findItemInCollection } from 'utils/collections';
import CollectionToolBar from './CollectionToolBar'; import CollectionToolBar from './CollectionToolBar';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const RequestTabs = () => { const RequestTabs = () => {
const [store, storeDispatch] = useStore(); const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const { const collections = useSelector((state) => state.collections.collections);
collections, const dispatch = useDispatch();
requestTabs,
activeRequestTabUid
} = store;
const getTabClassname = (tab, index) => { const getTabClassname = (tab, index) => {
return classnames("request-tab select-none", { return classnames("request-tab select-none", {
'active': tab.uid === activeRequestTabUid, 'active': tab.uid === activeTabUid,
'last-tab': requestTabs && requestTabs.length && (index === requestTabs.length - 1) 'last-tab': tabs && tabs.length && (index === tabs.length - 1)
}); });
}; };
@ -41,33 +39,27 @@ const RequestTabs = () => {
}; };
const handleClick = (tab) => { const handleClick = (tab) => {
storeDispatch({ dispatch(focusTab({
type: actions.REQUEST_TAB_CLICK, uid: tab.uid
requestTab: tab }));
});
}; };
const handleCloseClick = (event, tab) => { const handleCloseClick = (event, tab) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
storeDispatch({ dispatch(closeTab(tab.uid))
type: actions.REQUEST_TAB_CLOSE,
requestTab: tab
});
}; };
const createNewTab = () => { const createNewTab = () => {
storeDispatch({ // todo
type: actions.ADD_NEW_HTTP_REQUEST
});
}; };
if(!activeRequestTabUid) { if(!activeTabUid) {
return null; return null;
} }
const activeRequestTab = find(requestTabs, (t) => t.uid === activeRequestTabUid); const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if(!activeRequestTab) { if(!activeTab) {
return ( return (
<StyledWrapper> <StyledWrapper>
Something went wrong! Something went wrong!
@ -75,8 +67,19 @@ const RequestTabs = () => {
); );
} }
const activeCollection = find(collections, (c) => c.uid === activeRequestTab.collectionUid); const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
const collectionRequestTabs = filter(requestTabs, (t) => t.collectionUid === activeRequestTab.collectionUid); const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
const item = findItemInCollection(activeCollection, activeTab.uid);
const getRequestName = (tab) => {
const item = findItemInCollection(activeCollection, tab.uid);
return item.name;
}
const getRequestMethod = (tab) => {
const item = findItemInCollection(activeCollection, tab.uid);
return item.request.name;
}
return ( return (
<StyledWrapper> <StyledWrapper>
@ -90,15 +93,15 @@ const RequestTabs = () => {
<IconHome2 size={18} strokeWidth={1.5}/> <IconHome2 size={18} strokeWidth={1.5}/>
</div> </div>
</li> </li>
{collectionRequestTabs && collectionRequestTabs.length ? collectionRequestTabs.map((rt, index) => { {collectionRequestTabs && collectionRequestTabs.length ? collectionRequestTabs.map((tab, index) => {
return <li key={rt.uid} className={getTabClassname(rt, index)} role="tab" onClick={() => handleClick(rt)}> return <li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}>
<div className="flex items-center justify-between tab-container px-1"> <div className="flex items-center justify-between tab-container px-1">
<div className="flex items-center tab-label pl-2"> <div className="flex items-center tab-label pl-2">
<span className="tab-method" style={{color: getMethodColor(rt.method)}}>{rt.method}</span> <span className="tab-method" style={{color: getMethodColor(getRequestMethod(tab))}}>{getRequestMethod(tab)}</span>
<span className="text-gray-700 ml-1 tab-name">{rt.name}</span> <span className="text-gray-700 ml-1 tab-name">{getRequestName(tab)}</span>
</div> </div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e, rt)}> <div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e, tab)}>
{!rt.hasChanges ? ( {!tab.hasChanges ? (
<svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon"> <svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> <path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path>
</svg> </svg>

View File

@ -1,21 +1,20 @@
import React, { useRef, forwardRef } from 'react'; import React, { useRef, forwardRef } from 'react';
import range from 'lodash/range'; import range from 'lodash/range';
import get from 'lodash/get'; import get from 'lodash/get';
import actions from 'providers/Store/actions'
import { useStore } from 'providers/Store';
import { IconChevronRight, IconDots } from '@tabler/icons'; import { IconChevronRight, IconDots } from '@tabler/icons';
import classnames from 'classnames'; import classnames from 'classnames';
import { useSelector, useDispatch } from 'react-redux';
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
import { isItemARequest, itemIsOpenedInTabs } from 'utils/tabs';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import RequestMethod from './RequestMethod'; import RequestMethod from './RequestMethod';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const CollectionItem = ({item, collectionUid}) => { const CollectionItem = ({item, collection}) => {
const [store, storeDispatch] = useStore(); const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const { const dispatch = useDispatch();
activeRequestTabUid
} = store;
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
@ -31,7 +30,7 @@ const CollectionItem = ({item, collectionUid}) => {
}); });
const itemRowClassName = classnames('flex collection-item-name items-center', { const itemRowClassName = classnames('flex collection-item-name items-center', {
'item-focused-in-tab': item.uid == activeRequestTabUid 'item-focused-in-tab': item.uid == activeTabUid
}); });
const handleClick = (event) => { const handleClick = (event) => {
@ -40,19 +39,20 @@ const CollectionItem = ({item, collectionUid}) => {
return; return;
} }
storeDispatch({ if(isItemARequest(item)) {
type: actions.SIDEBAR_COLLECTION_ITEM_CLICK, if(itemIsOpenedInTabs(item, tabs)) {
itemUid: item.uid, dispatch(focusTab({
collectionUid: collectionUid uid: item.uid
}); }));
}; } else {
dispatch(addTab({
const addRequest = () => { uid: item.uid,
storeDispatch({ collectionUid: collection.uid
type: actions.ADD_REQUEST, }));
itemUid: item.uid, }
collectionUid: collectionUid } else {
}); // todo for folder: must expand folder : item.collapsed = !item.collapsed;
}
}; };
let indents = range(item.depth); let indents = range(item.depth);
@ -100,7 +100,6 @@ const CollectionItem = ({item, collectionUid}) => {
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement='bottom-start'> <Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement='bottom-start'>
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item" onClick={(e) => {
dropdownTippyRef.current.hide(); dropdownTippyRef.current.hide();
addRequest();
}}> }}>
New Request New Request
</div> </div>
@ -130,7 +129,7 @@ const CollectionItem = ({item, collectionUid}) => {
return <CollectionItem return <CollectionItem
key={i.uid} key={i.uid}
item={i} item={i}
collectionUid={collectionUid} collection={collection}
/> />
}) : null} }) : null}
</div> </div>

View File

@ -90,7 +90,7 @@ const Collection = ({collection}) => {
return <CollectionItem return <CollectionItem
key={i.uid} key={i.uid}
item={i} item={i}
collectionUid={collection.uid} collection={collection}
/> />
}) : null} }) : null}
</div> </div>

View File

@ -1,11 +1,13 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import appReducer from './slices/app'; import appReducer from './slices/app';
import collectionsReducer from './slices/collections'; import collectionsReducer from './slices/collections';
import tabsReducer from './slices/tabs';
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
app: appReducer, app: appReducer,
collections: collectionsReducer collections: collectionsReducer,
tabs: tabsReducer
} }
}); });

View File

@ -10,7 +10,7 @@ const initialState = {
}; };
export const collectionsSlice = createSlice({ export const collectionsSlice = createSlice({
name: 'app', name: 'collections',
initialState, initialState,
reducers: { reducers: {
_loadCollections: (state, action) => { _loadCollections: (state, action) => {

View File

@ -0,0 +1,45 @@
import filter from 'lodash/filter';
import last from 'lodash/last';
import { createSlice } from '@reduxjs/toolkit'
// todo: errors should be tracked in each slice and displayed as toasts
const initialState = {
tabs: [],
activeTabUid: null
};
export const tabsSlice = createSlice({
name: 'tabs',
initialState,
reducers: {
addTab: (state, action) => {
state.tabs.push({
uid: action.payload.uid,
collectionUid: action.payload.collectionUid
});
state.activeTabUid = action.payload.uid;
},
focusTab: (state, action) => {
state.activeTabUid = action.payload.uid;
},
closeTab: (state, action) => {
state.tabs = filter(state.tabs, (t) => t.uid !== action.payload);
if(state.tabs && state.tabs.length) {
// todo: closing tab needs to focus on the right adjacent tab
state.activeTabUid = last(state.tabs).uid;
} else {
state.activeTabUid = null;
}
}
}
});
export const {
addTab,
focusTab,
closeTab
} = tabsSlice.actions;
export default tabsSlice.reducer;

View File

@ -1,16 +1,11 @@
const SIDEBAR_COLLECTION_ITEM_CLICK = "SIDEBAR_COLLECTION_ITEM_CLICK";
const SIDEBAR_COLLECTION_NEW_FOLDER = "SIDEBAR_COLLECTION_NEW_FOLDER"; const SIDEBAR_COLLECTION_NEW_FOLDER = "SIDEBAR_COLLECTION_NEW_FOLDER";
const SIDEBAR_COLLECTION_NEW_REQUEST = "SIDEBAR_COLLECTION_NEW_REQUEST"; const SIDEBAR_COLLECTION_NEW_REQUEST = "SIDEBAR_COLLECTION_NEW_REQUEST";
const LOAD_COLLECTIONS_FROM_IDB = "LOAD_COLLECTIONS_FROM_IDB"; const LOAD_COLLECTIONS_FROM_IDB = "LOAD_COLLECTIONS_FROM_IDB";
const REQUEST_TAB_CLICK = "REQUEST_TAB_CLICK";
const REQUEST_TAB_CLOSE = "REQUEST_TAB_CLOSE";
const REQUEST_URL_CHANGED = "REQUEST_URL_CHANGED"; const REQUEST_URL_CHANGED = "REQUEST_URL_CHANGED";
const REQUEST_GQL_QUERY_CHANGED = "REQUEST_GQL_QUERY_CHANGED"; const REQUEST_GQL_QUERY_CHANGED = "REQUEST_GQL_QUERY_CHANGED";
const RESPONSE_RECEIVED = "RESPONSE_RECEIVED"; const RESPONSE_RECEIVED = "RESPONSE_RECEIVED";
const SEND_REQUEST = "SEND_REQUEST"; const SEND_REQUEST = "SEND_REQUEST";
const SENDING_REQUEST = "SENDING_REQUEST"; const SENDING_REQUEST = "SENDING_REQUEST";
const ADD_REQUEST = "ADD_REQUEST";
const ADD_NEW_HTTP_REQUEST = "ADD_NEW_HTTP_REQUEST";
const ADD_NEW_GQL_REQUEST = "ADD_NEW_GQL_REQUEST"; const ADD_NEW_GQL_REQUEST = "ADD_NEW_GQL_REQUEST";
const IDB_CONNECTION_READY = "IDB_CONNECTION_READY"; const IDB_CONNECTION_READY = "IDB_CONNECTION_READY";
const IDB_COLLECTIONS_SYNC_STARTED = "IDB_COLLECTIONS_SYNC_STARTED"; const IDB_COLLECTIONS_SYNC_STARTED = "IDB_COLLECTIONS_SYNC_STARTED";
@ -18,19 +13,14 @@ const IDB_COLLECTIONS_SYNC_ERROR = "IDB_COLLECTIONS_SYNC_ERROR";
const HOTKEY_SAVE = "HOTKEY_SAVE"; const HOTKEY_SAVE = "HOTKEY_SAVE";
export default { export default {
SIDEBAR_COLLECTION_ITEM_CLICK,
SIDEBAR_COLLECTION_NEW_FOLDER, SIDEBAR_COLLECTION_NEW_FOLDER,
SIDEBAR_COLLECTION_NEW_REQUEST, SIDEBAR_COLLECTION_NEW_REQUEST,
LOAD_COLLECTIONS_FROM_IDB, LOAD_COLLECTIONS_FROM_IDB,
REQUEST_TAB_CLICK,
REQUEST_TAB_CLOSE,
REQUEST_URL_CHANGED, REQUEST_URL_CHANGED,
REQUEST_GQL_QUERY_CHANGED, REQUEST_GQL_QUERY_CHANGED,
RESPONSE_RECEIVED, RESPONSE_RECEIVED,
SEND_REQUEST, SEND_REQUEST,
SENDING_REQUEST, SENDING_REQUEST,
ADD_REQUEST,
ADD_NEW_HTTP_REQUEST,
ADD_NEW_GQL_REQUEST, ADD_NEW_GQL_REQUEST,
IDB_CONNECTION_READY, IDB_CONNECTION_READY,
IDB_COLLECTIONS_SYNC_STARTED, IDB_COLLECTIONS_SYNC_STARTED,

View File

@ -2,14 +2,10 @@ import produce from 'immer';
import {nanoid} from 'nanoid'; import {nanoid} from 'nanoid';
import union from 'lodash/union'; import union from 'lodash/union';
import find from 'lodash/find'; import find from 'lodash/find';
import filter from 'lodash/filter';
import last from 'lodash/last';
import actions from './actions'; import actions from './actions';
import { import {
flattenItems, flattenItems,
findItem, findItem,
isItemARequest,
itemIsOpenedInTabs,
cloneItem, cloneItem,
updateRequestTabAsChanged, updateRequestTabAsChanged,
findCollectionByUid findCollectionByUid
@ -41,36 +37,6 @@ const reducer = (state, action) => {
}); });
} }
case actions.SIDEBAR_COLLECTION_ITEM_CLICK: {
return produce(state, (draft) => {
const collection = findCollectionByUid(draft.collections, action.collectionUid);
if(collection) {
let flattenedItems = flattenItems(collection.items);
let item = findItem(flattenedItems, action.itemUid);
if(item) {
item.collapsed = !item.collapsed;
if(isItemARequest(item)) {
if(itemIsOpenedInTabs(item, draft.requestTabs)) {
draft.activeRequestTabUid = item.uid;
} else {
draft.requestTabs.push({
uid: item.uid,
name: item.name,
method: item.request.method,
collectionUid: collection.uid,
hasChanges: false
});
draft.activeRequestTabUid = item.uid;
}
}
}
}
});
}
case actions.SIDEBAR_COLLECTION_NEW_REQUEST: { case actions.SIDEBAR_COLLECTION_NEW_REQUEST: {
return produce(state, (draft) => { return produce(state, (draft) => {
const collection = findCollectionByUid(draft.collections, action.collectionUid); const collection = findCollectionByUid(draft.collections, action.collectionUid);
@ -122,12 +88,6 @@ const reducer = (state, action) => {
}); });
} }
case actions.REQUEST_TAB_CLICK: {
return produce(state, (draft) => {
draft.activeRequestTabUid = action.requestTab.uid;
});
}
case actions.REQUEST_URL_CHANGED: { case actions.REQUEST_URL_CHANGED: {
return produce(state, (draft) => { return produce(state, (draft) => {
const collection = findCollectionByUid(draft.collections, action.collectionUid); const collection = findCollectionByUid(draft.collections, action.collectionUid);
@ -162,24 +122,6 @@ const reducer = (state, action) => {
}); });
} }
case actions.ADD_NEW_HTTP_REQUEST: {
return produce(state, (draft) => {
const uid = nanoid();
draft.requestTabs.push({
uid: uid,
name: 'New Tab',
type: 'http-request',
request: {
method: 'GET',
url: 'https://api.spacex.land/graphql/',
body: {}
},
collectionUid: null
});
draft.activeRequestTabUid = uid;
});
}
case actions.ADD_NEW_GQL_REQUEST: { case actions.ADD_NEW_GQL_REQUEST: {
return produce(state, (draft) => { return produce(state, (draft) => {
const uid = nanoid(); const uid = nanoid();
@ -257,62 +199,6 @@ const reducer = (state, action) => {
}); });
} }
case actions.REQUEST_TAB_CLOSE: {
return produce(state, (draft) => {
draft.requestTabs = filter(draft.requestTabs, (rt) => rt.id !== action.requestTab.id);
if(draft.requestTabs && draft.requestTabs.length) {
// todo: closing tab needs to focus on the right adjacent tab
draft.activeRequestTabUid = last(draft.requestTabs).uid;
} else {
draft.activeRequestTabUid = null;
}
});
}
case actions.ADD_REQUEST: {
return produce(state, (draft) => {
const collection = findCollectionByUid(draft.collections, action.collectionUid);
if(collection) {
let flattenedItems = flattenItems(collection.items);
let item = findItem(flattenedItems, action.itemId);
if(item) {
if(!isItemARequest(item)) {
let newRequest = {
"uid": nanoid(),
"depth": 2,
"name": "Capsules 2",
"type": "graphql-request",
"request": {
"url": "https://api.spacex.land/graphql/",
"method": "POST",
"headers": [],
"body": {
"mimeType": "application/graphql",
"graphql": {
"query": "{\n launchesPast(limit: 10) {\n mission_name\n launch_date_local\n launch_site {\n site_name_long\n }\n links {\n article_link\n video_link\n }\n rocket {\n rocket_name\n first_stage {\n cores {\n flight\n core {\n reuse_count\n status\n }\n }\n }\n second_stage {\n payloads {\n payload_type\n payload_mass_kg\n payload_mass_lbs\n }\n }\n }\n ships {\n name\n home_port\n image\n }\n }\n}",
"variables": ""
}
}
},
"response": null
};
draft.activeRequestTabUid = newRequest.uid;
item.items.push(newRequest);
draft.requestTabs.push({
uid: newRequest.uid,
name: newRequest.name,
method: newRequest.request.method,
collectionUid: collection.id
});
}
}
}
});
}
case actions.HOTKEY_SAVE: { case actions.HOTKEY_SAVE: {
return produce(state, (draft) => { return produce(state, (draft) => {
if(!draft.activeRequestTabUid) { if(!draft.activeRequestTabUid) {

View File

@ -1,5 +1,34 @@
import each from 'lodash/each';
import find from 'lodash/find'; import find from 'lodash/find';
export const flattenItems = (items = []) => {
const flattenedItems = [];
const flatten = (itms, flattened) => {
each(itms, (i) => {
flattened.push(i);
if(i.items && i.items.length) {
flatten(i.items, flattened);
}
})
}
flatten(items, flattenedItems);
return flattenedItems;
};
export const findItem = (items = [], itemUid) => {
return find(items, (i) => i.uid === itemUid);
};
export const findCollectionByUid = (collections, collectionUid) => { export const findCollectionByUid = (collections, collectionUid) => {
return find(collections, (c) => c.uid === collectionUid); return find(collections, (c) => c.uid === collectionUid);
}; };
export const findItemInCollection = (collection, itemUid) => {
let flattenedItems = flattenItems(collection.items);
return findItem(flattenedItems, itemUid);
}

View File

@ -0,0 +1,9 @@
import find from 'lodash/find';
export const isItemARequest = (item) => {
return item.hasOwnProperty('request');
};
export const itemIsOpenedInTabs = (item, tabs) => {
return find(tabs, (t) => t.uid === item.uid);
};