1
- import Button from "@mui/material/Button" ;
2
- import AddIcon from "@mui/icons-material/AddOutlined" ;
1
+ import { type FC , useRef , useState } from "react" ;
2
+ import { Link as RouterLink , useNavigate } from "react-router-dom" ;
3
+ import { useDeletionDialogState } from "./useDeletionDialogState" ;
4
+
5
+ import { useQuery } from "react-query" ;
6
+ import { workspaces } from "api/queries/workspaces" ;
7
+ import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" ;
3
8
import {
4
9
AuthorizationResponse ,
5
10
Template ,
6
11
TemplateVersion ,
7
12
} from "api/typesGenerated" ;
13
+
8
14
import { Avatar } from "components/Avatar/Avatar" ;
9
15
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog" ;
16
+ import { Stack } from "components/Stack/Stack" ;
17
+ import { Margins } from "components/Margins/Margins" ;
10
18
import {
11
19
PageHeader ,
12
20
PageHeaderTitle ,
13
21
PageHeaderSubtitle ,
14
22
} from "components/PageHeader/PageHeader" ;
15
- import { Stack } from "components/Stack/Stack" ;
16
- import { FC , useRef , useState } from "react" ;
17
- import { Link as RouterLink , useNavigate } from "react-router-dom" ;
18
- import { useDeleteTemplate } from "./deleteTemplate" ;
19
- import { Margins } from "components/Margins/Margins" ;
23
+
24
+ import Button from "@mui/material/Button" ;
20
25
import MoreVertOutlined from "@mui/icons-material/MoreVertOutlined" ;
21
26
import Menu from "@mui/material/Menu" ;
22
27
import MenuItem from "@mui/material/MenuItem" ;
23
- import SettingsOutlined from "@mui/icons-material/SettingsOutlined" ;
24
- import DeleteOutlined from "@mui/icons-material/DeleteOutlined" ;
25
- import EditOutlined from "@mui/icons-material/EditOutlined" ;
26
- import FileCopyOutlined from "@mui/icons-material/FileCopyOutlined" ;
27
28
import IconButton from "@mui/material/IconButton" ;
29
+ import AddIcon from "@mui/icons-material/AddOutlined" ;
30
+ import SettingsIcon from "@mui/icons-material/SettingsOutlined" ;
31
+ import DeleteIcon from "@mui/icons-material/DeleteOutlined" ;
32
+ import EditIcon from "@mui/icons-material/EditOutlined" ;
33
+ import CopyIcon from "@mui/icons-material/FileCopyOutlined" ;
28
34
29
- const TemplateMenu : FC < {
35
+ type TemplateMenuProps = {
30
36
templateName : string ;
31
37
templateVersion : string ;
38
+ templateId : string ;
32
39
onDelete : ( ) => void ;
33
- } > = ( { templateName, templateVersion, onDelete } ) => {
40
+ } ;
41
+
42
+ const TemplateMenu : FC < TemplateMenuProps > = ( {
43
+ templateName,
44
+ templateVersion,
45
+ templateId,
46
+ onDelete,
47
+ } ) => {
48
+ const dialogState = useDeletionDialogState ( templateId , onDelete ) ;
34
49
const menuTriggerRef = useRef < HTMLButtonElement > ( null ) ;
35
50
const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ;
36
51
const navigate = useNavigate ( ) ;
37
52
53
+ const queryText = `template:${ templateName } ` ;
54
+ const workspaceCountQuery = useQuery ( {
55
+ ...workspaces ( { q : queryText } ) ,
56
+ select : ( res ) => res . count ,
57
+ } ) ;
58
+
38
59
// Returns a function that will execute the action and close the menu
39
60
const onMenuItemClick = ( actionFn : ( ) => void ) => ( ) => {
40
61
setIsMenuOpen ( false ) ;
41
-
42
62
actionFn ( ) ;
43
63
} ;
44
64
65
+ const safeToDeleteTemplate = workspaceCountQuery . data === 0 ;
66
+
45
67
return (
46
- < div >
47
- < IconButton
48
- aria-controls = "template-options"
49
- aria-haspopup = "true"
50
- onClick = { ( ) => setIsMenuOpen ( true ) }
51
- ref = { menuTriggerRef }
52
- arial-label = "More options"
53
- >
54
- < MoreVertOutlined />
55
- </ IconButton >
56
-
57
- < Menu
58
- id = "template-options"
59
- anchorEl = { menuTriggerRef . current }
60
- open = { isMenuOpen }
61
- onClose = { ( ) => setIsMenuOpen ( false ) }
62
- >
63
- < MenuItem
64
- onClick = { onMenuItemClick ( ( ) =>
65
- navigate ( `/templates/${ templateName } /settings` ) ,
66
- ) }
68
+ < >
69
+ < div >
70
+ < IconButton
71
+ aria-controls = "template-options"
72
+ aria-haspopup = "true"
73
+ onClick = { ( ) => setIsMenuOpen ( true ) }
74
+ ref = { menuTriggerRef }
75
+ arial-label = "More options"
67
76
>
68
- < SettingsOutlined />
69
- Settings
70
- </ MenuItem >
71
- < MenuItem
72
- onClick = { onMenuItemClick ( ( ) =>
73
- navigate (
74
- `/templates/${ templateName } /versions/${ templateVersion } /edit` ,
75
- ) ,
76
- ) }
77
- >
78
- < EditOutlined />
79
- Edit files
80
- </ MenuItem >
81
- < MenuItem
82
- onClick = { onMenuItemClick ( ( ) =>
83
- navigate ( `/templates/new?fromTemplate=${ templateName } ` ) ,
84
- ) }
77
+ < MoreVertOutlined />
78
+ </ IconButton >
79
+
80
+ < Menu
81
+ id = "template-options"
82
+ anchorEl = { menuTriggerRef . current }
83
+ open = { isMenuOpen }
84
+ onClose = { ( ) => setIsMenuOpen ( false ) }
85
85
>
86
- < FileCopyOutlined />
87
- Duplicate…
88
- </ MenuItem >
89
- < MenuItem onClick = { onMenuItemClick ( onDelete ) } >
90
- < DeleteOutlined />
91
- Delete…
92
- </ MenuItem >
93
- </ Menu >
94
- </ div >
86
+ < MenuItem
87
+ onClick = { onMenuItemClick ( ( ) =>
88
+ navigate ( `/templates/${ templateName } /settings` ) ,
89
+ ) }
90
+ >
91
+ < SettingsIcon />
92
+ Settings
93
+ </ MenuItem >
94
+
95
+ < MenuItem
96
+ onClick = { onMenuItemClick ( ( ) =>
97
+ navigate (
98
+ `/templates/${ templateName } /versions/${ templateVersion } /edit` ,
99
+ ) ,
100
+ ) }
101
+ >
102
+ < EditIcon />
103
+ Edit files
104
+ </ MenuItem >
105
+
106
+ < MenuItem
107
+ onClick = { onMenuItemClick ( ( ) =>
108
+ navigate ( `/templates/new?fromTemplate=${ templateName } ` ) ,
109
+ ) }
110
+ >
111
+ < CopyIcon />
112
+ Duplicate…
113
+ </ MenuItem >
114
+
115
+ < MenuItem
116
+ onClick = { onMenuItemClick ( dialogState . openDeleteConfirmation ) }
117
+ >
118
+ < DeleteIcon />
119
+ Delete…
120
+ </ MenuItem >
121
+ </ Menu >
122
+ </ div >
123
+
124
+ { safeToDeleteTemplate ? (
125
+ < DeleteDialog
126
+ isOpen = { dialogState . isDeleteDialogOpen }
127
+ onConfirm = { dialogState . confirmDelete }
128
+ onCancel = { dialogState . cancelDeleteConfirmation }
129
+ entity = "template"
130
+ name = { templateName }
131
+ />
132
+ ) : (
133
+ < ConfirmDialog
134
+ type = "info"
135
+ title = "Unable to delete"
136
+ hideCancel = { false }
137
+ open = { dialogState . isDeleteDialogOpen }
138
+ onClose = { dialogState . cancelDeleteConfirmation }
139
+ confirmText = "See workspaces"
140
+ confirmLoading = { workspaceCountQuery . status !== "success" }
141
+ onConfirm = { ( ) => {
142
+ navigate ( {
143
+ pathname : "/workspaces" ,
144
+ search : new URLSearchParams ( { filter : queryText } ) . toString ( ) ,
145
+ } ) ;
146
+ } }
147
+ description = {
148
+ < >
149
+ { workspaceCountQuery . isSuccess && (
150
+ < >
151
+ This template is used by{ " " }
152
+ < strong >
153
+ { workspaceCountQuery . data } workspace
154
+ { workspaceCountQuery . data === 1 ? "" : "s" }
155
+ </ strong >
156
+ . Please delete all related workspaces before deleting this
157
+ template.
158
+ </ >
159
+ ) }
160
+
161
+ { workspaceCountQuery . isLoading && (
162
+ < > Loading information about workspaces used by this template.</ >
163
+ ) }
164
+
165
+ { workspaceCountQuery . isError && (
166
+ < > Unable to determine workspaces used by this template.</ >
167
+ ) }
168
+ </ >
169
+ }
170
+ />
171
+ ) }
172
+ </ >
95
173
) ;
96
174
} ;
97
175
98
- const CreateWorkspaceButton : FC < {
99
- templateName : string ;
100
- className ?: string ;
101
- } > = ( { templateName } ) => (
102
- < Button
103
- variant = "contained"
104
- startIcon = { < AddIcon /> }
105
- component = { RouterLink }
106
- to = { `/templates/${ templateName } /workspace` }
107
- >
108
- Create Workspace
109
- </ Button >
110
- ) ;
111
-
112
176
export type TemplatePageHeaderProps = {
113
177
template : Template ;
114
178
activeVersion : TemplateVersion ;
@@ -123,19 +187,27 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
123
187
onDeleteTemplate,
124
188
} ) => {
125
189
const hasIcon = template . icon && template . icon !== "" ;
126
- const deleteTemplate = useDeleteTemplate ( template , onDeleteTemplate ) ;
127
190
128
191
return (
129
192
< Margins >
130
193
< PageHeader
131
194
actions = {
132
195
< >
133
- < CreateWorkspaceButton templateName = { template . name } />
196
+ < Button
197
+ variant = "contained"
198
+ startIcon = { < AddIcon /> }
199
+ component = { RouterLink }
200
+ to = { `/templates/${ template . name } /workspace` }
201
+ >
202
+ Create Workspace
203
+ </ Button >
204
+
134
205
{ permissions . canUpdateTemplate && (
135
206
< TemplateMenu
136
207
templateVersion = { activeVersion . name }
137
208
templateName = { template . name }
138
- onDelete = { deleteTemplate . openDeleteConfirmation }
209
+ templateId = { template . id }
210
+ onDelete = { onDeleteTemplate }
139
211
/>
140
212
) }
141
213
</ >
@@ -154,6 +226,7 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
154
226
? template . display_name
155
227
: template . name }
156
228
</ PageHeaderTitle >
229
+
157
230
{ template . description !== "" && (
158
231
< PageHeaderSubtitle condensed >
159
232
{ template . description }
@@ -162,15 +235,6 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
162
235
</ div >
163
236
</ Stack >
164
237
</ PageHeader >
165
-
166
- < DeleteDialog
167
- isOpen = { deleteTemplate . isDeleteDialogOpen }
168
- confirmLoading = { deleteTemplate . state . status === "deleting" }
169
- onConfirm = { deleteTemplate . confirmDelete }
170
- onCancel = { deleteTemplate . cancelDeleteConfirmation }
171
- entity = "template"
172
- name = { template . name }
173
- />
174
238
</ Margins >
175
239
) ;
176
240
} ;
0 commit comments