Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Inconsistent Save Prompts When Closing Request Tabs #4048

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,44 @@ import React from 'react';
import { IconAlertTriangle } from '@tabler/icons';
import Modal from 'components/Modal';

const ConfirmRequestClose = ({ item, onCancel, onCloseWithoutSave, onSaveAndClose }) => {
return (
<Modal
size="md"
title="Unsaved changes"
confirmText="Save and Close"
cancelText="Close without saving"
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
handleCancel={onCancel}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
hideFooter={true}
>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
const ConfirmRequestClose = ({ tabsToClose, onCancel, onCloseWithoutSave, onSaveAndClose }) => (
<Modal
size="md"
title="Unsaved changes"
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
handleCancel={onCancel}
hideFooter={true}
>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
</div>
<div className="font-normal mt-4">
You have unsaved changes in the following requests:
<ul>
{tabsToClose.map(({ item }) => (
<li key={item.uid}>
<span className="font-semibold">{item.name}</span>
</li>
))}
</ul>
</div>
<div className="flex justify-between mt-6">
<button className="btn btn-sm btn-danger" onClick={onCloseWithoutSave}>
Don't Save
</button>
<div>
<button className="btn btn-close btn-sm mr-2" onClick={onCancel}>
Cancel
</button>
<button className="btn btn-secondary btn-sm" onClick={onSaveAndClose}>
Save
</button>
</div>
<div className="font-normal mt-4">
You have unsaved changes in request <span className="font-semibold">{item.name}</span>.
</div>

<div className="flex justify-between mt-6">
<div>
<button className="btn btn-sm btn-danger" onClick={onCloseWithoutSave}>
Don't Save
</button>
</div>
<div>
<button className="btn btn-close btn-sm mr-2" onClick={onCancel}>
Cancel
</button>
<button className="btn btn-secondary btn-sm" onClick={onSaveAndClose}>
Save
</button>
</div>
</div>
</Modal>
);
};
</div>
</Modal>
);

export default ConfirmRequestClose;
101 changes: 33 additions & 68 deletions packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React, { useState, useRef, Fragment } from 'react';
import get from 'lodash/get';
import { closeTabs, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
import { attemptCloseTabs } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import darkTheme from 'themes/dark';
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';
Expand All @@ -18,11 +15,11 @@ import NewRequest from 'components/Sidebar/NewRequest/index';
import CloseTabIcon from './CloseTabIcon';
import DraftTabIcon from './DraftTabIcon';
import { flattenItems } from 'utils/collections/index';
import { closeTabs, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';

const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [showConfirmClose, setShowConfirmClose] = useState(false);

const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
Expand Down Expand Up @@ -109,40 +106,6 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi

return (
<StyledWrapper className="flex items-center justify-between tab-container px-1">
{showConfirmClose && (
<ConfirmRequestClose
item={item}
onCancel={() => setShowConfirmClose(false)}
onCloseWithoutSave={() => {
dispatch(
deleteRequestDraft({
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 ${tab.preview ? "italic" : ""}`}
onContextMenu={handleRightClick}
Expand All @@ -153,7 +116,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
if (e.button === 1) {
e.stopPropagation();
e.preventDefault();
setShowConfirmClose(true);
dispatch(attemptCloseTabs([tab.uid]));
}
}}
>
Expand All @@ -180,7 +143,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi

e.stopPropagation();
e.preventDefault();
setShowConfirmClose(true);
dispatch(attemptCloseTabs([tab.uid]));;
}}
>
{!item.draft ? (
Expand All @@ -204,46 +167,44 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
const hasLeftTabs = tabIndex !== 0;
const hasRightTabs = totalTabs > tabIndex + 1;
const hasOtherTabs = totalTabs > 1;


// Function to handle closing a single tab
async function handleCloseTab(event, tabUid) {
event.stopPropagation();
dropdownTippyRef.current.hide();

if (!tabUid) {
return;
}

try {
const item = findItemInCollection(collection, tabUid);
// silently save unsaved changes before closing the tab
if (item.draft) {
await dispatch(saveRequest(item.uid, collection.uid, true));
}

dispatch(closeTabs({ tabUids: [tabUid] }));
} catch (err) {}
}
if (!tabUid) return;
dispatch(attemptCloseTabs([tabUid]));
};


function handleCloseOtherTabs(event) {
event.stopPropagation();
dropdownTippyRef.current.hide();

const otherTabs = collectionRequestTabs.filter((_, index) => index !== tabIndex);
otherTabs.forEach((tab) => handleCloseTab(event, tab.uid));
const otherTabsUids = collectionRequestTabs.filter((_, index) => index !== tabIndex).map((tab) => tab.uid);
dispatch(attemptCloseTabs(otherTabsUids));
}

function handleCloseTabsToTheLeft(event) {

function handleCloseTabsToTheLeft (event){
event.stopPropagation();
dropdownTippyRef.current.hide();

const leftTabs = collectionRequestTabs.filter((_, index) => index < tabIndex);
leftTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
const leftTabUids = collectionRequestTabs
.filter((_, index) => index < tabIndex)
.map((tab) => tab.uid);
dispatch(attemptCloseTabs(leftTabUids));
};

function handleCloseTabsToTheRight(event) {
function handleCloseTabsToTheRight (event){
event.stopPropagation();
dropdownTippyRef.current.hide();

const rightTabs = collectionRequestTabs.filter((_, index) => index > tabIndex);
rightTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
const leftTabUids = collectionRequestTabs
.filter((_, index) => index > tabIndex)
.map((tab) => tab.uid);
dispatch(attemptCloseTabs(leftTabUids));
};

function handleCloseSavedTabs(event) {
event.stopPropagation();
Expand All @@ -255,7 +216,11 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
}

function handleCloseAllTabs(event) {
collectionRequestTabs.forEach((tab) => handleCloseTab(event, tab.uid));
event.stopPropagation();
dropdownTippyRef.current.hide();

const allTabsUids = collectionRequestTabs.map((tab) => tab.uid);
dispatch(attemptCloseTabs(allTabsUids));
}

return (
Expand Down
30 changes: 29 additions & 1 deletion packages/bruno-app/src/pages/Bruno/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import Welcome from 'components/Welcome';
import RequestTabs from 'components/RequestTabs';
import RequestTabPanel from 'components/RequestTabPanel';
import Sidebar from 'components/Sidebar';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import 'codemirror/theme/material.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/addon/scroll/simplescrollbars.css';
import ConfirmRequestClose from 'components/RequestTabs/RequestTab/ConfirmRequestClose/index';
import { cancelCloseTabs, closeTabsConfirmed } from 'providers/ReduxStore/slices/collections/actions';
import { hideConfirmCloseModal } from 'providers/ReduxStore/slices/tabs';

const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {
Expand Down Expand Up @@ -49,6 +52,25 @@ export default function Main() {
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isDragging = useSelector((state) => state.app.isDragging);
const showHomePage = useSelector((state) => state.app.showHomePage);
const dispatch = useDispatch();
const confirmCloseModal = useSelector((state) => state.tabs.confirmCloseModal);

const { show, tabsToClose } = confirmCloseModal;

const handleCancel = () => {
dispatch(cancelCloseTabs());
};

const handleCloseWithoutSave = () => {
dispatch(closeTabsConfirmed('close-without-saving'));
dispatch(hideConfirmCloseModal())
};

const handleSaveAndClose = () => {
dispatch(closeTabsConfirmed('save-and-close'));
dispatch(hideConfirmCloseModal())
};


// Todo: write a better logging flow that can be used to log by turning on debug flag
// Enable for debugging.
Expand All @@ -67,6 +89,12 @@ export default function Main() {
<Welcome />
) : (
<>
{show && <ConfirmRequestClose
tabsToClose={tabsToClose}
onCancel={handleCancel}
onCloseWithoutSave={handleCloseWithoutSave}
onSaveAndClose={handleSaveAndClose}
/>}
<RequestTabs />
<RequestTabPanel key={activeTabUid} />
</>
Expand Down
10 changes: 3 additions & 7 deletions packages/bruno-app/src/providers/Hotkeys/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
sendRequest,
saveRequest,
saveCollectionRoot,
saveFolderRoot
saveFolderRoot,
attemptCloseTabs
} from 'providers/ReduxStore/slices/collections/actions';
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
import { closeTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
Expand Down Expand Up @@ -138,12 +139,7 @@ export const HotkeysProvider = (props) => {
// close tab hotkey
useEffect(() => {
Mousetrap.bind([...getKeyBindingsForActionAllOS('closeTab')], (e) => {
dispatch(
closeTabs({
tabUids: [activeTabUid]
})
);

dispatch(attemptCloseTabs([activeTabUid]));
return false; // this stops the event bubbling
});

Expand Down
Loading