forked from extern/bruno
refactor: redux migration - save request
This commit is contained in:
parent
038eb47dda
commit
7faff0339b
@ -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 () => {
|
||||
|
@ -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 (
|
||||
<HotkeysContext.Provider {...props} value='hotkey'>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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 <StoreContext.Provider value={[state, dispatch]} {...props} />;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user