@@ -2,7 +2,10 @@ import Button from "@mui/material/Button";
2
2
import IconButton from "@mui/material/IconButton" ;
3
3
import Tooltip from "@mui/material/Tooltip" ;
4
4
import CreateIcon from "@mui/icons-material/AddOutlined" ;
5
- import { Link as RouterLink } from "react-router-dom" ;
5
+ import {
6
+ Link as RouterLink ,
7
+ unstable_usePrompt as usePrompt ,
8
+ } from "react-router-dom" ;
6
9
import { type Interpolation , type Theme , useTheme } from "@emotion/react" ;
7
10
import { type FC , useCallback , useEffect , useRef , useState } from "react" ;
8
11
import AlertTitle from "@mui/material/AlertTitle" ;
@@ -65,8 +68,8 @@ export interface TemplateVersionEditorProps {
65
68
defaultFileTree : FileTree ;
66
69
buildLogs ?: ProvisionerJobLog [ ] ;
67
70
resources ?: WorkspaceResource [ ] ;
68
- disablePreview ? : boolean ;
69
- disableUpdate ? : boolean ;
71
+ isBuilding : boolean ;
72
+ canPublish : boolean ;
70
73
onPreview : ( files : FileTree ) => Promise < void > ;
71
74
onPublish : ( ) => void ;
72
75
onConfirmPublish : ( data : PublishVersionData ) => void ;
@@ -88,8 +91,8 @@ export interface TemplateVersionEditorProps {
88
91
}
89
92
90
93
export const TemplateVersionEditor : FC < TemplateVersionEditorProps > = ( {
91
- disablePreview ,
92
- disableUpdate ,
94
+ isBuilding ,
95
+ canPublish ,
93
96
template,
94
97
templateVersion,
95
98
defaultFileTree,
@@ -179,6 +182,10 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
179
182
}
180
183
} , [ buildLogs ] ) ;
181
184
185
+ useLeaveSiteWarning ( canPublish ) ;
186
+
187
+ const canBuild = ! isBuilding && dirty ;
188
+
182
189
return (
183
190
< >
184
191
< div css = { { height : "100%" , display : "flex" , flexDirection : "column" } } >
@@ -242,7 +249,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
242
249
borderLeft : "1px solid #FFF" ,
243
250
} ,
244
251
} }
245
- disabled = { disablePreview }
252
+ disabled = { ! canBuild }
246
253
>
247
254
< TopbarButton
248
255
startIcon = {
@@ -251,7 +258,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
251
258
/>
252
259
}
253
260
title = "Build template (Ctrl + Enter)"
254
- disabled = { disablePreview }
261
+ disabled = { ! canBuild }
255
262
onClick = { async ( ) => {
256
263
await triggerPreview ( ) ;
257
264
} }
@@ -276,7 +283,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
276
283
277
284
< TopbarButton
278
285
variant = "contained"
279
- disabled = { dirty || disableUpdate }
286
+ disabled = { dirty || ! canPublish }
280
287
onClick = { onPublish }
281
288
>
282
289
Publish
@@ -540,7 +547,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
540
547
</ button >
541
548
542
549
< button
543
- disabled = { disableUpdate }
550
+ disabled = { ! canPublish }
544
551
css = { styles . tab }
545
552
className = { selectedTab === "resources" ? "active" : "" }
546
553
onClick = { ( ) => {
@@ -649,6 +656,38 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
649
656
) ;
650
657
} ;
651
658
659
+ const useLeaveSiteWarning = ( enabled : boolean ) => {
660
+ const MESSAGE =
661
+ "You have unpublished changes. Are you sure you want to leave?" ;
662
+
663
+ // This works for regular browser actions like close tab and back button
664
+ useEffect ( ( ) => {
665
+ const onBeforeUnload = ( e : BeforeUnloadEvent ) => {
666
+ if ( enabled ) {
667
+ e . preventDefault ( ) ;
668
+ return MESSAGE ;
669
+ }
670
+ } ;
671
+
672
+ window . addEventListener ( "beforeunload" , onBeforeUnload ) ;
673
+
674
+ return ( ) => {
675
+ window . removeEventListener ( "beforeunload" , onBeforeUnload ) ;
676
+ } ;
677
+ } , [ enabled ] ) ;
678
+
679
+ // This is used for react router navigation that is not triggered by the
680
+ // browser
681
+ usePrompt ( {
682
+ message : MESSAGE ,
683
+ when : ( { nextLocation } ) => {
684
+ // We need to check the path because we change the URL when new template
685
+ // version is created during builds
686
+ return enabled && ! nextLocation . pathname . endsWith ( "/edit" ) ;
687
+ } ,
688
+ } ) ;
689
+ } ;
690
+
652
691
const styles = {
653
692
tab : ( theme ) => ( {
654
693
"&:not(:disabled)" : {
0 commit comments