From 7faff0339beac03835d2a541fef075c21e1759e1 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Fri, 18 Mar 2022 18:38:49 +0530 Subject: [PATCH] refactor: redux migration - save request --- renderer/components/RequestTabPanel/index.js | 16 +++-- renderer/providers/Hotkeys/index.js | 25 +++++-- .../ReduxStore/slices/collections.js | 53 ++++++++++++++- renderer/providers/ReduxStore/slices/tabs.js | 22 ++++++- renderer/providers/Store/actions.js | 8 --- renderer/providers/Store/index.js | 12 +--- renderer/providers/Store/reducer.js | 66 ------------------- .../Store/useLoadCollectionsFromIdb.js | 20 ------ .../Store/useSyncCollectionsToIdb.js | 41 ------------ renderer/utils/collections/index.js | 61 ++++++++++++++++- renderer/utils/network/index.js | 2 +- 11 files changed, 160 insertions(+), 166 deletions(-) delete mode 100644 renderer/providers/Store/useLoadCollectionsFromIdb.js delete mode 100644 renderer/providers/Store/useSyncCollectionsToIdb.js diff --git a/renderer/components/RequestTabPanel/index.js b/renderer/components/RequestTabPanel/index.js index e56418c73..794178ef2 100644 --- a/renderer/components/RequestTabPanel/index.js +++ b/renderer/components/RequestTabPanel/index.js @@ -9,7 +9,8 @@ import HttpRequestPane from 'components/HttpRequestPane'; import ResponsePane from 'components/ResponsePane'; import Welcome from 'components/Welcome'; import { findItemInCollection } from 'utils/collections'; -import { sendRequest } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, requestUrlChanged } from 'providers/ReduxStore/slices/collections'; +import { requestChanged } from 'providers/ReduxStore/slices/tabs'; import useGraphqlSchema from '../../hooks/useGraphqlSchema'; import StyledWrapper from './StyledWrapper'; @@ -92,12 +93,15 @@ const RequestTabPanel = () => { const item = findItemInCollection(collection, activeTabUid); const onUrlChange = (value) => { - storeDispatch({ - type: actions.REQUEST_URL_CHANGED, - url: value, + dispatch(requestChanged({ itemUid: item.uid, - collectionUid: collection ? collection.uid : null - }); + collectionUid: collection.uid + })); + dispatch(requestUrlChanged({ + itemUid: item.uid, + collectionUid: collection.uid, + url: value + })); }; const runQuery = async () => { diff --git a/renderer/providers/Hotkeys/index.js b/renderer/providers/Hotkeys/index.js index 9482defde..eec833b44 100644 --- a/renderer/providers/Hotkeys/index.js +++ b/renderer/providers/Hotkeys/index.js @@ -1,20 +1,31 @@ import React, { useEffect } from 'react'; +import find from 'lodash/find'; import Mousetrap from 'mousetrap'; -import { useStore } from 'providers/Store'; -import actions from 'providers/Store/actions'; +import { saveRequest } from 'providers/ReduxStore/slices/collections'; +import { requestSaved } from 'providers/ReduxStore/slices/tabs'; +import { useSelector, useDispatch } from 'react-redux'; export const HotkeysContext = React.createContext(); export const HotkeysProvider = props => { - const [store, storeDispatch] = useStore(); + const dispatch = useDispatch(); + const tabs = useSelector((state) => state.tabs.tabs); + const activeTabUid = useSelector((state) => state.tabs.activeTabUid); useEffect(() => { Mousetrap.bind(['command+s', 'ctrl+s'], (e) => { console.log("Save hotkey"); - storeDispatch({ - type: actions.HOTKEY_SAVE - }); + if(activeTabUid) { + const activeTab = find(tabs, (t) => t.uid === activeTabUid); + if(activeTab) { + // todo: these dispatches need to be chained and errors need to be handled + dispatch(saveRequest(activeTab.uid, activeTab.collectionUid)); + dispatch(requestSaved({ + itemUid: activeTab.uid + })) + } + } return false; // this stops the event bubbling }); @@ -22,7 +33,7 @@ export const HotkeysProvider = props => { return () => { Mousetrap.unbind(['command+s', 'ctrl+s']); }; - }, []); + }, [activeTabUid, tabs, saveRequest, requestSaved]); return ( diff --git a/renderer/providers/ReduxStore/slices/collections.js b/renderer/providers/ReduxStore/slices/collections.js index f3df1fb85..b46e1f42e 100644 --- a/renderer/providers/ReduxStore/slices/collections.js +++ b/renderer/providers/ReduxStore/slices/collections.js @@ -1,8 +1,9 @@ import { nanoid } from 'nanoid'; +import cloneDeep from 'lodash/cloneDeep'; import { createSlice } from '@reduxjs/toolkit' import { getCollectionsFromIdb, saveCollectionToIdb } from 'utils/idb'; import { sendNetworkRequest } from 'utils/network'; -import { findCollectionByUid, findItemInCollection } from 'utils/collections'; +import { findCollectionByUid, findItemInCollection, cloneItem, transformCollectionToSaveToIdb } from 'utils/collections'; // todo: errors should be tracked in each slice and displayed as toasts @@ -41,12 +42,39 @@ export const collectionsSlice = createSlice({ } } }, + _saveRequest: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if(collection) { + const item = findItemInCollection(collection, action.payload.itemUid); + + if(item && item.draft) { + item.name = item.draft.name; + item.request = item.draft.request; + item.draft = null; + } + } + }, collectionClicked: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload); if(collection) { collection.collapsed = !collection.collapsed; } + }, + requestUrlChanged: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if(collection) { + const item = findItemInCollection(collection, action.payload.itemUid); + + if(item) { + if(!item.draft) { + item.draft = cloneItem(item); + } + item.draft.request.url = action.payload.url; + } + } } } }); @@ -56,7 +84,9 @@ export const { _createCollection, _requestSent, _responseReceived, - collectionClicked + _saveRequest, + collectionClicked, + requestUrlChanged, } = collectionsSlice.actions; export const loadCollectionsFromIdb = () => (dispatch) => { @@ -93,4 +123,23 @@ export const sendRequest = (item, collectionUid) => (dispatch) => { .catch((err) => console.log(err)); }; +export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + if(collection) { + const collectionCopy = cloneDeep(collection); + const collectionToSave = transformCollectionToSaveToIdb(collectionCopy); + + saveCollectionToIdb(window.__idb, collectionToSave) + .then(() => { + dispatch(_saveRequest({ + itemUid: itemUid, + collectionUid: collectionUid + })); + }) + .catch((err) => console.log(err)); + } +}; + export default collectionsSlice.reducer; diff --git a/renderer/providers/ReduxStore/slices/tabs.js b/renderer/providers/ReduxStore/slices/tabs.js index 5f98deaf6..8d89f3e55 100644 --- a/renderer/providers/ReduxStore/slices/tabs.js +++ b/renderer/providers/ReduxStore/slices/tabs.js @@ -1,3 +1,4 @@ +import find from 'lodash/find'; import filter from 'lodash/filter'; import last from 'lodash/last'; import { createSlice } from '@reduxjs/toolkit' @@ -6,7 +7,8 @@ import { createSlice } from '@reduxjs/toolkit' const initialState = { tabs: [], - activeTabUid: null + activeTabUid: null, + hasChanges: false }; export const tabsSlice = createSlice({ @@ -32,14 +34,28 @@ export const tabsSlice = createSlice({ } else { state.activeTabUid = null; } - } + }, + requestChanged: (state, action) => { + const tab = find(state.tabs, (t) => t.uid == action.payload.itemUid); + if(tab) { + tab.hasChanges = true; + } + }, + requestSaved: (state, action) => { + const tab = find(state.tabs, (t) => t.uid == action.payload.itemUid); + if(tab) { + tab.hasChanges = false; + } + }, } }); export const { addTab, focusTab, - closeTab + closeTab, + requestChanged, + requestSaved } = tabsSlice.actions; export default tabsSlice.reducer; diff --git a/renderer/providers/Store/actions.js b/renderer/providers/Store/actions.js index ce880c571..b6ec973d5 100644 --- a/renderer/providers/Store/actions.js +++ b/renderer/providers/Store/actions.js @@ -1,23 +1,15 @@ const SIDEBAR_COLLECTION_NEW_FOLDER = "SIDEBAR_COLLECTION_NEW_FOLDER"; const SIDEBAR_COLLECTION_NEW_REQUEST = "SIDEBAR_COLLECTION_NEW_REQUEST"; const LOAD_COLLECTIONS_FROM_IDB = "LOAD_COLLECTIONS_FROM_IDB"; -const REQUEST_URL_CHANGED = "REQUEST_URL_CHANGED"; const REQUEST_GQL_QUERY_CHANGED = "REQUEST_GQL_QUERY_CHANGED"; const ADD_NEW_GQL_REQUEST = "ADD_NEW_GQL_REQUEST"; const IDB_CONNECTION_READY = "IDB_CONNECTION_READY"; -const IDB_COLLECTIONS_SYNC_STARTED = "IDB_COLLECTIONS_SYNC_STARTED"; -const IDB_COLLECTIONS_SYNC_ERROR = "IDB_COLLECTIONS_SYNC_ERROR"; -const HOTKEY_SAVE = "HOTKEY_SAVE"; export default { SIDEBAR_COLLECTION_NEW_FOLDER, SIDEBAR_COLLECTION_NEW_REQUEST, LOAD_COLLECTIONS_FROM_IDB, - REQUEST_URL_CHANGED, REQUEST_GQL_QUERY_CHANGED, ADD_NEW_GQL_REQUEST, IDB_CONNECTION_READY, - IDB_COLLECTIONS_SYNC_STARTED, - IDB_COLLECTIONS_SYNC_ERROR, - HOTKEY_SAVE }; diff --git a/renderer/providers/Store/index.js b/renderer/providers/Store/index.js index 3ae70be93..de0a901b0 100644 --- a/renderer/providers/Store/index.js +++ b/renderer/providers/Store/index.js @@ -1,9 +1,7 @@ -import React, { useState, useEffect, useContext, useReducer, createContext } from 'react'; +import React, {useContext, useReducer, createContext } from 'react'; import { nanoid } from 'nanoid'; import reducer from './reducer'; import useIdb from './useIdb'; -import useLoadCollectionsFromIdb from './useLoadCollectionsFromIdb'; -import useSyncCollectionsToIdb from './useSyncCollectionsToIdb'; export const StoreContext = createContext(); const collection = { @@ -121,15 +119,7 @@ const initialState = { export const StoreProvider = props => { const [state, dispatch] = useReducer(reducer, initialState); - const { - collections, - idbConnection, - collectionsToSyncToIdb - } = state; - useIdb(dispatch); - useLoadCollectionsFromIdb(idbConnection, dispatch); - useSyncCollectionsToIdb(collectionsToSyncToIdb, collections, idbConnection, dispatch); return ; }; diff --git a/renderer/providers/Store/reducer.js b/renderer/providers/Store/reducer.js index 6b0a6589e..40bf1b6ba 100644 --- a/renderer/providers/Store/reducer.js +++ b/renderer/providers/Store/reducer.js @@ -19,24 +19,6 @@ const reducer = (state, action) => { }); } - case actions.IDB_COLLECTIONS_SYNC_STARTED: { - return produce(state, (draft) => { - draft.collectionsToSyncToIdb = []; - }); - } - - case actions.IDB_COLLECTIONS_SYNC_ERROR: { - return produce(state, (draft) => { - draft.collectionsToSyncToIdb = union(draft.collectionsToSyncToIdb, action.collectionUids); - }); - } - - case actions.LOAD_COLLECTIONS_FROM_IDB: { - return produce(state, (draft) => { - draft.collections = action.collections; - }); - } - case actions.SIDEBAR_COLLECTION_NEW_REQUEST: { return produce(state, (draft) => { const collection = findCollectionByUid(draft.collections, action.collectionUid); @@ -88,25 +70,6 @@ const reducer = (state, action) => { }); } - case actions.REQUEST_URL_CHANGED: { - 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) { - if(!item.draft) { - item.draft = cloneItem(item); - } - item.draft.request.url = action.url; - updateRequestTabAsChanged(draft.requestTabs, item.uid); - } - } - }); - } - case actions.REQUEST_GQL_QUERY_CHANGED: { return produce(state, (draft) => { const collection = findCollectionByUid(draft.collections, action.collectionUid); @@ -144,35 +107,6 @@ const reducer = (state, action) => { }); } - case actions.HOTKEY_SAVE: { - return produce(state, (draft) => { - if(!draft.activeRequestTabUid) { - return; - } - - // find request tab - const activeRequestTab = find(draft.requestTabs, (t) => t.uid === draft.activeRequestTabUid); - - // resolve item, save and delete draft - if(activeRequestTab) { - const collection = findCollectionByUid(draft.collections, activeRequestTab.collectionUid); - - if(collection) { - let flattenedItems = flattenItems(collection.items); - let item = findItem(flattenedItems, activeRequestTab.uid); - - if(item && item.draft) { - item.name = item.draft.name; - item.request = item.draft.request; - item.draft = null; - activeRequestTab.hasChanges = false; - draft.collectionsToSyncToIdb.push(collection.uid); - } - } - } - }); - } - default: { return state; } diff --git a/renderer/providers/Store/useLoadCollectionsFromIdb.js b/renderer/providers/Store/useLoadCollectionsFromIdb.js deleted file mode 100644 index 710ec7721..000000000 --- a/renderer/providers/Store/useLoadCollectionsFromIdb.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect } from 'react'; -import { getCollectionsFromIdb } from './idb'; -import actions from './actions'; - -const useLoadCollectionsFromIdb = (idbConnection, dispatch) => { - useEffect(() => { - if(idbConnection) { - getCollectionsFromIdb(idbConnection) - .then((collections) => { - dispatch({ - type: actions.LOAD_COLLECTIONS_FROM_IDB, - collections: collections - }); - }) - .catch((err) => console.log(err)); - } - }, [idbConnection, dispatch]); -}; - -export default useLoadCollectionsFromIdb; \ No newline at end of file diff --git a/renderer/providers/Store/useSyncCollectionsToIdb.js b/renderer/providers/Store/useSyncCollectionsToIdb.js deleted file mode 100644 index 31e5a857a..000000000 --- a/renderer/providers/Store/useSyncCollectionsToIdb.js +++ /dev/null @@ -1,41 +0,0 @@ -import { useState, useEffect } from 'react'; -import map from 'lodash/map'; -import filter from 'lodash/filter'; -import actions from './actions'; -import { saveCollectionToIdb } from './idb'; - -// This hook listens to changes in 'collectionsToSyncToIdb' and syncs them to idb -// The app uses this when collections are created as well as when collections get updated -const useSyncCollectionsToIdb = (collectionsToSyncToIdb, collections, idbConnection, dispatch) => { - const [collectionsSyncingToIdb, setCollectionsSyncingToIdb] = useState(false); - - useEffect(() => { - if(collectionsSyncingToIdb) { - return; - } - if(collectionsToSyncToIdb && collectionsToSyncToIdb.length) { - setCollectionsSyncingToIdb(true); - const _collections = filter(collections, (c) => { - return collectionsToSyncToIdb.indexOf(c.uid) > -1; - }); - dispatch({ - type: actions.IDB_COLLECTIONS_SYNC_STARTED - }); - saveCollectionToIdb(idbConnection, _collections) - .then(() => { - setCollectionsSyncingToIdb(false); - }) - .catch((err) => { - setCollectionsSyncingToIdb(false); - dispatch({ - type: actions.IDB_COLLECTIONS_SYNC_ERROR, - collectionUids: map(collections, (c) => c.uid) - }); - console.log(err); - }); - - } - }, [collectionsToSyncToIdb]); -}; - -export default useSyncCollectionsToIdb; \ No newline at end of file diff --git a/renderer/utils/collections/index.js b/renderer/utils/collections/index.js index 590c68705..fbdd8644a 100644 --- a/renderer/utils/collections/index.js +++ b/renderer/utils/collections/index.js @@ -1,5 +1,6 @@ import each from 'lodash/each'; import find from 'lodash/find'; +import cloneDeep from 'lodash/cloneDeep'; export const flattenItems = (items = []) => { const flattenedItems = []; @@ -31,4 +32,62 @@ export const findItemInCollection = (collection, itemUid) => { let flattenedItems = flattenItems(collection.items); return findItem(flattenedItems, itemUid); -} \ No newline at end of file +} + +export const cloneItem = (item) => { + return cloneDeep(item); +}; + +export const transformCollectionToSaveToIdb = (collection) => { + const copyItems = (sourceItems, destItems) => { + each(sourceItems, (si) => { + const di = { + uid: si.uid, + type: si.type + }; + + // if items is draft, then take data from draft to save + if(si.draft) { + di.name = si.draft.name; + + if(si.draft.request) { + di.request = { + url: si.draft.request.url, + method: si.draft.request.method, + headers: si.draft.request.headers, + body: si.draft.request.body + }; + } + } else { + di.name = si.name; + + if(si.request) { + di.request = { + url: si.request.url, + method: si.request.method, + headers: si.request.headers, + body: si.request.body + } + }; + } + + destItems.push(di); + + if(si.items && si.items.length) { + di.items = []; + copyItems(si.items, di.items); + } + }); + } + + const collectionToSave = {}; + collectionToSave.name = collection.name; + collectionToSave.uid = collection.uid; + collectionToSave.userId = collection.userId; + collectionToSave.environments = cloneDeep(collection.environments); + collectionToSave.items = []; + + copyItems(collection.items, collectionToSave.items); + + return collectionToSave; +}; \ No newline at end of file diff --git a/renderer/utils/network/index.js b/renderer/utils/network/index.js index 0f5f1d9ea..9a850debf 100644 --- a/renderer/utils/network/index.js +++ b/renderer/utils/network/index.js @@ -4,7 +4,7 @@ const sendNetworkRequest = async (item) => { return new Promise((resolve, reject) => { if(item.type === 'http-request') { const timeStart = Date.now(); - sendHttpRequest(item.request) + sendHttpRequest(item.draft ? item.draft.request : item.request) .then((response) => { const timeEnd = Date.now(); resolve({