forked from extern/bruno
[Feature] Prompt user to save requests before exiting app (#1317)
* Starting quit flow and focusing in draft * Finishing app if there is no draft to save * Automatically opening request after creation through event queue * Fix remove events from queue using pathname to find item * Removing updateNextAction * Listening via predicate * Confirm close dialog toggle moved to store * Draft operations as tab actions * Complete quit flow * Fixing close app/window hooks * Breaking the chain when dismissing dialog * Displaying request name in ConfirmRequestClose modal * Added disableEscapeKey and disableCloseOnOutsideClick props to Modal (passed in ConfirmRequestClose) * Removing logs * Refactor * listenerMiddleware module * ipc events listeners names * Update next action * Helpful comments * Eventually handle events to close request even if is no draft * Request name in bold
This commit is contained in:
parent
bdfcd78f3a
commit
85f24eec77
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const ModalHeader = ({ title, handleCancel }) => (
|
const ModalHeader = ({ title, handleCancel }) => (
|
||||||
@ -62,6 +62,8 @@ const Modal = ({
|
|||||||
confirmDisabled,
|
confirmDisabled,
|
||||||
hideCancel,
|
hideCancel,
|
||||||
hideFooter,
|
hideFooter,
|
||||||
|
disableCloseOnOutsideClick,
|
||||||
|
disableEscapeKey,
|
||||||
closeModalFadeTimeout = 500
|
closeModalFadeTimeout = 500
|
||||||
}) => {
|
}) => {
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
@ -78,6 +80,7 @@ const Modal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (disableEscapeKey) return;
|
||||||
document.addEventListener('keydown', escFunction, false);
|
document.addEventListener('keydown', escFunction, false);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -111,9 +114,13 @@ const Modal = ({
|
|||||||
{/* Clicking on backdrop closes the modal */}
|
{/* Clicking on backdrop closes the modal */}
|
||||||
<div
|
<div
|
||||||
className="bruno-modal-backdrop"
|
className="bruno-modal-backdrop"
|
||||||
onClick={() => {
|
onClick={
|
||||||
|
disableCloseOnOutsideClick
|
||||||
|
? null
|
||||||
|
: () => {
|
||||||
closeModal({ type: 'backdrop' });
|
closeModal({ type: 'backdrop' });
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) => {
|
const ConfirmRequestClose = ({ item, onCancel, onCloseWithoutSave, onSaveAndClose }) => {
|
||||||
const _handleCancel = ({ type }) => {
|
const _handleCancel = ({ type }) => {
|
||||||
if (type === 'button') {
|
if (type === 'button') {
|
||||||
return onCloseWithoutSave();
|
return onCloseWithoutSave();
|
||||||
@ -22,7 +22,9 @@ const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) =
|
|||||||
disableCloseOnOutsideClick={true}
|
disableCloseOnOutsideClick={true}
|
||||||
closeModalFadeTimeout={150}
|
closeModalFadeTimeout={150}
|
||||||
>
|
>
|
||||||
<div className="font-normal">You have unsaved changes in your request.</div>
|
<div className="font-normal">
|
||||||
|
You have unsaved changes in request <span className="font-semibold">{item.name}</span>.
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
import {
|
||||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
cancelCloseDraft,
|
||||||
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
closeAndSaveDraft,
|
||||||
import { useDispatch } from 'react-redux';
|
closeTabs,
|
||||||
import { findItemInCollection } from 'utils/collections';
|
closeWithoutSavingDraft,
|
||||||
import StyledWrapper from './StyledWrapper';
|
setShowConfirmClose
|
||||||
import RequestTabNotFound from './RequestTabNotFound';
|
} from 'providers/ReduxStore/slices/tabs';
|
||||||
import ConfirmRequestClose from './ConfirmRequestClose';
|
|
||||||
import SpecialTab from './SpecialTab';
|
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import React from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
import darkTheme from 'themes/dark';
|
import darkTheme from 'themes/dark';
|
||||||
import lightTheme from 'themes/light';
|
import lightTheme from 'themes/light';
|
||||||
|
import { findItemInCollection } from 'utils/collections';
|
||||||
|
import ConfirmRequestClose from './ConfirmRequestClose';
|
||||||
|
import RequestTabNotFound from './RequestTabNotFound';
|
||||||
|
import SpecialTab from './SpecialTab';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const RequestTab = ({ tab, collection }) => {
|
const RequestTab = ({ tab, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
|
||||||
|
|
||||||
const handleCloseClick = (event) => {
|
const handleCloseClick = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -28,6 +31,15 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showConfirmClose = () => {
|
||||||
|
dispatch(
|
||||||
|
setShowConfirmClose({
|
||||||
|
tabUid: tab.uid,
|
||||||
|
showConfirmClose: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getMethodColor = (method = '') => {
|
const getMethodColor = (method = '') => {
|
||||||
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
|
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
|
||||||
|
|
||||||
@ -90,37 +102,12 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||||
{showConfirmClose && (
|
{tab.showConfirmClose && (
|
||||||
<ConfirmRequestClose
|
<ConfirmRequestClose
|
||||||
onCancel={() => setShowConfirmClose(false)}
|
item={item}
|
||||||
onCloseWithoutSave={() => {
|
onCancel={() => dispatch(cancelCloseDraft(item.uid))}
|
||||||
dispatch(
|
onCloseWithoutSave={() => dispatch(closeWithoutSavingDraft(item.uid, collection.uid))}
|
||||||
deleteRequestDraft({
|
onSaveAndClose={() => dispatch(closeAndSaveDraft(item.uid, collection.uid))}
|
||||||
itemUid: item.uid,
|
|
||||||
collectionUid: collection.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
closeTabs({
|
|
||||||
tabUids: [tab.uid]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setShowConfirmClose(false);
|
|
||||||
}}
|
|
||||||
onSaveAndClose={() => {
|
|
||||||
dispatch(saveRequest(item.uid, collection.uid))
|
|
||||||
.then(() => {
|
|
||||||
dispatch(
|
|
||||||
closeTabs({
|
|
||||||
tabUids: [tab.uid]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setShowConfirmClose(false);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log('err', err);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-baseline tab-label pl-2">
|
<div className="flex items-baseline tab-label pl-2">
|
||||||
@ -135,8 +122,7 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
className="flex px-2 close-icon-container"
|
className="flex px-2 close-icon-container"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!item.draft) return handleCloseClick(e);
|
if (!item.draft) return handleCloseClick(e);
|
||||||
|
showConfirmClose();
|
||||||
setShowConfirmClose(true);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!item.draft ? (
|
{!item.draft ? (
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import useTelemetry from './useTelemetry';
|
|
||||||
import useIpcEvents from './useIpcEvents';
|
|
||||||
import useCollectionNextAction from './useCollectionNextAction';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import useIpcEvents from './useIpcEvents';
|
||||||
|
import useTelemetry from './useTelemetry';
|
||||||
|
|
||||||
export const AppContext = React.createContext();
|
export const AppContext = React.createContext();
|
||||||
|
|
||||||
export const AppProvider = (props) => {
|
export const AppProvider = (props) => {
|
||||||
useTelemetry();
|
useTelemetry();
|
||||||
useIpcEvents();
|
useIpcEvents();
|
||||||
useCollectionNextAction();
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import each from 'lodash/each';
|
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
|
||||||
import { getDefaultRequestPaneTab, findItemInCollectionByPathname } from 'utils/collections/index';
|
|
||||||
import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
|
||||||
import { updateNextAction } from 'providers/ReduxStore/slices/collections/index';
|
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
const useCollectionNextAction = () => {
|
|
||||||
const collections = useSelector((state) => state.collections.collections);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
each(collections, (collection) => {
|
|
||||||
if (collection.nextAction && collection.nextAction.type === 'OPEN_REQUEST') {
|
|
||||||
const item = findItemInCollectionByPathname(collection, get(collection, 'nextAction.payload.pathname'));
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
dispatch(updateNextAction({ collectionUid: collection.uid, nextAction: null }));
|
|
||||||
dispatch(
|
|
||||||
addTab({
|
|
||||||
uid: item.uid,
|
|
||||||
collectionUid: collection.uid,
|
|
||||||
requestPaneTab: getDefaultRequestPaneTab(item.type)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(hideHomePage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [collections, each, dispatch, updateNextAction, hideHomePage, addTab]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useCollectionNextAction;
|
|
@ -1,22 +1,22 @@
|
|||||||
import { useEffect } from 'react';
|
import { showPreferences, startQuitFlow, updateCookies, updatePreferences } from 'providers/ReduxStore/slices/app';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import {
|
import {
|
||||||
|
brunoConfigUpdateEvent,
|
||||||
collectionAddDirectoryEvent,
|
collectionAddDirectoryEvent,
|
||||||
collectionAddFileEvent,
|
collectionAddFileEvent,
|
||||||
collectionChangeFileEvent,
|
collectionChangeFileEvent,
|
||||||
collectionUnlinkFileEvent,
|
collectionRenamedEvent,
|
||||||
collectionUnlinkDirectoryEvent,
|
collectionUnlinkDirectoryEvent,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
scriptEnvironmentUpdateEvent,
|
collectionUnlinkFileEvent,
|
||||||
processEnvUpdateEvent,
|
processEnvUpdateEvent,
|
||||||
collectionRenamedEvent,
|
|
||||||
runRequestEvent,
|
|
||||||
runFolderEvent,
|
runFolderEvent,
|
||||||
brunoConfigUpdateEvent
|
runRequestEvent,
|
||||||
|
scriptEnvironmentUpdateEvent
|
||||||
} from 'providers/ReduxStore/slices/collections';
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
import { showPreferences, updatePreferences, updateCookies } from 'providers/ReduxStore/slices/app';
|
import { collectionAddEnvFileEvent, openCollectionEvent } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
|
import { useDispatch } from 'react-redux';
|
||||||
import { isElectron } from 'utils/common/platform';
|
import { isElectron } from 'utils/common/platform';
|
||||||
|
|
||||||
const useIpcEvents = () => {
|
const useIpcEvents = () => {
|
||||||
@ -80,6 +80,7 @@ const useIpcEvents = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.invoke('renderer:ready');
|
ipcRenderer.invoke('renderer:ready');
|
||||||
|
|
||||||
const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);
|
const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);
|
||||||
|
|
||||||
const removeOpenCollectionListener = ipcRenderer.on('main:collection-opened', (pathname, uid, brunoConfig) => {
|
const removeOpenCollectionListener = ipcRenderer.on('main:collection-opened', (pathname, uid, brunoConfig) => {
|
||||||
@ -127,7 +128,7 @@ const useIpcEvents = () => {
|
|||||||
dispatch(brunoConfigUpdateEvent(val))
|
dispatch(brunoConfigUpdateEvent(val))
|
||||||
);
|
);
|
||||||
|
|
||||||
const showPreferencesListener = ipcRenderer.on('main:open-preferences', () => {
|
const removeShowPreferencesListener = ipcRenderer.on('main:open-preferences', () => {
|
||||||
dispatch(showPreferences(true));
|
dispatch(showPreferences(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,6 +140,10 @@ const useIpcEvents = () => {
|
|||||||
dispatch(updateCookies(val));
|
dispatch(updateCookies(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeStartQuitFlowListener = ipcRenderer.on('main:start-quit-flow', () => {
|
||||||
|
dispatch(startQuitFlow());
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
removeCollectionTreeUpdateListener();
|
removeCollectionTreeUpdateListener();
|
||||||
removeOpenCollectionListener();
|
removeOpenCollectionListener();
|
||||||
@ -151,9 +156,10 @@ const useIpcEvents = () => {
|
|||||||
removeProcessEnvUpdatesListener();
|
removeProcessEnvUpdatesListener();
|
||||||
removeConsoleLogListener();
|
removeConsoleLogListener();
|
||||||
removeConfigUpdatesListener();
|
removeConfigUpdatesListener();
|
||||||
showPreferencesListener();
|
removeShowPreferencesListener();
|
||||||
removePreferencesUpdatesListener();
|
removePreferencesUpdatesListener();
|
||||||
removeCookieUpdateListener();
|
removeCookieUpdateListener();
|
||||||
|
removeStartQuitFlowListener();
|
||||||
};
|
};
|
||||||
}, [isElectron]);
|
}, [isElectron]);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import listenerMiddleware from './middlewares/listenerMiddleware';
|
||||||
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';
|
import tabsReducer from './slices/tabs';
|
||||||
@ -8,7 +9,8 @@ export const store = configureStore({
|
|||||||
app: appReducer,
|
app: appReducer,
|
||||||
collections: collectionsReducer,
|
collections: collectionsReducer,
|
||||||
tabs: tabsReducer
|
tabs: tabsReducer
|
||||||
}
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
||||||
|
import { completeQuitFlow, removeEventsFromQueue } from 'providers/ReduxStore/slices/app';
|
||||||
|
import { addTab, closeTabs, focusTab, setShowConfirmClose } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
import {
|
||||||
|
findCollectionByUid,
|
||||||
|
findItemInCollection,
|
||||||
|
findItemInCollectionByPathname,
|
||||||
|
getDefaultRequestPaneTab
|
||||||
|
} from 'utils/collections/index';
|
||||||
|
import { eventMatchesItem, eventTypes } from 'utils/events-queue/index';
|
||||||
|
import { itemIsOpenedInTabs } from 'utils/tabs/index';
|
||||||
|
|
||||||
|
const listenerMiddleware = createListenerMiddleware();
|
||||||
|
|
||||||
|
listenerMiddleware.startListening({
|
||||||
|
predicate: (action) => ['app/insertEventsIntoQueue', 'app/removeEventsFromQueue'].includes(action.type),
|
||||||
|
effect: async (action, listenerApi) => {
|
||||||
|
const state = listenerApi.getState();
|
||||||
|
const { tabs } = state.tabs;
|
||||||
|
|
||||||
|
// after events are added or removed from queue, it will handle the first (if there is any left)
|
||||||
|
const [firstEvent] = state.app.eventsQueue;
|
||||||
|
if (!firstEvent) return;
|
||||||
|
|
||||||
|
if (firstEvent.eventType === eventTypes.CLOSE_APP) {
|
||||||
|
// this events closes the window
|
||||||
|
return listenerApi.dispatch(completeQuitFlow());
|
||||||
|
}
|
||||||
|
|
||||||
|
const { itemUid, itemPathname, collectionUid, eventType } = firstEvent;
|
||||||
|
let eventItem = null;
|
||||||
|
if (firstEvent.eventType === eventTypes.OPEN_REQUEST) {
|
||||||
|
// this event adds or opens a request
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
eventItem = findItemInCollectionByPathname(collection, itemPathname);
|
||||||
|
if (!eventItem) {
|
||||||
|
// waiting until item is added into collection (only happens after IO completes) before handling event
|
||||||
|
// this happens when first opening a request just after creating it
|
||||||
|
await listenerApi.condition((action, currentState, originalState) => {
|
||||||
|
const { collections } = currentState.collections;
|
||||||
|
const collection = findCollectionByUid(collections, collectionUid);
|
||||||
|
const item = findItemInCollectionByPathname(collection, itemPathname);
|
||||||
|
if (item) eventItem = item;
|
||||||
|
return !!item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { collections } = state.collections;
|
||||||
|
const collection = findCollectionByUid(collections, collectionUid);
|
||||||
|
const item = findItemInCollection(collection, itemUid);
|
||||||
|
if (item) eventItem = item;
|
||||||
|
}
|
||||||
|
if (eventItem) {
|
||||||
|
switch (eventType) {
|
||||||
|
case eventTypes.OPEN_REQUEST: // this event adds or opens a request
|
||||||
|
return listenerApi.dispatch(
|
||||||
|
itemIsOpenedInTabs(eventItem, tabs)
|
||||||
|
? focusTab({
|
||||||
|
uid: eventItem.uid
|
||||||
|
})
|
||||||
|
: addTab({
|
||||||
|
uid: eventItem.uid,
|
||||||
|
collectionUid,
|
||||||
|
requestPaneTab: getDefaultRequestPaneTab(eventItem)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
case eventTypes.CLOSE_REQUEST: // this event closes a request or prompts the user to save it if has pending changes
|
||||||
|
return listenerApi.dispatch(
|
||||||
|
eventItem.draft
|
||||||
|
? setShowConfirmClose({
|
||||||
|
tabUid: eventItem.uid,
|
||||||
|
showConfirmClose: true
|
||||||
|
})
|
||||||
|
: closeTabs({
|
||||||
|
tabUids: [eventItem.uid]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listenerMiddleware.startListening({
|
||||||
|
predicate: (action) => ['tabs/addTab', 'tabs/focusTab'].includes(action.type),
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
let { uid, collectionUid } = action.payload;
|
||||||
|
const state = listenerApi.getState();
|
||||||
|
const { eventsQueue } = state.app;
|
||||||
|
const { collections } = state.collections;
|
||||||
|
const { tabs } = state.tabs;
|
||||||
|
|
||||||
|
// after tab is opened, remove corresponding event from start of queue (if any)
|
||||||
|
const [firstEvent] = eventsQueue;
|
||||||
|
if (firstEvent && firstEvent.eventType == eventTypes.OPEN_REQUEST) {
|
||||||
|
collectionUid = collectionUid ?? tabs.find((t) => t.uid === uid).collectionUid;
|
||||||
|
const collection = findCollectionByUid(collections, collectionUid);
|
||||||
|
const item = findItemInCollection(collection, uid);
|
||||||
|
const eventToRemove = eventMatchesItem(firstEvent, item) ? firstEvent : null;
|
||||||
|
if (eventToRemove) {
|
||||||
|
listenerApi.dispatch(removeEventsFromQueue([eventToRemove]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
listenerMiddleware.startListening({
|
||||||
|
actionCreator: closeTabs,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
const state = listenerApi.getState();
|
||||||
|
const { tabUids } = action.payload;
|
||||||
|
const { eventsQueue } = state.app;
|
||||||
|
|
||||||
|
// after tab is closed, remove corresponding event from start of queue (if any)
|
||||||
|
const [firstEvent] = eventsQueue;
|
||||||
|
if (!firstEvent || firstEvent.eventType !== eventTypes.CLOSE_REQUEST) return;
|
||||||
|
const eventToRemove = tabUids.some((uid) => uid === firstEvent.itemUid) ? firstEvent : null;
|
||||||
|
if (eventToRemove) {
|
||||||
|
listenerApi.dispatch(removeEventsFromQueue([eventToRemove]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default listenerMiddleware;
|
@ -1,5 +1,11 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import groupBy from 'lodash/groupBy';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { isItemARequest } from 'utils/collections';
|
||||||
|
import { findCollectionByUid, flattenItems } from 'utils/collections/index';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
import { eventTypes } from 'utils/events-queue/index';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
@ -21,7 +27,8 @@ const initialState = {
|
|||||||
codeFont: 'default'
|
codeFont: 'default'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cookies: []
|
cookies: [],
|
||||||
|
eventsQueue: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appSlice = createSlice({
|
export const appSlice = createSlice({
|
||||||
@ -54,6 +61,19 @@ export const appSlice = createSlice({
|
|||||||
},
|
},
|
||||||
updateCookies: (state, action) => {
|
updateCookies: (state, action) => {
|
||||||
state.cookies = action.payload;
|
state.cookies = action.payload;
|
||||||
|
},
|
||||||
|
insertEventsIntoQueue: (state, action) => {
|
||||||
|
state.eventsQueue = state.eventsQueue.concat(action.payload);
|
||||||
|
},
|
||||||
|
removeEventsFromQueue: (state, action) => {
|
||||||
|
const eventsToRemove = action.payload;
|
||||||
|
state.eventsQueue = filter(
|
||||||
|
state.eventsQueue,
|
||||||
|
(event) => !eventsToRemove.some((e) => e.eventUid === event.eventUid)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
removeAllEventsFromQueue: (state) => {
|
||||||
|
state.eventsQueue = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -67,7 +87,10 @@ export const {
|
|||||||
hideHomePage,
|
hideHomePage,
|
||||||
showPreferences,
|
showPreferences,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
updateCookies
|
updateCookies,
|
||||||
|
insertEventsIntoQueue,
|
||||||
|
removeEventsFromQueue,
|
||||||
|
removeAllEventsFromQueue
|
||||||
} = appSlice.actions;
|
} = appSlice.actions;
|
||||||
|
|
||||||
export const savePreferences = (preferences) => (dispatch, getState) => {
|
export const savePreferences = (preferences) => (dispatch, getState) => {
|
||||||
@ -95,4 +118,55 @@ export const deleteCookiesForDomain = (domain) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const startQuitFlow = () => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
// Before closing the app, checks for unsaved requests (drafts)
|
||||||
|
const currentDrafts = [];
|
||||||
|
const { collections } = state.collections;
|
||||||
|
const { tabs } = state.tabs;
|
||||||
|
|
||||||
|
const tabsByCollection = groupBy(tabs, (t) => t.collectionUid);
|
||||||
|
Object.keys(tabsByCollection).forEach((collectionUid) => {
|
||||||
|
const collectionItems = flattenItems(findCollectionByUid(collections, collectionUid).items);
|
||||||
|
let openedTabs = tabsByCollection[collectionUid];
|
||||||
|
for (const item of collectionItems) {
|
||||||
|
if (isItemARequest(item) && item.draft) {
|
||||||
|
openedTabs = filter(openedTabs, (t) => t.uid !== item.uid);
|
||||||
|
currentDrafts.push({ ...item, collectionUid });
|
||||||
|
}
|
||||||
|
if (!openedTabs.length) return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there are no drafts, closes the window
|
||||||
|
if (currentDrafts.length === 0) {
|
||||||
|
return dispatch(completeQuitFlow());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequence of events tracked by listener middleware
|
||||||
|
// For every draft, it will focus the request and immediately prompt if the user wants to save it
|
||||||
|
// At the end of the sequence, closes the window
|
||||||
|
const events = currentDrafts
|
||||||
|
.reduce((acc, draft) => {
|
||||||
|
const { uid, pathname, collectionUid } = draft;
|
||||||
|
const defaultProperties = { itemUid: uid, collectionUid, itemPathname: pathname };
|
||||||
|
acc.push(
|
||||||
|
...[
|
||||||
|
{ eventUid: uuid(), eventType: eventTypes.OPEN_REQUEST, ...defaultProperties },
|
||||||
|
{ eventUid: uuid(), eventType: eventTypes.CLOSE_REQUEST, ...defaultProperties }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.concat([{ eventUid: uuid(), eventType: eventTypes.CLOSE_APP }]);
|
||||||
|
|
||||||
|
dispatch(insertEventsIntoQueue(events));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const completeQuitFlow = () => (dispatch, getState) => {
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
return ipcRenderer.invoke('main:complete-quit-flow');
|
||||||
|
};
|
||||||
|
|
||||||
export default appSlice.reducer;
|
export default appSlice.reducer;
|
||||||
|
@ -1,51 +1,45 @@
|
|||||||
import path from 'path';
|
import { collectionSchema, environmentSchema, itemSchema } from '@usebruno/schema';
|
||||||
import toast from 'react-hot-toast';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import trim from 'lodash/trim';
|
import filter from 'lodash/filter';
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import filter from 'lodash/filter';
|
import trim from 'lodash/trim';
|
||||||
import { uuid } from 'utils/common';
|
import path from 'path';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import { insertEventsIntoQueue } from 'providers/ReduxStore/slices/app';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import {
|
import {
|
||||||
findItemInCollection,
|
|
||||||
moveCollectionItem,
|
|
||||||
getItemsToResequence,
|
|
||||||
moveCollectionItemToRootOfCollection,
|
|
||||||
findCollectionByUid,
|
findCollectionByUid,
|
||||||
transformRequestToSaveToFilesystem,
|
|
||||||
findParentItemInCollection,
|
|
||||||
findEnvironmentInCollection,
|
findEnvironmentInCollection,
|
||||||
isItemARequest,
|
findItemInCollection,
|
||||||
|
findParentItemInCollection,
|
||||||
|
getItemsToResequence,
|
||||||
isItemAFolder,
|
isItemAFolder,
|
||||||
refreshUidsInItem
|
isItemARequest,
|
||||||
|
moveCollectionItem,
|
||||||
|
moveCollectionItemToRootOfCollection,
|
||||||
|
refreshUidsInItem,
|
||||||
|
transformRequestToSaveToFilesystem
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema';
|
import { uuid, waitForNextTick } from 'utils/common';
|
||||||
import { waitForNextTick } from 'utils/common';
|
import { PATH_SEPARATOR, getDirectoryName } from 'utils/common/platform';
|
||||||
import { getDirectoryName, isWindowsOS, PATH_SEPARATOR } from 'utils/common/platform';
|
import { cancelNetworkRequest, sendNetworkRequest } from 'utils/network';
|
||||||
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
updateLastAction,
|
collectionAddEnvFileEvent as _collectionAddEnvFileEvent,
|
||||||
updateNextAction,
|
|
||||||
resetRunResults,
|
|
||||||
requestCancelled,
|
|
||||||
responseReceived,
|
|
||||||
newItem as _newItem,
|
|
||||||
cloneItem as _cloneItem,
|
|
||||||
deleteItem as _deleteItem,
|
|
||||||
saveRequest as _saveRequest,
|
|
||||||
selectEnvironment as _selectEnvironment,
|
|
||||||
createCollection as _createCollection,
|
createCollection as _createCollection,
|
||||||
renameCollection as _renameCollection,
|
|
||||||
removeCollection as _removeCollection,
|
removeCollection as _removeCollection,
|
||||||
|
selectEnvironment as _selectEnvironment,
|
||||||
sortCollections as _sortCollections,
|
sortCollections as _sortCollections,
|
||||||
collectionAddEnvFileEvent as _collectionAddEnvFileEvent
|
requestCancelled,
|
||||||
|
resetRunResults,
|
||||||
|
responseReceived,
|
||||||
|
updateLastAction
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
|
import { each } from 'lodash';
|
||||||
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { resolveRequestFilename } from 'utils/common/platform';
|
import { resolveRequestFilename } from 'utils/common/platform';
|
||||||
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
import { each } from 'lodash';
|
|
||||||
|
|
||||||
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -595,7 +589,6 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
urlParam.enabled = true;
|
urlParam.enabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
|
||||||
const item = {
|
const item = {
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
type: requestType,
|
type: requestType,
|
||||||
@ -632,18 +625,16 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
||||||
// the useCollectionNextAction() will track this and open the new request in a new tab
|
// listener middleware will track this and open the new request in a new tab once request is created
|
||||||
// once the request is created
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateNextAction({
|
insertEventsIntoQueue([
|
||||||
nextAction: {
|
{
|
||||||
type: 'OPEN_REQUEST',
|
eventUid: uuid(),
|
||||||
payload: {
|
eventType: 'OPEN_REQUEST',
|
||||||
pathname: fullName
|
collectionUid,
|
||||||
|
itemPathname: fullName
|
||||||
}
|
}
|
||||||
},
|
])
|
||||||
collectionUid
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
||||||
@ -662,19 +653,16 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
||||||
|
// listener middleware will track this and open the new request in a new tab once request is created
|
||||||
// the useCollectionNextAction() will track this and open the new request in a new tab
|
|
||||||
// once the request is created
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateNextAction({
|
insertEventsIntoQueue([
|
||||||
nextAction: {
|
{
|
||||||
type: 'OPEN_REQUEST',
|
eventUid: uuid(),
|
||||||
payload: {
|
eventType: 'OPEN_REQUEST',
|
||||||
pathname: fullName
|
collectionUid,
|
||||||
|
itemPathname: fullName
|
||||||
}
|
}
|
||||||
},
|
])
|
||||||
collectionUid
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
import { uuid } from 'utils/common';
|
|
||||||
import find from 'lodash/find';
|
|
||||||
import map from 'lodash/map';
|
|
||||||
import forOwn from 'lodash/forOwn';
|
|
||||||
import concat from 'lodash/concat';
|
|
||||||
import filter from 'lodash/filter';
|
|
||||||
import each from 'lodash/each';
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import set from 'lodash/set';
|
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { splitOnFirst } from 'utils/url';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import concat from 'lodash/concat';
|
||||||
|
import each from 'lodash/each';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import forOwn from 'lodash/forOwn';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import map from 'lodash/map';
|
||||||
|
import set from 'lodash/set';
|
||||||
import {
|
import {
|
||||||
findCollectionByUid,
|
|
||||||
findCollectionByPathname,
|
|
||||||
findItemInCollection,
|
|
||||||
findEnvironmentInCollection,
|
|
||||||
findItemInCollectionByPathname,
|
|
||||||
addDepth,
|
addDepth,
|
||||||
|
areItemsTheSameExceptSeqUpdate,
|
||||||
collapseCollection,
|
collapseCollection,
|
||||||
deleteItemInCollection,
|
deleteItemInCollection,
|
||||||
deleteItemInCollectionByPathname,
|
deleteItemInCollectionByPathname,
|
||||||
isItemARequest,
|
findCollectionByPathname,
|
||||||
areItemsTheSameExceptSeqUpdate
|
findCollectionByUid,
|
||||||
|
findEnvironmentInCollection,
|
||||||
|
findItemInCollection,
|
||||||
|
findItemInCollectionByPathname,
|
||||||
|
isItemARequest
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { parseQueryParams, stringifyQueryParams } from 'utils/url';
|
import { uuid } from 'utils/common';
|
||||||
import { getSubdirectoriesFromRoot, getDirectoryName, PATH_SEPARATOR } from 'utils/common/platform';
|
import { PATH_SEPARATOR, getDirectoryName, getSubdirectoriesFromRoot } from 'utils/common/platform';
|
||||||
|
import { parseQueryParams, splitOnFirst, stringifyQueryParams } from 'utils/url';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
collections: [],
|
collections: [],
|
||||||
@ -50,10 +49,6 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.importedAt = new Date().getTime();
|
collection.importedAt = new Date().getTime();
|
||||||
collection.lastAction = null;
|
collection.lastAction = null;
|
||||||
|
|
||||||
// an improvement over the above approach.
|
|
||||||
// this defines an action that need to be performed next and is executed vy the useCollectionNextAction()
|
|
||||||
collection.nextAction = null;
|
|
||||||
|
|
||||||
collapseCollection(collection);
|
collapseCollection(collection);
|
||||||
addDepth(collection.items);
|
addDepth(collection.items);
|
||||||
if (!collectionUids.includes(collection.uid)) {
|
if (!collectionUids.includes(collection.uid)) {
|
||||||
@ -100,14 +95,6 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.lastAction = lastAction;
|
collection.lastAction = lastAction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateNextAction: (state, action) => {
|
|
||||||
const { collectionUid, nextAction } = action.payload;
|
|
||||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
|
||||||
|
|
||||||
if (collection) {
|
|
||||||
collection.nextAction = nextAction;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateSettingsSelectedTab: (state, action) => {
|
updateSettingsSelectedTab: (state, action) => {
|
||||||
const { collectionUid, tab } = action.payload;
|
const { collectionUid, tab } = action.payload;
|
||||||
|
|
||||||
@ -1394,7 +1381,6 @@ export const {
|
|||||||
removeCollection,
|
removeCollection,
|
||||||
sortCollections,
|
sortCollections,
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
updateNextAction,
|
|
||||||
updateSettingsSelectedTab,
|
updateSettingsSelectedTab,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
saveEnvironment,
|
saveEnvironment,
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import find from 'lodash/find';
|
|
||||||
import filter from 'lodash/filter';
|
|
||||||
import last from 'lodash/last';
|
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import last from 'lodash/last';
|
||||||
|
import { removeAllEventsFromQueue } from 'providers/ReduxStore/slices/app';
|
||||||
|
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { eventTypes } from 'utils/events-queue/index';
|
||||||
|
|
||||||
// todo: errors should be tracked in each slice and displayed as toasts
|
// todo: errors should be tracked in each slice and displayed as toasts
|
||||||
|
|
||||||
@ -101,6 +105,11 @@ 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;
|
||||||
|
},
|
||||||
|
setShowConfirmClose: (state, action) => {
|
||||||
|
const { tabUid, showConfirmClose } = action.payload;
|
||||||
|
const tab = find(state.tabs, (t) => t.uid === tabUid);
|
||||||
|
if (tab) tab.showConfirmClose = showConfirmClose;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -112,7 +121,46 @@ export const {
|
|||||||
updateRequestPaneTab,
|
updateRequestPaneTab,
|
||||||
updateResponsePaneTab,
|
updateResponsePaneTab,
|
||||||
closeTabs,
|
closeTabs,
|
||||||
closeAllCollectionTabs
|
closeAllCollectionTabs,
|
||||||
|
setShowConfirmClose
|
||||||
} = tabsSlice.actions;
|
} = tabsSlice.actions;
|
||||||
|
|
||||||
|
export const closeAndSaveDraft = (itemUid, collectionUid) => (dispatch) => {
|
||||||
|
dispatch(saveRequest(itemUid, collectionUid)).then(() => {
|
||||||
|
dispatch(
|
||||||
|
closeTabs({
|
||||||
|
tabUids: [itemUid]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(setShowConfirmClose({ tabUid: itemUid, showConfirmClose: false }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeWithoutSavingDraft = (itemUid, collectionUid) => (dispatch) => {
|
||||||
|
dispatch(
|
||||||
|
deleteRequestDraft({
|
||||||
|
itemUid: itemUid,
|
||||||
|
collectionUid: collectionUid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
closeTabs({
|
||||||
|
tabUids: [itemUid]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(setShowConfirmClose({ tabUid: itemUid, showConfirmClose: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cancelCloseDraft = (itemUid) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
dispatch(setShowConfirmClose({ tabUid: itemUid, showConfirmClose: false }));
|
||||||
|
|
||||||
|
// check if there was an event to close this tab and aborts the sequence
|
||||||
|
const { eventsQueue } = state.app;
|
||||||
|
const [firstEvent] = eventsQueue;
|
||||||
|
if (firstEvent && firstEvent.eventType === eventTypes.CLOSE_REQUEST && firstEvent.itemUid === itemUid) {
|
||||||
|
dispatch(removeAllEventsFromQueue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default tabsSlice.reducer;
|
export default tabsSlice.reducer;
|
||||||
|
9
packages/bruno-app/src/utils/events-queue/index.js
Normal file
9
packages/bruno-app/src/utils/events-queue/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const eventTypes = {
|
||||||
|
OPEN_REQUEST: 'OPEN_REQUEST',
|
||||||
|
CLOSE_REQUEST: 'CLOSE_REQUEST',
|
||||||
|
CLOSE_APP: 'CLOSE_APP'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const eventMatchesItem = (event, item) => {
|
||||||
|
return event.itemUid === item.uid || event.itemPathname === item.pathname;
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const isDev = require('electron-is-dev');
|
const isDev = require('electron-is-dev');
|
||||||
const { format } = require('url');
|
const { format } = require('url');
|
||||||
const { BrowserWindow, app, Menu } = require('electron');
|
const { BrowserWindow, app, Menu, ipcMain } = require('electron');
|
||||||
const { setContentSecurityPolicy } = require('electron-util');
|
const { setContentSecurityPolicy } = require('electron-util');
|
||||||
|
|
||||||
const menuTemplate = require('./app/menu-template');
|
const menuTemplate = require('./app/menu-template');
|
||||||
@ -97,6 +97,10 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
mainWindow.on('maximize', () => saveMaximized(true));
|
mainWindow.on('maximize', () => saveMaximized(true));
|
||||||
mainWindow.on('unmaximize', () => saveMaximized(false));
|
mainWindow.on('unmaximize', () => saveMaximized(false));
|
||||||
|
mainWindow.on('close', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
ipcMain.emit('main:start-quit-flow');
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow.webContents.on('will-redirect', (event, url) => {
|
mainWindow.webContents.on('will-redirect', (event, url) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { ipcMain, shell, dialog } = require('electron');
|
const { ipcMain, shell, dialog, app } = require('electron');
|
||||||
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -585,6 +585,14 @@ const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) =
|
|||||||
watcher.addWatcher(win, pathname, uid);
|
watcher.addWatcher(win, pathname, uid);
|
||||||
lastOpenedCollections.add(pathname);
|
lastOpenedCollections.add(pathname);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on('main:start-quit-flow', () => {
|
||||||
|
mainWindow.webContents.send('main:start-quit-flow');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('main:complete-quit-flow', () => {
|
||||||
|
mainWindow.destroy();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerCollectionsIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerCollectionsIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user