From d30aaf9e60ebaeb7c2656bf1bbf3cf1a3e3cdd2d Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:18:07 +0200 Subject: [PATCH 1/7] feat: warn when leaving page with selected rows --- src/dashboard/Data/Browser/Browser.react.js | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 1d5cd34da1..849de3200a 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,11 +38,30 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; +import { unstable_usePrompt as usePrompt, useBeforeUnload } from 'react-router-dom'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; +function SelectedRowsNavigationPrompt({ when }) { + usePrompt({ + when, + message: 'There are selected rows. Are you sure you want to leave this page?' + }); + useBeforeUnload( + React.useCallback( + event => { + if (when) { + event.preventDefault(); + } + }, + [when] + ) + ); + return null; +} + // The initial and max amount of rows fetched by lazy loading const BROWSER_LAST_LOCATION = 'brower_last_location'; @@ -2446,6 +2465,9 @@ class Browser extends DashboardView { {pageTitle} + 0} + /> {browser} {notification} {extras} From 1388477aab28f9fff8d791b3790d4b20951401aa Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:34:54 +0200 Subject: [PATCH 2/7] fix: warn when leaving page with selections --- src/dashboard/Data/Browser/Browser.react.js | 46 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 849de3200a..716f9bcbb4 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,17 +38,53 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; -import { unstable_usePrompt as usePrompt, useBeforeUnload } from 'react-router-dom'; +import { + UNSAFE_NavigationContext, + useBeforeUnload, +} from 'react-router-dom'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; +function useBlocker(blocker, when = true) { + const { navigator } = React.useContext(UNSAFE_NavigationContext); + + React.useEffect(() => { + if (!when) { + return; + } + + const unblock = navigator.block(tx => { + const autoUnblockingTx = { + ...tx, + retry() { + unblock(); + tx.retry(); + } + }; + blocker(autoUnblockingTx); + }); + + return unblock; + }, [navigator, blocker, when]); +} + +function usePrompt(message, when = true) { + const blocker = React.useCallback( + tx => { + if (window.confirm(message)) { + tx.retry(); + } + }, + [message] + ); + + useBlocker(blocker, when); +} + function SelectedRowsNavigationPrompt({ when }) { - usePrompt({ - when, - message: 'There are selected rows. Are you sure you want to leave this page?' - }); + usePrompt('There are selected rows. Are you sure you want to leave this page?', when); useBeforeUnload( React.useCallback( event => { From 7170bff1a6c96d7dcebe3aea5b9e8fdd03cdbeeb Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:53:32 +0200 Subject: [PATCH 3/7] fix: warn when leaving page with selections --- src/components/BrowserRow/BrowserRow.react.js | 2 +- src/dashboard/Dashboard.js | 12 +++-- src/dashboard/Data/Browser/Browser.react.js | 47 +++++-------------- src/lib/history.js | 5 ++ 4 files changed, 27 insertions(+), 39 deletions(-) create mode 100644 src/lib/history.js diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index c9993b7a0b..991af6ae22 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -92,7 +92,7 @@ export default class BrowserRow extends Component { > selectRow(obj.id, e.target.checked)} onMouseDown={e => onMouseDownRowCheckBox(e.target.checked)} /> diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js index 96be0559be..fe4a3c428c 100644 --- a/src/dashboard/Dashboard.js +++ b/src/dashboard/Dashboard.js @@ -48,7 +48,13 @@ import { AsyncStatus } from 'lib/Constants'; import baseStyles from 'stylesheets/base.scss'; import { get } from 'lib/AJAX'; import { setBasePath } from 'lib/AJAX'; -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { + unstable_HistoryRouter as HistoryRouter, + Routes, + Route, + Navigate, +} from 'react-router-dom'; +import history from 'lib/history'; import { Helmet } from 'react-helmet'; import Playground from './Data/Playground/Playground.react'; import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react'; @@ -306,7 +312,7 @@ export default class Dashboard extends React.Component { ); return ( - + Parse Dashboard @@ -317,7 +323,7 @@ export default class Dashboard extends React.Component { } /> } /> - + ); } } diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 716f9bcbb4..a02fd0cdce 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,58 +38,35 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; -import { - UNSAFE_NavigationContext, - useBeforeUnload, -} from 'react-router-dom'; +import { useBeforeUnload } from 'react-router-dom'; +import history from 'lib/history'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; -function useBlocker(blocker, when = true) { - const { navigator } = React.useContext(UNSAFE_NavigationContext); - +function SelectedRowsNavigationPrompt({ when }) { + const message = 'There are selected rows. Are you sure you want to leave this page?'; React.useEffect(() => { if (!when) { - return; + return undefined; } - - const unblock = navigator.block(tx => { - const autoUnblockingTx = { - ...tx, - retry() { - unblock(); - tx.retry(); - } - }; - blocker(autoUnblockingTx); - }); - - return unblock; - }, [navigator, blocker, when]); -} - -function usePrompt(message, when = true) { - const blocker = React.useCallback( - tx => { + const unblock = history.block(tx => { if (window.confirm(message)) { + unblock(); tx.retry(); } - }, - [message] - ); - - useBlocker(blocker, when); -} + }); + return unblock; + }, [when]); -function SelectedRowsNavigationPrompt({ when }) { - usePrompt('There are selected rows. Are you sure you want to leave this page?', when); useBeforeUnload( React.useCallback( event => { if (when) { event.preventDefault(); + event.returnValue = message; + return message; } }, [when] diff --git a/src/lib/history.js b/src/lib/history.js new file mode 100644 index 0000000000..564e8ec761 --- /dev/null +++ b/src/lib/history.js @@ -0,0 +1,5 @@ +import { createBrowserHistory } from 'history'; + +const history = createBrowserHistory(); + +export default history; From 78d82fcf30287c15f14275fce3f37497a4c9279d Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:05:56 +0200 Subject: [PATCH 4/7] feat: confirm navigation when rows selected --- src/dashboard/Dashboard.js | 12 +++-------- src/dashboard/Data/Browser/Browser.react.js | 24 ++++++++++++--------- src/lib/history.js | 5 ----- 3 files changed, 17 insertions(+), 24 deletions(-) delete mode 100644 src/lib/history.js diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js index fe4a3c428c..96be0559be 100644 --- a/src/dashboard/Dashboard.js +++ b/src/dashboard/Dashboard.js @@ -48,13 +48,7 @@ import { AsyncStatus } from 'lib/Constants'; import baseStyles from 'stylesheets/base.scss'; import { get } from 'lib/AJAX'; import { setBasePath } from 'lib/AJAX'; -import { - unstable_HistoryRouter as HistoryRouter, - Routes, - Route, - Navigate, -} from 'react-router-dom'; -import history from 'lib/history'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import Playground from './Data/Playground/Playground.react'; import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react'; @@ -312,7 +306,7 @@ export default class Dashboard extends React.Component { ); return ( - + Parse Dashboard @@ -323,7 +317,7 @@ export default class Dashboard extends React.Component { } /> } /> - + ); } } diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index a02fd0cdce..79d5c1094f 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,27 +38,31 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; -import { useBeforeUnload } from 'react-router-dom'; -import history from 'lib/history'; +import { useBeforeUnload, useLocation, useNavigate } from 'react-router-dom'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; function SelectedRowsNavigationPrompt({ when }) { + const location = useLocation(); + const navigate = useNavigate(); + const previousLocation = React.useRef(location); const message = 'There are selected rows. Are you sure you want to leave this page?'; + React.useEffect(() => { if (!when) { - return undefined; + previousLocation.current = location; + return; } - const unblock = history.block(tx => { - if (window.confirm(message)) { - unblock(); - tx.retry(); + if (location !== previousLocation.current) { + if (!window.confirm(message)) { + navigate(-1); + } else { + previousLocation.current = location; } - }); - return unblock; - }, [when]); + } + }, [location, when, navigate]); useBeforeUnload( React.useCallback( diff --git a/src/lib/history.js b/src/lib/history.js deleted file mode 100644 index 564e8ec761..0000000000 --- a/src/lib/history.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createBrowserHistory } from 'history'; - -const history = createBrowserHistory(); - -export default history; From 982523c4f446b10ba7ea4f1853cd017281a05076 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:19:37 +0200 Subject: [PATCH 5/7] fix: show confirmation when leaving with selected rows --- src/dashboard/Data/Browser/Browser.react.js | 33 +++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 79d5c1094f..2a66a589d4 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,32 +38,35 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; -import { useBeforeUnload, useLocation, useNavigate } from 'react-router-dom'; +import { + useBeforeUnload, + UNSAFE_NavigationContext, +} from 'react-router-dom'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; -function SelectedRowsNavigationPrompt({ when }) { - const location = useLocation(); - const navigate = useNavigate(); - const previousLocation = React.useRef(location); - const message = 'There are selected rows. Are you sure you want to leave this page?'; - +function usePrompt(message, when) { + const { navigator } = React.useContext(UNSAFE_NavigationContext); React.useEffect(() => { if (!when) { - previousLocation.current = location; return; } - if (location !== previousLocation.current) { - if (!window.confirm(message)) { - navigate(-1); - } else { - previousLocation.current = location; + const unblock = navigator.block(tx => { + if (window.confirm(message)) { + unblock(); + tx.retry(); } - } - }, [location, when, navigate]); + }); + return unblock; + }, [navigator, message, when]); +} +function SelectedRowsNavigationPrompt({ when }) { + const message = + 'There are selected rows. Are you sure you want to leave this page?'; + usePrompt(message, when); useBeforeUnload( React.useCallback( event => { From 6632f61c7bc9807328450617ed426a2ce99bb604 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:25:41 +0200 Subject: [PATCH 6/7] fix: warn before leaving with selected rows --- src/dashboard/Data/Browser/Browser.react.js | 72 +++++++++++++++------ 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 2a66a589d4..32b4a90b1f 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -38,35 +38,68 @@ import subscribeTo from 'lib/subscribeTo'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import * as ClassPreferences from 'lib/ClassPreferences'; import { Helmet } from 'react-helmet'; -import { - useBeforeUnload, - UNSAFE_NavigationContext, -} from 'react-router-dom'; +import { useBeforeUnload } from 'react-router-dom'; import generatePath from 'lib/generatePath'; import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; -function usePrompt(message, when) { - const { navigator } = React.useContext(UNSAFE_NavigationContext); +function SelectedRowsNavigationPrompt({ when }) { + const message = + 'There are selected rows. Are you sure you want to leave this page?'; + React.useEffect(() => { if (!when) { return; } - const unblock = navigator.block(tx => { - if (window.confirm(message)) { - unblock(); - tx.retry(); + + const handleBeforeUnload = event => { + event.preventDefault(); + event.returnValue = message; + return message; + }; + + const handleLinkClick = event => { + if (event.defaultPrevented) { + return; } - }); - return unblock; - }, [navigator, message, when]); -} + if (event.button !== 0) { + return; + } + if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) { + return; + } + const anchor = event.target.closest('a[href]'); + if (!anchor || anchor.target === '_blank') { + return; + } + const href = anchor.getAttribute('href'); + if (!href || href === '#') { + return; + } + if (!window.confirm(message)) { + event.preventDefault(); + event.stopPropagation(); + } + }; + + const handlePopState = () => { + if (!window.confirm(message)) { + window.history.go(1); + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + document.addEventListener('click', handleLinkClick, true); + window.addEventListener('popstate', handlePopState); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + document.removeEventListener('click', handleLinkClick, true); + window.removeEventListener('popstate', handlePopState); + }; + }, [when, message]); -function SelectedRowsNavigationPrompt({ when }) { - const message = - 'There are selected rows. Are you sure you want to leave this page?'; - usePrompt(message, when); useBeforeUnload( React.useCallback( event => { @@ -76,9 +109,10 @@ function SelectedRowsNavigationPrompt({ when }) { return message; } }, - [when] + [when, message] ) ); + return null; } From f74ba306b9fb9cc3005ee8e388be77fb5690f52e Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:31:05 +0200 Subject: [PATCH 7/7] feat: confirm refresh when rows selected --- src/dashboard/Data/Browser/Browser.react.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 32b4a90b1f..b60df5be4a 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -44,9 +44,11 @@ import { withRouter } from 'lib/withRouter'; import { get } from 'lib/AJAX'; import BrowserFooter from './BrowserFooter.react'; +const SELECTED_ROWS_MESSAGE = + 'There are selected rows. Are you sure you want to leave this page?'; + function SelectedRowsNavigationPrompt({ when }) { - const message = - 'There are selected rows. Are you sure you want to leave this page?'; + const message = SELECTED_ROWS_MESSAGE; React.useEffect(() => { if (!when) { @@ -952,6 +954,11 @@ class Browser extends DashboardView { } async refresh() { + if (Object.keys(this.state.selection).length > 0) { + if (!window.confirm(SELECTED_ROWS_MESSAGE)) { + return; + } + } const relation = this.state.relation; const prevFilters = this.state.filters || new List(); const initialState = {