@@ -296,6 +296,7 @@ export function CloudEnvironmentsSettings() {
296296 buildMutation,
297297 builderTaskMutation,
298298 deleteMutation : deleteImageMutation ,
299+ updateMutation : updateImageMutation ,
299300 } = useSandboxCustomImages ( ) ;
300301 const handleOpenTask = useHandleOpenTask ( ) ;
301302 const repoPickerProps = useCloudRepoPicker ( ) ;
@@ -313,6 +314,10 @@ export function CloudEnvironmentsSettings() {
313314 const [ specDraft , setSpecDraft ] = useState ( "" ) ;
314315 const [ deleteConfirmImage , setDeleteConfirmImage ] =
315316 useState < SandboxCustomImage | null > ( null ) ;
317+ const [ renameImage , setRenameImage ] = useState < SandboxCustomImage | null > (
318+ null ,
319+ ) ;
320+ const [ renameName , setRenameName ] = useState ( "" ) ;
316321 const [ viewingLogImageId , setViewingLogImageId ] = useState < string | null > (
317322 null ,
318323 ) ;
@@ -456,6 +461,30 @@ export function CloudEnvironmentsSettings() {
456461 setDeleteConfirmImage ( null ) ;
457462 } , [ deleteConfirmImage , deleteImageMutation ] ) ;
458463
464+ const openRenameImage = useCallback ( ( image : SandboxCustomImage ) => {
465+ setRenameImage ( image ) ;
466+ setRenameName ( image . name ) ;
467+ } , [ ] ) ;
468+
469+ const handleRenameImage = useCallback ( async ( ) => {
470+ if ( ! renameImage ) return ;
471+ const name = renameName . trim ( ) ;
472+ if ( ! name ) {
473+ toast . error ( "Name cannot be blank" ) ;
474+ return ;
475+ }
476+ if ( name === renameImage . name ) {
477+ setRenameImage ( null ) ;
478+ return ;
479+ }
480+ try {
481+ await updateImageMutation . mutateAsync ( { id : renameImage . id , name } ) ;
482+ setRenameImage ( null ) ;
483+ } catch {
484+ // The mutation's onError callback displays the failure toast.
485+ }
486+ } , [ renameImage , renameName , updateImageMutation ] ) ;
487+
459488 if ( isFormOpen ) {
460489 return (
461490 < Flex direction = "column" gap = "4" >
@@ -1000,6 +1029,18 @@ export function CloudEnvironmentsSettings() {
10001029 >
10011030 Save & build
10021031 </ Button >
1032+ < Button
1033+ size = "1"
1034+ variant = "ghost"
1035+ color = "gray"
1036+ onClick = { ( ) => openRenameImage ( image ) }
1037+ disabled = {
1038+ deleteImageMutation . isPending ||
1039+ updateImageMutation . isPending
1040+ }
1041+ >
1042+ < PencilSimple size = { 14 } />
1043+ </ Button >
10031044 < Button
10041045 size = "1"
10051046 variant = "ghost"
@@ -1090,6 +1131,59 @@ export function CloudEnvironmentsSettings() {
10901131 </ Flex >
10911132 </ AlertDialog . Content >
10921133 </ AlertDialog . Root >
1134+ < AlertDialog . Root
1135+ open = { renameImage !== null }
1136+ onOpenChange = { ( open ) => {
1137+ if ( ! open ) setRenameImage ( null ) ;
1138+ } }
1139+ >
1140+ < AlertDialog . Content maxWidth = "420px" size = "1" >
1141+ < AlertDialog . Title className = "text-sm" >
1142+ Rename custom image
1143+ </ AlertDialog . Title >
1144+ < AlertDialog . Description >
1145+ < Text color = "gray" className = "text-[13px]" >
1146+ Updates the display name. The build spec and status are
1147+ unchanged.
1148+ </ Text >
1149+ </ AlertDialog . Description >
1150+ < Flex direction = "column" gap = "1" mt = "3" >
1151+ < Text className = "font-medium text-[13px]" > Name</ Text >
1152+ < TextField . Root
1153+ size = "2"
1154+ value = { renameName }
1155+ onChange = { ( e ) => setRenameName ( e . target . value ) }
1156+ placeholder = "Image name"
1157+ autoFocus
1158+ onKeyDown = { ( e ) => {
1159+ if ( e . key === "Enter" && ! updateImageMutation . isPending ) {
1160+ e . preventDefault ( ) ;
1161+ void handleRenameImage ( ) ;
1162+ }
1163+ } }
1164+ />
1165+ </ Flex >
1166+ < Flex justify = "end" gap = "3" mt = "3" >
1167+ < AlertDialog . Cancel >
1168+ < Button variant = "soft" color = "gray" size = "1" >
1169+ Cancel
1170+ </ Button >
1171+ </ AlertDialog . Cancel >
1172+ < Button
1173+ variant = "solid"
1174+ size = "1"
1175+ onClick = { ( ) => void handleRenameImage ( ) }
1176+ loading = { updateImageMutation . isPending }
1177+ disabled = {
1178+ ! renameName . trim ( ) ||
1179+ renameName . trim ( ) === renameImage ?. name
1180+ }
1181+ >
1182+ Save
1183+ </ Button >
1184+ </ Flex >
1185+ </ AlertDialog . Content >
1186+ </ AlertDialog . Root >
10931187 </ >
10941188 ) }
10951189 </ Flex >
0 commit comments