mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-18 17:04:53 -05:00
Warn user of unsaved data output edits before page navigation. #8916
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
@@ -241,6 +241,8 @@ Pagination Options
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Edit Range* | Click to open the from and to rows range inputs to allow setting them. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Show Entire Range* | Click to get all the rows and set the from and to rows range | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Page No* | Enter the page no you want to jump to out of total shown next to this input | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *First Page* | Click to go to the first page. | |
|
||||
|
||||
@@ -286,3 +286,10 @@ export function useBeforeUnload({ enabled, isNewTab, beforeClose, closePanel })
|
||||
|
||||
return {forceClose};
|
||||
}
|
||||
|
||||
export function useLatestFunc(fn) {
|
||||
const fnRef = useRef(fn);
|
||||
fnRef.current = fn;
|
||||
|
||||
return useCallback(((...args) => fnRef.current(...args)), []);
|
||||
}
|
||||
|
||||
@@ -86,19 +86,14 @@ function alert(title, text, onOkClick, okLabel = gettext('OK')) {
|
||||
function confirm(title, text, onOkClick, onCancelClick, okLabel = gettext('Yes'), cancelLabel = gettext('No'), okIcon = 'default', modalId=null) {
|
||||
// bind the modal provider before calling
|
||||
this.showModal(title, (closeModal) => {
|
||||
const onCancelClickClose = () => {
|
||||
onCancelClick?.();
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const onOkClickClose = () => {
|
||||
onOkClick?.();
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
<AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} okLabel={okLabel} cancelLabel={cancelLabel} okIcon={okIcon}/>
|
||||
<AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={closeModal} okLabel={okLabel} cancelLabel={cancelLabel} okIcon={okIcon}/>
|
||||
);
|
||||
}, {id: modalId});
|
||||
}, {id: modalId, onClose: onCancelClick});
|
||||
}
|
||||
|
||||
function confirmDelete(title, text, onDeleteClick, onCancelClick, deleteLabel = gettext('Delete'), cancelLabel = gettext('Cancel')) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { GraphVisualiser } from './GraphVisualiser';
|
||||
import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { connectServer, connectServerModal } from '../connectServer';
|
||||
import { useLatestFunc } from '../../../../../../static/js/custom_hooks';
|
||||
|
||||
const StyledBox = styled(Box)(({theme}) => ({
|
||||
display: 'flex',
|
||||
@@ -846,7 +847,7 @@ export function ResultSet() {
|
||||
const modalId = MODAL_DIALOGS.QT_CONFIRMATIONS;
|
||||
// We'll use this track if any changes were saved.
|
||||
// It will help to decide whether results refresh is required or not on page change.
|
||||
const pageDataDirty = useRef(false);
|
||||
const pageDataOutOfSync = useRef(false);
|
||||
|
||||
const selectedCell = useRef([]);
|
||||
const selectedRange = useRef(null);
|
||||
@@ -873,9 +874,10 @@ export function ResultSet() {
|
||||
// To use setLoaderText to the ResultSetUtils.
|
||||
rsu.current.setLoaderText = setLoaderText;
|
||||
|
||||
const isDataChanged = ()=>{
|
||||
return Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));
|
||||
};
|
||||
const isDataChangedRef = useRef(false);
|
||||
useEffect(()=>{
|
||||
isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));
|
||||
}, [dataChangeStore]);
|
||||
|
||||
const fireRowsColsCellChanged = ()=>{
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, selectedRows.size, selectedColumns.size, selectedRange.current, selectedCell.current?.length);
|
||||
@@ -948,7 +950,7 @@ export function ResultSet() {
|
||||
});
|
||||
};
|
||||
|
||||
if(isDataChanged() && !refreshData) {
|
||||
if(isDataChangedRef.current && !refreshData) {
|
||||
queryToolCtx.modal.confirm(
|
||||
gettext('Unsaved changes'),
|
||||
gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'),
|
||||
@@ -1093,6 +1095,7 @@ export function ResultSet() {
|
||||
setLoaderText(gettext('Fetching rows...'));
|
||||
try {
|
||||
res = await rsu.current.getWindowRows(fromRownum, toRownum);
|
||||
resetSelectionAndChanges();
|
||||
const newRows = rsu.current.processRows(res.data.data.result, columns);
|
||||
setRows([...newRows]);
|
||||
setQueryData((prev)=>({
|
||||
@@ -1119,18 +1122,33 @@ export function ResultSet() {
|
||||
useEffect(()=>{
|
||||
let deregExecEnd;
|
||||
const deregFetch = eventBus.registerListener(QUERY_TOOL_EVENTS.FETCH_WINDOW, (...args)=>{
|
||||
if(pageDataDirty.current) {
|
||||
deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{
|
||||
if(!success) return;
|
||||
pageDataDirty.current = false;
|
||||
const impl = ()=> {
|
||||
if(pageDataOutOfSync.current) {
|
||||
deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{
|
||||
if(!success) return;
|
||||
pageDataOutOfSync.current = false;
|
||||
fetchWindow(...args);
|
||||
}, true);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true});
|
||||
// executionStartCallback(rsu.current.query, {refreshData: true});
|
||||
} else {
|
||||
pageDataOutOfSync.current = false;
|
||||
fetchWindow(...args);
|
||||
}, true);
|
||||
executionStartCallback(rsu.current.query, {refreshData: true});
|
||||
}
|
||||
};
|
||||
|
||||
if(isDataChangedRef.current) {
|
||||
queryToolCtx.modal.confirm(
|
||||
gettext('Unsaved changes'),
|
||||
gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'),
|
||||
impl,
|
||||
function() {
|
||||
/* Do nothing */
|
||||
}
|
||||
);
|
||||
} else {
|
||||
pageDataDirty.current = false;
|
||||
fetchWindow(...args);
|
||||
impl();
|
||||
}
|
||||
resetSelectionAndChanges();
|
||||
});
|
||||
return ()=>{
|
||||
deregFetch();
|
||||
@@ -1144,7 +1162,7 @@ export function ResultSet() {
|
||||
|
||||
const warnSaveDataClose = ()=>{
|
||||
// No changes.
|
||||
if(!isDataChanged() || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) {
|
||||
if(!isDataChangedRef.current || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) {
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_TEXT_CLOSE);
|
||||
return;
|
||||
}
|
||||
@@ -1172,7 +1190,7 @@ export function ResultSet() {
|
||||
};
|
||||
}, [dataChangeStore]);
|
||||
|
||||
const triggerSaveData = async ()=>{
|
||||
const triggerSaveData = useLatestFunc(async ()=>{
|
||||
if(!_.size(dataChangeStore.updated) && !_.size(dataChangeStore.added) && !_.size(dataChangeStore.deleted)) {
|
||||
return;
|
||||
}
|
||||
@@ -1210,7 +1228,7 @@ export function ResultSet() {
|
||||
} catch {/* History errors should not bother others */}
|
||||
|
||||
if(!respData.data.status) {
|
||||
pageDataDirty.current = false;
|
||||
pageDataOutOfSync.current = false;
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, respData.data.result);
|
||||
pgAdmin.Browser.notifier.error(respData.data.result, 20000);
|
||||
// If the transaction is not idle, notify the user that previous queries are not rolled back,
|
||||
@@ -1223,7 +1241,7 @@ export function ResultSet() {
|
||||
return;
|
||||
}
|
||||
|
||||
pageDataDirty.current = true;
|
||||
pageDataOutOfSync.current = true;
|
||||
if(_.size(dataChangeStore.added)) {
|
||||
// Update the rows in a grid after addition
|
||||
respData.data.query_results.forEach((qr)=>{
|
||||
@@ -1258,18 +1276,19 @@ export function ResultSet() {
|
||||
resetSelectionAndChanges();
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_CONNECTION_STATUS, respData.data.transaction_status);
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, '');
|
||||
setLoaderText(null);
|
||||
pgAdmin.Browser.notifier.success(gettext('Data saved successfully.'));
|
||||
if(respData.data.transaction_status > CONNECTION_STATUS.TRANSACTION_STATUS_IDLE) {
|
||||
pgAdmin.Browser.notifier.info(gettext('Auto-commit is off. You still need to commit changes to the database.'));
|
||||
}
|
||||
} catch (error) {
|
||||
pageDataDirty.current = false;
|
||||
pageDataOutOfSync.current = false;
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, error, {
|
||||
checkTransaction: true,
|
||||
});
|
||||
setLoaderText(null);
|
||||
}
|
||||
setLoaderText(null);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(()=>{
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);
|
||||
|
||||
@@ -690,7 +690,8 @@ WHERE db.oid = {0}""".format(did))
|
||||
if key == 'hostaddr' and self.use_ssh_tunnel:
|
||||
continue
|
||||
|
||||
# Convert boolean connection parameters to integer for libpq compatibility
|
||||
# Convert boolean connection parameters to integer for
|
||||
# libpq compatibility
|
||||
if key in ('sslcompression', 'sslsni'):
|
||||
value = 1 if value else 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user