1- import { type Interpolation , type Theme , useTheme } from "@emotion/react" ;
2- import { visuallyHidden } from "@mui/utils" ;
3- import type { Task , Workspace } from "api/typesGenerated" ;
1+ import type { Task } from "api/typesGenerated" ;
42import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" ;
5- import { ExternalImage } from "components/ExternalImage/ExternalImage" ;
6- import { Stack } from "components/Stack/Stack" ;
73import dayjs from "dayjs" ;
84import relativeTime from "dayjs/plugin/relativeTime" ;
95import { ClockIcon , ServerIcon , UserIcon } from "lucide-react" ;
106import { type FC , type ReactNode , useState } from "react" ;
11- import { getResourceIconPath } from "utils/workspace" ;
127
138dayjs . extend ( relativeTime ) ;
149
1510type BatchDeleteConfirmationProps = {
1611 checkedTasks : readonly Task [ ] ;
17- workspaces : readonly Workspace [ ] ;
12+ workspaceCount : number ;
1813 open : boolean ;
1914 isLoading : boolean ;
2015 onClose : ( ) => void ;
@@ -23,7 +18,7 @@ type BatchDeleteConfirmationProps = {
2318
2419export const BatchDeleteConfirmation : FC < BatchDeleteConfirmationProps > = ( {
2520 checkedTasks,
26- workspaces ,
21+ workspaceCount ,
2722 open,
2823 onClose,
2924 onConfirm,
@@ -56,38 +51,16 @@ export const BatchDeleteConfirmation: FC<BatchDeleteConfirmationProps> = ({
5651 confirmText = < > Confirm { taskCount } …</ > ;
5752 }
5853 if ( stage === "resources" ) {
59- const workspaceCount = workspaces . length ;
60- const resources = workspaces
61- . map ( ( workspace ) => workspace . latest_build . resources . length )
62- . reduce ( ( a , b ) => a + b , 0 ) ;
63- const resourceCount = `${ resources } ${
64- resources === 1 ? "resource" : "resources"
65- } `;
6654 const workspaceCountText = `${ workspaceCount } ${
6755 workspaceCount === 1 ? "workspace" : "workspaces"
6856 } `;
6957 confirmText = (
7058 < >
71- Delete { taskCount } , { workspaceCountText } and { resourceCount }
59+ Delete { taskCount } and { workspaceCountText }
7260 </ >
7361 ) ;
7462 }
7563
76- // The flicker of these icons is quite noticeable if they aren't
77- // loaded in advance, so we insert them into the document without
78- // actually displaying them yet.
79- const resourceIconPreloads = [
80- ...new Set (
81- workspaces . flatMap ( ( workspace ) =>
82- workspace . latest_build . resources . map (
83- ( resource ) => resource . icon || getResourceIconPath ( resource . type ) ,
84- ) ,
85- ) ,
86- ) ,
87- ] . map ( ( url ) => (
88- < img key = { url } alt = "" aria-hidden css = { { ...visuallyHidden } } src = { url } />
89- ) ) ;
90-
9164 return (
9265 < ConfirmDialog
9366 type = "delete"
@@ -106,9 +79,10 @@ export const BatchDeleteConfirmation: FC<BatchDeleteConfirmationProps> = ({
10679 { stage === "consequences" && < Consequences /> }
10780 { stage === "tasks" && < Tasks tasks = { checkedTasks } /> }
10881 { stage === "resources" && (
109- < Resources tasks = { checkedTasks } workspaces = { workspaces } />
82+ < Resources tasks = { checkedTasks } workspaceCount = { workspaceCount } />
11083 ) }
111- { resourceIconPreloads }
84+ { /* Preload ServerIcon to prevent flicker on stage 3 */ }
85+ < ServerIcon className = "sr-only" aria-hidden />
11286 </ >
11387 }
11488 />
@@ -121,27 +95,24 @@ interface TasksStageProps {
12195
12296interface ResourcesStageProps {
12397 tasks : readonly Task [ ] ;
124- workspaces : readonly Workspace [ ] ;
98+ workspaceCount : number ;
12599}
126100
127101const Consequences : FC = ( ) => {
128102 return (
129103 < >
130104 < p > Deleting tasks is irreversible!</ p >
131- < ul css = { styles . consequences } >
105+ < ul className = "flex flex-col gap-2 pl-4 mb-0" >
132106 < li >
133107 Tasks with associated workspaces will have those workspaces deleted.
134108 </ li >
135- < li > Terraform resources in task workspaces will be destroyed.</ li >
136109 < li > Any data stored in task workspaces will be permanently deleted.</ li >
137110 </ ul >
138111 </ >
139112 ) ;
140113} ;
141114
142115const Tasks : FC < TasksStageProps > = ( { tasks } ) => {
143- const theme = useTheme ( ) ;
144-
145116 const mostRecent = tasks . reduce (
146117 ( latestSoFar , against ) => {
147118 if ( ! latestSoFar ) {
@@ -161,156 +132,66 @@ const Tasks: FC<TasksStageProps> = ({ tasks }) => {
161132
162133 return (
163134 < >
164- < ul css = { styles . tasksList } >
135+ < ul className = "list-none p-0 border border-solid border-border rounded-lg overflow-x-hidden overflow-y-auto max-h-[184px]" >
165136 { tasks . map ( ( task ) => (
166- < li key = { task . id } css = { styles . task } >
167- < Stack
168- direction = "row"
169- alignItems = "center"
170- justifyContent = "space-between"
171- spacing = { 3 }
172- >
173- < span
174- css = { {
175- fontWeight : 500 ,
176- color : theme . experimental . l1 . text ,
177- maxWidth : 400 ,
178- overflow : "hidden" ,
179- textOverflow : "ellipsis" ,
180- whiteSpace : "nowrap" ,
181- } }
182- >
183- { task . initial_prompt }
137+ < li
138+ key = { task . id }
139+ className = "py-2 px-4 border-solid border-0 border-b border-border last:border-b-0"
140+ >
141+ < div className = "flex items-center justify-between gap-6" >
142+ < span className = "font-medium text-content-primary max-w-[400px] overflow-hidden text-ellipsis whitespace-nowrap" >
143+ { task . display_name }
184144 </ span >
185145
186- < Stack css = { { gap : 0 , fontSize : 14 } } justifyContent = "flex-end" >
187- < Stack
188- direction = "row"
189- alignItems = "center"
190- justifyContent = "flex-end"
191- spacing = { 1 }
192- >
193- < span css = { { whiteSpace : "nowrap" } } > { task . owner_name } </ span >
194- < PersonIcon />
195- </ Stack >
196- < Stack
197- direction = "row"
198- alignItems = "center"
199- spacing = { 1 }
200- justifyContent = "flex-end"
201- >
202- < span css = { { whiteSpace : "nowrap" } } >
146+ < div className = "flex flex-col text-sm items-end" >
147+ < div className = "flex items-center gap-2" >
148+ < span className = "whitespace-nowrap" > { task . owner_name } </ span >
149+ < UserIcon className = "size-icon-sm -m-px" />
150+ </ div >
151+ < div className = "flex items-center gap-2" >
152+ < span className = "whitespace-nowrap" >
203153 { dayjs ( task . created_at ) . fromNow ( ) }
204154 </ span >
205155 < ClockIcon className = "size-icon-xs" />
206- </ Stack >
207- </ Stack >
208- </ Stack >
156+ </ div >
157+ </ div >
158+ </ div >
209159 </ li >
210160 ) ) }
211161 </ ul >
212- < Stack
213- justifyContent = "center"
214- direction = "row"
215- wrap = "wrap"
216- css = { { gap : "6px 20px" , fontSize : 14 } }
217- >
218- < Stack direction = "row" alignItems = "center" spacing = { 1 } >
219- < PersonIcon />
162+ < div className = "flex flex-wrap justify-center gap-x-5 gap-y-1.5 text-sm" >
163+ < div className = "flex items-center gap-2" >
164+ < UserIcon className = "size-icon-sm -m-px" />
220165 < span > { ownersCount } </ span >
221- </ Stack >
166+ </ div >
222167 { mostRecent && (
223- < Stack direction = "row" alignItems = " center" spacing = { 1 } >
168+ < div className = "flex items- center gap-2" >
224169 < ClockIcon className = "size-icon-xs" />
225170 < span > Last created { dayjs ( mostRecent . created_at ) . fromNow ( ) } </ span >
226- </ Stack >
171+ </ div >
227172 ) }
228- </ Stack >
173+ </ div >
229174 </ >
230175 ) ;
231176} ;
232177
233- const Resources : FC < ResourcesStageProps > = ( { tasks, workspaces } ) => {
234- const resources : Record < string , { count : number ; icon : string } > = { } ;
235- for ( const workspace of workspaces ) {
236- for ( const resource of workspace . latest_build . resources ) {
237- if ( ! resources [ resource . type ] ) {
238- resources [ resource . type ] = {
239- count : 0 ,
240- icon : resource . icon || getResourceIconPath ( resource . type ) ,
241- } ;
242- }
243-
244- resources [ resource . type ] . count ++ ;
245- }
246- }
178+ const Resources : FC < ResourcesStageProps > = ( { tasks, workspaceCount } ) => {
179+ const taskCount = tasks . length ;
247180
248181 return (
249- < Stack >
182+ < div className = "flex flex-col gap-4" >
250183 < p >
251- Deleting { tasks . length === 1 ? "this task" : "these tasks" } will also
184+ Deleting { taskCount === 1 ? "this task" : "these tasks" } will also
252185 permanently destroy…
253186 </ p >
254- < Stack
255- direction = "row"
256- justifyContent = "center"
257- wrap = "wrap"
258- css = { { gap : "6px 20px" , fontSize : 14 } }
259- >
260- < Stack direction = "row" alignItems = "center" spacing = { 1 } >
187+ < div className = "flex flex-wrap justify-center gap-x-5 gap-y-1.5 text-sm" >
188+ < div className = "flex items-center gap-2" >
261189 < ServerIcon className = "size-icon-sm" />
262190 < span >
263- { workspaces . length } { " " }
264- { workspaces . length === 1 ? "workspace" : "workspaces" }
191+ { workspaceCount } { workspaceCount === 1 ? "workspace" : "workspaces" }
265192 </ span >
266- </ Stack >
267- { Object . entries ( resources ) . map ( ( [ type , summary ] ) => (
268- < Stack key = { type } direction = "row" alignItems = "center" spacing = { 1 } >
269- < ExternalImage
270- src = { summary . icon }
271- width = { styles . summaryIcon . width }
272- height = { styles . summaryIcon . height }
273- />
274- < span >
275- { summary . count } < code > { type } </ code >
276- </ span >
277- </ Stack >
278- ) ) }
279- </ Stack >
280- </ Stack >
193+ </ div >
194+ </ div >
195+ </ div >
281196 ) ;
282197} ;
283-
284- const PersonIcon : FC = ( ) => {
285- return < UserIcon className = "size-icon-sm" css = { { margin : - 1 } } /> ;
286- } ;
287-
288- const styles = {
289- summaryIcon : { width : 16 , height : 16 } ,
290-
291- consequences : {
292- display : "flex" ,
293- flexDirection : "column" ,
294- gap : 8 ,
295- paddingLeft : 16 ,
296- marginBottom : 0 ,
297- } ,
298-
299- tasksList : ( theme ) => ( {
300- listStyleType : "none" ,
301- padding : 0 ,
302- border : `1px solid ${ theme . palette . divider } ` ,
303- borderRadius : 8 ,
304- overflow : "hidden auto" ,
305- maxHeight : 184 ,
306- } ) ,
307-
308- task : ( theme ) => ( {
309- padding : "8px 16px" ,
310- borderBottom : `1px solid ${ theme . palette . divider } ` ,
311-
312- "&:last-child" : {
313- border : "none" ,
314- } ,
315- } ) ,
316- } satisfies Record < string , Interpolation < Theme > > ;
0 commit comments