mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-22 16:03:39 +01:00
request save control
This commit is contained in:
parent
1e27427d09
commit
12f3f802a7
@ -61,19 +61,20 @@ const Modal = ({
|
||||
children,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter
|
||||
hideFooter,
|
||||
closeModalFadeTimeout = 500
|
||||
}) => {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const escFunction = (event) => {
|
||||
const escKeyCode = 27;
|
||||
if (event.keyCode === escKeyCode) {
|
||||
closeModal();
|
||||
closeModal({ type: 'esc' });
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
const closeModal = (args) => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => handleCancel(), 500);
|
||||
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -94,12 +95,12 @@ const Modal = ({
|
||||
return (
|
||||
<StyledWrapper className={classes}>
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<ModalHeader title={title} handleCancel={() => closeModal()} />
|
||||
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} />
|
||||
<ModalContent>{children}</ModalContent>
|
||||
<ModalFooter
|
||||
confirmText={confirmText}
|
||||
cancelText={cancelText}
|
||||
handleCancel={() => closeModal()}
|
||||
handleCancel={() => closeModal({ type: 'button' })}
|
||||
handleSubmit={handleConfirm}
|
||||
confirmDisabled={confirmDisabled}
|
||||
hideCancel={hideCancel}
|
||||
@ -108,7 +109,12 @@ const Modal = ({
|
||||
</div>
|
||||
|
||||
{/* Clicking on backdrop closes the modal */}
|
||||
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
||||
<div
|
||||
className="bruno-modal-backdrop"
|
||||
onClick={() => {
|
||||
closeModal({ type: 'backdrop' });
|
||||
}}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -32,6 +32,50 @@ const Wrapper = styled.div`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
background-color: ${(props) => props.theme.requestTabs.active.bg};
|
||||
color: ${(props) => props.theme.text};
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 34px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tooltiptext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
@ -6,7 +6,9 @@ import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import HttpMethodSelector from './HttpMethodSelector';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import SendIcon from 'components/Icons/Send';
|
||||
import { IconDeviceFloppy } from '@tabler/icons';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
@ -14,6 +16,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const dispatch = useDispatch();
|
||||
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
|
||||
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||
const isMac = isMacOS();
|
||||
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
|
||||
|
||||
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
|
||||
|
||||
@ -22,7 +26,10 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
setMethodSelectorWidth(el.offsetWidth);
|
||||
}, [method]);
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const onSave = () => {
|
||||
dispatch(saveRequest(item.uid, collection.uid));
|
||||
};
|
||||
|
||||
const onUrlChange = (value) => {
|
||||
dispatch(
|
||||
requestUrlChanged({
|
||||
@ -65,6 +72,24 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
collection={collection}
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
||||
<div
|
||||
className="tooltip mr-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!item.draft) return;
|
||||
onSave();
|
||||
}}
|
||||
>
|
||||
<IconDeviceFloppy
|
||||
color={item.draft ? theme.colors.text.yellow : theme.requestTabs.icon.color}
|
||||
strokeWidth={1.5}
|
||||
size={22}
|
||||
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
/>
|
||||
<span className="tooltiptext text-xs">
|
||||
Save <span className="shortcut">({saveShortcut})</span>
|
||||
</span>
|
||||
</div>
|
||||
<SendIcon color={theme.requestTabPanel.url.icon} width={22} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) => {
|
||||
const _handleCancel = ({ type }) => {
|
||||
if (type === 'button') {
|
||||
return onCloseWithoutSave();
|
||||
}
|
||||
|
||||
return onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="sm"
|
||||
title="Unsaved changes"
|
||||
confirmText="Save and Close"
|
||||
cancelText="Close without saving"
|
||||
handleConfirm={onSaveAndClose}
|
||||
handleCancel={_handleCancel}
|
||||
disableEscapeKey={true}
|
||||
disableCloseOnOutsideClick={true}
|
||||
closeModalFadeTimeout={150}
|
||||
>
|
||||
<div className="font-normal">You have unsaved changes in you request.</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmRequestClose;
|
@ -1,10 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { findItemInCollection } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import RequestTabNotFound from './RequestTabNotFound';
|
||||
import ConfirmRequestClose from './ConfirmRequestClose';
|
||||
import SpecialTab from './SpecialTab';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import darkTheme from 'themes/dark';
|
||||
@ -13,6 +16,7 @@ import lightTheme from 'themes/light';
|
||||
const RequestTab = ({ tab, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
||||
|
||||
const handleCloseClick = (event) => {
|
||||
event.stopPropagation();
|
||||
@ -86,6 +90,39 @@ const RequestTab = ({ tab, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
{showConfirmClose && (
|
||||
<ConfirmRequestClose
|
||||
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">
|
||||
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
|
||||
{method}
|
||||
@ -94,7 +131,14 @@ const RequestTab = ({ tab, collection }) => {
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<div
|
||||
className="flex px-2 close-icon-container"
|
||||
onClick={(e) => {
|
||||
if (!item.draft) return handleCloseClick(e);
|
||||
|
||||
setShowConfirmClose(true);
|
||||
}}
|
||||
>
|
||||
{!item.draft ? (
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
|
@ -82,8 +82,12 @@ export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
|
||||
itemSchema
|
||||
.validate(itemToSave)
|
||||
.then(() => ipcRenderer.invoke('renderer:save-request', item.pathname, itemToSave))
|
||||
.then(() => toast.success('Request saved successfully'))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
.catch((err) => {
|
||||
toast.error('Failed to save request!');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -258,6 +258,17 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteRequestDraft: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if (collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if (item && item.draft) {
|
||||
item.draft = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
newEphemeralHttpRequest: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@ -1224,6 +1235,7 @@ export const {
|
||||
requestCancelled,
|
||||
responseReceived,
|
||||
saveRequest,
|
||||
deleteRequestDraft,
|
||||
newEphemeralHttpRequest,
|
||||
collectionClicked,
|
||||
collectionFolderClicked,
|
||||
|
@ -41,3 +41,10 @@ export const isWindowsOS = () => {
|
||||
|
||||
return osFamily.includes('windows');
|
||||
};
|
||||
|
||||
export const isMacOS = () => {
|
||||
const os = platform.os;
|
||||
const osFamily = os.family.toLowerCase();
|
||||
|
||||
return osFamily.includes('os x');
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user