Skip to content
Merged
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
26 changes: 26 additions & 0 deletions cypress/e2e/workflows/changeDetection.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,29 @@ it('detects no changes when the workflow is saved', () => {
}).click();
cy.findByTestId('saveRedDot').should('not.exist');
});

it('prompts a warning when leaving an unsaved workflow', () => {
cy.get('@node').click();
cy.findByRole('textbox', { name: 'Edit label' })
.click()
.type('Always and forever...');

cy.get('@node').click();

cy.findByRole('button', {
name: 'Save workflow to server: changes pending',
}).should('exist');

cy.window().then((win) => cy.spy(win, 'confirm').as('confirm'));

cy.findByRole('link', { name: 'Monitor' }).click();
cy.get('@confirm').should('have.been.calledOnce');

// We should be able to navigate back without a second warning prompt
cy.findAllByRole('link', { name: 'Edit' }).first().click();
cy.location().should((loc) => {
expect(loc.pathname).to.eq('/edit');
expect(loc.search).to.eq('?workflow=tutorial_Graph');
});
cy.get('@confirm').should('have.been.calledOnce');
});
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import SimpleSnackbar from './general/Snackbar';
import MonitorRoute from './MonitorRoute';
import NavBar from './navbar/NavBar';
import SocketClientProvider from './SocketClientProvider';
import { useLocationWithWarningPrompt } from './edition/hooks';

const queryClient = new QueryClient();

export default function App() {
return (
<QueryClientProvider client={queryClient}>
<SocketClientProvider baseUrl={baseUrl} apiSuffix={apiSuffix}>
<Router base={import.meta.env.VITE_ROUTER_BASE_DIR}>
<Router
base={import.meta.env.VITE_ROUTER_BASE_DIR}
hook={useLocationWithWarningPrompt}
>
<SimpleSnackbar />
<CssBaseline />
<NavBar />
Expand Down
36 changes: 35 additions & 1 deletion src/edition/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,46 @@ import { useReactFlow } from '@xyflow/react';
import type { RefCallback } from 'react';
import { useCallback, useState } from 'react';

import { useNodesIds } from '../store/graph-hooks';
import { useNodesIds, useWorkflowHasChanges } from '../store/graph-hooks';
import useNodeDataStore from '../store/useNodeDataStore';
import type { RFNode } from '../types';
import { getNodeData } from '../utils';
import { assertDefined, assertNodeDataDefined } from '../utils/typeGuards';
import { generateNewNodeId } from './utils';
import { type navigate, useBrowserLocation } from 'wouter/use-browser-location';
import { type Path } from 'wouter';
import { useEventListener } from '@react-hookz/web';

export function useLocationWithWarningPrompt(): [Path, typeof navigate] {
const [location, setLocation] = useBrowserLocation();
const workflowHasChanges = useWorkflowHasChanges();

const displayWarning = location === '/edit' && workflowHasChanges;

useEventListener(window, 'beforeunload', (event: BeforeUnloadEvent) => {
if (displayWarning) {
event.preventDefault();
}
});
Comment on lines +21 to +25

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for external navigation so same as https://github.com/ewoks-kit/ewoksweb/pull/789/changes


return [
location,
(newLocation, options) => {
if (!displayWarning) {
setLocation(newLocation, options);
return;
}
// eslint-disable-next-line no-alert
const perfomNavigation = window.confirm(
'There are unsaved changes that will be lost when navigating to another page. Do you want to continue?',
);

if (perfomNavigation) {
setLocation(newLocation, options);
}
},
];
Comment on lines +27 to +43

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For inner navigation

Inspired by molefrog/wouter#39 but without the specific context since on our case, the workflow store already acts as the context

}

export function useCloneNode() {
const rfInstance = useReactFlow();
Expand Down