88import SwiftUI
99import UniformTypeIdentifiers
1010
11+ /// A view that displays a terminal group with a header and a list of its terminals.
12+ /// Supports editing the group name, collapsing, drag-and-drop, and inline terminal row management.
1113struct UtilityAreaTerminalGroupView : View {
14+ /// The index of the group within the terminalGroups array.
1215 let index : Int
16+
17+ /// Whether this group is currently selected in the UI.
1318 let isGroupSelected : Bool
19+
20+ /// The shared view model that manages terminal groups and their state.
1421 @EnvironmentObject private var utilityAreaViewModel : UtilityAreaViewModel
22+
23+ /// Manages focus on a specific terminal row for keyboard navigation or editing.
1524 @FocusState private var focusedTerminalID : UUID ?
1625
1726 var body : some View {
1827 VStack ( alignment: . leading, spacing: 0 ) {
19- HStack ( spacing: 4 ) {
2028
29+ // MARK: - Group Header
30+
31+ HStack ( spacing: 4 ) {
2132 Image ( systemName: " square.on.square " )
2233 . font ( . system( size: 14 , weight: . medium) )
2334 . frame ( width: 20 , height: 20 )
2435 . foregroundStyle ( . primary. opacity ( 0.6 ) )
2536
37+ // Editable group name when in edit mode
2638 if utilityAreaViewModel. editingGroupID == utilityAreaViewModel. terminalGroups [ index] . id {
2739 TextField ( " " , text: Binding (
2840 get: { utilityAreaViewModel. terminalGroups [ index] . name } ,
@@ -47,7 +59,12 @@ struct UtilityAreaTerminalGroupView: View {
4759 utilityAreaViewModel. editingGroupID = nil
4860 }
4961 } else {
50- Text ( utilityAreaViewModel. terminalGroups [ index] . name. isEmpty ? " terminals " : utilityAreaViewModel. terminalGroups [ index] . name)
62+ // Display group name normally
63+ Text (
64+ utilityAreaViewModel. terminalGroups [ index] . name. isEmpty
65+ ? " terminals "
66+ : utilityAreaViewModel. terminalGroups [ index] . name
67+ )
5168 . lineLimit ( 1 )
5269 . truncationMode ( . middle)
5370 . font ( . system( size: 13 , weight: . regular) )
@@ -63,7 +80,12 @@ struct UtilityAreaTerminalGroupView: View {
6380
6481 Spacer ( )
6582
66- Image ( systemName: utilityAreaViewModel. terminalGroups [ index] . isCollapsed ? " chevron.right " : " chevron.down " )
83+ // Expand/collapse toggle
84+ Image (
85+ systemName: utilityAreaViewModel. terminalGroups [ index] . isCollapsed
86+ ? " chevron.right "
87+ : " chevron.down "
88+ )
6789 . font ( . system( size: 11 , weight: . medium) )
6890 . foregroundColor ( . secondary)
6991 }
@@ -76,8 +98,7 @@ struct UtilityAreaTerminalGroupView: View {
7698 }
7799 }
78100 . onDrag {
79- // utilityAreaViewModel.draggedTerminalID = terminal.id
80-
101+ // Optional: dragging the entire group (stubbed terminal ID)
81102 let dragInfo = TerminalDragInfo ( terminalID: . init( ) )
82103 let provider = NSItemProvider ( )
83104 do {
@@ -90,10 +111,13 @@ struct UtilityAreaTerminalGroupView: View {
90111 return nil
91112 }
92113 } catch {
93- print ( " ❌ Erro ao codificar dragInfo: \( error) " )
114+ print ( " ❌ Failed to encode dragInfo: \( error) " )
94115 }
95116 return provider
96117 }
118+
119+ // MARK: - Terminal Rows (if group is expanded)
120+
97121 if !utilityAreaViewModel. terminalGroups [ index] . isCollapsed {
98122 VStack ( spacing: 0 ) {
99123 ForEach ( utilityAreaViewModel. terminalGroups [ index] . terminals, id: \. id) { terminal in
@@ -107,17 +131,15 @@ struct UtilityAreaTerminalGroupView: View {
107131
108132 let dragInfo = TerminalDragInfo ( terminalID: terminal. id)
109133 let provider = NSItemProvider ( )
110- do {
111- let data = try JSONEncoder ( ) . encode ( dragInfo)
112- provider. registerDataRepresentation (
113- forTypeIdentifier: UTType . terminal. identifier,
114- visibility: . all
115- ) { completion in
116- completion ( data, nil )
117- return nil
118- }
119- } catch {
120- print ( " ❌ Erro ao codificar dragInfo: \( error) " )
134+ guard let data = try ? JSONEncoder ( ) . encode ( dragInfo) else {
135+ return provider
136+ }
137+ provider. registerDataRepresentation (
138+ forTypeIdentifier: UTType . terminal. identifier,
139+ visibility: . all
140+ ) { completion in
141+ completion ( data, nil )
142+ return nil
121143 }
122144 return provider
123145 }
@@ -130,7 +152,10 @@ struct UtilityAreaTerminalGroupView: View {
130152 )
131153 )
132154 . transition ( . opacity. combined ( with: . move( edge: . top) ) )
133- . animation ( . easeInOut( duration: 0.2 ) , value: utilityAreaViewModel. terminalGroups [ index] . isCollapsed)
155+ . animation (
156+ . easeInOut( duration: 0.2 ) ,
157+ value: utilityAreaViewModel. terminalGroups [ index] . isCollapsed
158+ )
134159 }
135160 }
136161 }
@@ -147,6 +172,9 @@ struct UtilityAreaTerminalGroupView: View {
147172 }
148173}
149174
175+ // MARK: - Preview
176+
177+ /// Preview provider for `UtilityAreaTerminalGroupView`, showing a mock terminal group with two terminals.
150178struct TerminalGroupViewPreviews : PreviewProvider {
151179 static var previews : some View {
152180 let terminal = UtilityAreaTerminal (
@@ -165,7 +193,7 @@ struct TerminalGroupViewPreviews: PreviewProvider {
165193
166194 let utilityAreaViewModel = UtilityAreaViewModel ( )
167195 utilityAreaViewModel. terminalGroups = [
168- UtilityAreaTerminalGroup ( name: " Grupo de Preview" , terminals: [ terminal, terminal2] )
196+ UtilityAreaTerminalGroup ( name: " Preview Group " , terminals: [ terminal, terminal2] )
169197 ]
170198 utilityAreaViewModel. selectedTerminals = [ terminal. id]
171199
@@ -182,6 +210,7 @@ struct TerminalGroupViewPreviews: PreviewProvider {
182210 }
183211}
184212
213+ // Wrapper view to render the preview group.
185214private struct TerminalGroupViewPreviewWrapper : View {
186215 @EnvironmentObject var utilityAreaViewModel : UtilityAreaViewModel
187216 @FocusState private var focusedTerminalID : UUID ?
0 commit comments