Skip to content

Commit db4b8eb

Browse files
committedMar 28, 2024·
Prompt the user when navigating away from a dirty editor
1 parent 856e97d commit db4b8eb

File tree

4 files changed

+104
-82
lines changed

4 files changed

+104
-82
lines changed
 

‎ui/ui-app/src/app/App.tsx

+16-13
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,32 @@ import "@patternfly/patternfly/patternfly.css";
33
import "@patternfly/patternfly/patternfly-addons.css";
44

55
import React from "react";
6-
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
6+
import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from "react-router-dom";
77
import { EditorPage, EmbeddedEditorPage, HomePage } from "@app/pages";
8-
import { AppHeader } from "@app/components";
9-
import { Page } from "@patternfly/react-core";
10-
import { ApplicationAuth, AuthConfigContext, AuthConfig } from "@apicurio/common-ui-components";
8+
import { ApplicationAuth, AuthConfig, AuthConfigContext } from "@apicurio/common-ui-components";
119
import { ApicurioStudioConfig, useConfigService } from "@services/useConfigService.ts";
1210

1311

1412
export const App: React.FunctionComponent = () => {
1513
const appConfig: ApicurioStudioConfig = useConfigService().getApicurioStudioConfig();
1614

15+
const router = createBrowserRouter(
16+
createRoutesFromElements(
17+
<>
18+
<Route path="/" element={<HomePage />}/>,
19+
<Route path="/designs/:designId/editor" element={<EditorPage />}/>,
20+
<Route path="/editor-embedded" element={<EmbeddedEditorPage />}/>,
21+
</>
22+
),
23+
{
24+
basename: appConfig.ui.contextPath
25+
}
26+
);
27+
1728
return (
1829
<AuthConfigContext.Provider value={appConfig.auth as AuthConfig}>
1930
<ApplicationAuth>
20-
<Router basename={appConfig.ui.contextPath}>
21-
<Page className="pf-m-redhat-font" isManagedSidebar={false} header={<AppHeader />}>
22-
<Routes>
23-
<Route path="/" element={<HomePage />}/>
24-
<Route path="/designs/:designId/editor" element={<EditorPage />}/>
25-
<Route path="/editor-embedded" element={<EmbeddedEditorPage />}/>
26-
</Routes>
27-
</Page>
28-
</Router>
31+
<RouterProvider router={router} />
2932
</ApplicationAuth>
3033
</AuthConfigContext.Provider>
3134
);

‎ui/ui-app/src/app/pages/EditorPage.tsx

+44-38
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import React, { CSSProperties, FunctionComponent, useEffect, useState } from "react";
2-
import {
3-
PageSection,
4-
PageSectionVariants
5-
} from "@patternfly/react-core";
2+
import { Page, PageSection, PageSectionVariants } from "@patternfly/react-core";
63
import { ArtifactTypes, ContentTypes, Design, DesignContent } from "@models/designs";
74
import { DesignsService, useDesignsService } from "@services/useDesignsService.ts";
85
import { DownloadService, useDownloadService } from "@services/useDownloadService.ts";
@@ -18,9 +15,10 @@ import { ProtoEditor } from "@editors/ProtoEditor.tsx";
1815
import { OpenApiEditor } from "@editors/OpenApiEditor.tsx";
1916
import { AsyncApiEditor } from "@editors/AsyncApiEditor.tsx";
2017
import { CompareModal, DeleteDesignModal, EditorContext, RenameData, RenameModal } from "@app/pages/components";
21-
import { useParams } from "react-router-dom";
18+
import { unstable_usePrompt, useParams } from "react-router-dom";
2219
import { IfNotLoading } from "@apicurio/common-ui-components";
2320
import { AppNavigationService, useAppNavigation } from "@services/useAppNavigation.ts";
21+
import { AppHeader } from "@app/components";
2422

2523
const sectionContextStyle: CSSProperties = {
2624
borderBottom: "1px solid #ccc",
@@ -113,6 +111,12 @@ export const EditorPage: FunctionComponent<EditorPageProps> = () => {
113111
}
114112
}, [isDirty]);
115113

114+
// Add a react router prompt that will be used when *navigating* but the editor is dirty
115+
unstable_usePrompt({
116+
message: "You have unsaved changes. Do you really want to navigate away and lose them?",
117+
when: () => isDirty
118+
});
119+
116120
// Called when the user makes an edit in the editor.
117121
const onEditorChange = (value: any): void => {
118122
setCurrentContent(value);
@@ -230,39 +234,41 @@ export const EditorPage: FunctionComponent<EditorPageProps> = () => {
230234
};
231235

232236
return (
233-
<IfNotLoading isLoading={isLoading}>
234-
<PageSection variant={PageSectionVariants.light} id="section-context" style={sectionContextStyle}>
235-
<EditorContext
236-
design={design as Design}
237-
dirty={isDirty}
238-
onSave={onSave}
239-
onFormat={onFormat}
240-
onDelete={onDelete}
237+
<Page className="pf-m-redhat-font" isManagedSidebar={false} header={<AppHeader />}>
238+
<IfNotLoading isLoading={isLoading}>
239+
<PageSection variant={PageSectionVariants.light} id="section-context" style={sectionContextStyle}>
240+
<EditorContext
241+
design={design as Design}
242+
dirty={isDirty}
243+
onSave={onSave}
244+
onFormat={onFormat}
245+
onDelete={onDelete}
246+
onDownload={onDownload}
247+
onRename={() => setRenameModalOpen(true)}
248+
onCompareContent={onCompareContent}
249+
artifactContent={currentContent}
250+
/>
251+
</PageSection>
252+
<PageSection variant={PageSectionVariants.light} id="section-editor" style={sectionEditorStyle}>
253+
<div className="editor-parent" style={editorParentStyle} children={editor() as any} />
254+
</PageSection>
255+
<CompareModal isOpen={isCompareModalOpen}
256+
onClose={closeCompareEditor}
257+
before={originalContent}
258+
beforeName={design?.name || ""}
259+
after={currentContent}
260+
afterName={design?.name || ""}/>
261+
<RenameModal design={design}
262+
isOpen={isRenameModalOpen}
263+
onRename={doRenameDesign}
264+
onCancel={() => setRenameModalOpen(false)}/>
265+
<DeleteDesignModal design={design}
266+
isOpen={isDeleteModalOpen}
267+
onDelete={onDeleteDesignConfirmed}
241268
onDownload={onDownload}
242-
onRename={() => setRenameModalOpen(true)}
243-
onCompareContent={onCompareContent}
244-
artifactContent={currentContent}
245-
/>
246-
</PageSection>
247-
<PageSection variant={PageSectionVariants.light} id="section-editor" style={sectionEditorStyle}>
248-
<div className="editor-parent" style={editorParentStyle} children={editor() as any} />
249-
</PageSection>
250-
<CompareModal isOpen={isCompareModalOpen}
251-
onClose={closeCompareEditor}
252-
before={originalContent}
253-
beforeName={design?.name || ""}
254-
after={currentContent}
255-
afterName={design?.name || ""}/>
256-
<RenameModal design={design}
257-
isOpen={isRenameModalOpen}
258-
onRename={doRenameDesign}
259-
onCancel={() => setRenameModalOpen(false)}/>
260-
<DeleteDesignModal design={design}
261-
isOpen={isDeleteModalOpen}
262-
onDelete={onDeleteDesignConfirmed}
263-
onDownload={onDownload}
264-
onCancel={() => setDeleteModalOpen(false)}/>
265-
{/*<Prompt when={isDirty} message={ "You have unsaved changes. Do you really want to leave?" }/>*/}
266-
</IfNotLoading>
269+
onCancel={() => setDeleteModalOpen(false)}/>
270+
{/*<Prompt when={isDirty} message={ "You have unsaved changes. Do you really want to leave?" }/>*/}
271+
</IfNotLoading>
272+
</Page>
267273
);
268274
};

‎ui/ui-app/src/app/pages/EmbeddedEditorPage.tsx

+39-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import React, { CSSProperties, FunctionComponent, useEffect, useState } from "react";
2-
import { Button, PageSection, PageSectionVariants, Toolbar, ToolbarContent, ToolbarItem } from "@patternfly/react-core";
2+
import {
3+
Button,
4+
Page,
5+
PageSection,
6+
PageSectionVariants,
7+
Toolbar,
8+
ToolbarContent,
9+
ToolbarItem
10+
} from "@patternfly/react-core";
311
import { ArtifactTypes, ContentTypes, DesignContent } from "@models/designs";
412
import { TextEditor } from "@editors/TextEditor.tsx";
513
import { ProtoEditor } from "@editors/ProtoEditor.tsx";
@@ -8,6 +16,7 @@ import { AsyncApiEditor } from "@editors/AsyncApiEditor.tsx";
816
import { CompareModal } from "@app/pages/components";
917
import { IfNotLoading } from "@apicurio/common-ui-components";
1018
import { formatContent, toJsonString, toYamlString } from "@utils/content.utils.ts";
19+
import { AppHeader } from "@app/components";
1120

1221
const sectionContextStyle: CSSProperties = {
1322
borderBottom: "1px solid #ccc",
@@ -169,32 +178,34 @@ export const EmbeddedEditorPage: FunctionComponent<EmbeddedEditorPageProps> = ()
169178
};
170179

171180
return (
172-
<IfNotLoading isLoading={isLoading}>
173-
<PageSection variant={PageSectionVariants.light} id="section-context" style={sectionContextStyle}>
174-
<Toolbar id="embedded-editor-toolbar">
175-
<ToolbarContent>
176-
<ToolbarItem>
177-
<Button variant="secondary" isDisabled={!isDirty} onClick={onCompareContent}>Diff changes</Button>
178-
</ToolbarItem>
179-
<ToolbarItem>
180-
<Button variant="secondary" onClick={onFormat}>Format content</Button>
181-
</ToolbarItem>
182-
<ToolbarItem variant="separator" />
183-
<ToolbarItem>
184-
<Button variant="primary" isDisabled={!isDirty} onClick={onSave}>Save</Button>
185-
</ToolbarItem>
186-
</ToolbarContent>
187-
</Toolbar>
188-
</PageSection>
189-
<PageSection variant={PageSectionVariants.light} id="section-editor" style={sectionEditorStyle}>
190-
<div className="editor-parent" style={editorParentStyle} children={editor() as any} />
191-
</PageSection>
192-
<CompareModal isOpen={isCompareModalOpen}
193-
onClose={closeCompareEditor}
194-
before={originalContent}
195-
beforeName={""}
196-
after={currentContent}
197-
afterName={""}/>
198-
</IfNotLoading>
181+
<Page className="pf-m-redhat-font" isManagedSidebar={false} header={<AppHeader />}>
182+
<IfNotLoading isLoading={isLoading}>
183+
<PageSection variant={PageSectionVariants.light} id="section-context" style={sectionContextStyle}>
184+
<Toolbar id="embedded-editor-toolbar">
185+
<ToolbarContent>
186+
<ToolbarItem>
187+
<Button variant="secondary" isDisabled={!isDirty} onClick={onCompareContent}>Diff changes</Button>
188+
</ToolbarItem>
189+
<ToolbarItem>
190+
<Button variant="secondary" onClick={onFormat}>Format content</Button>
191+
</ToolbarItem>
192+
<ToolbarItem variant="separator" />
193+
<ToolbarItem>
194+
<Button variant="primary" isDisabled={!isDirty} onClick={onSave}>Save</Button>
195+
</ToolbarItem>
196+
</ToolbarContent>
197+
</Toolbar>
198+
</PageSection>
199+
<PageSection variant={PageSectionVariants.light} id="section-editor" style={sectionEditorStyle}>
200+
<div className="editor-parent" style={editorParentStyle} children={editor() as any} />
201+
</PageSection>
202+
<CompareModal isOpen={isCompareModalOpen}
203+
onClose={closeCompareEditor}
204+
before={originalContent}
205+
beforeName={""}
206+
after={currentContent}
207+
afterName={""}/>
208+
</IfNotLoading>
209+
</Page>
199210
);
200211
};

‎ui/ui-app/src/app/pages/HomePage.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FunctionComponent, useRef, useState } from "react";
1+
import { FunctionComponent, useRef, useState } from "react";
22
import {
33
Drawer,
44
DrawerActions,
@@ -8,6 +8,7 @@ import {
88
DrawerHead,
99
DrawerPanelBody,
1010
DrawerPanelContent,
11+
Page,
1112
PageSection,
1213
PageSectionVariants,
1314
Text,
@@ -29,6 +30,7 @@ import {
2930
ImportFrom
3031
} from "@app/pages/components";
3132
import { AppNavigationService, useAppNavigation } from "@services/useAppNavigation.ts";
33+
import { AppHeader } from "@app/components";
3234

3335
export type HomePageProps = Record<string, never>;
3436

@@ -133,7 +135,7 @@ export const HomePage: FunctionComponent<HomePageProps> = () => {
133135
);
134136

135137
return (
136-
<React.Fragment>
138+
<Page className="pf-m-redhat-font" isManagedSidebar={false} header={<AppHeader />}>
137139
<Drawer isStatic={false} position="right" isInline={false} isExpanded={isDrawerExpanded} onExpand={onDrawerExpand}>
138140
<DrawerContent panelContent={panelContent}>
139141
<DrawerContentBody className="home-panel-body">
@@ -165,6 +167,6 @@ export const HomePage: FunctionComponent<HomePageProps> = () => {
165167
</DrawerContentBody>
166168
</DrawerContent>
167169
</Drawer>
168-
</React.Fragment>
170+
</Page>
169171
);
170172
};

0 commit comments

Comments
 (0)
Please sign in to comment.