1- // UtilityAreaViewModel.swift
2- // Atualizado para suportar drag-and-drop com reordenação visual e final com ScrollView + VStack
1+ //
2+ // UtilityAreaViewModel.swift
3+ // CodeEdit
4+ //
5+ // Created by Lukas Pistrol on 20.03.22.
6+ //
37
48import SwiftUI
59import UniformTypeIdentifiers
610
7- /// # UtilityAreaViewModel
8- /// A model class to host and manage data for the Utility area .
11+ /// View model responsible for managing terminal groups, individual terminals,
12+ /// selection state, drag-and-drop operations, and utility panel configuration .
913class UtilityAreaViewModel : ObservableObject {
1014
15+ // MARK: - UI State
16+
17+ /// Currently selected tab in the utility area.
1118 @Published var selectedTab : UtilityAreaTab ? = . terminal
1219
20+ /// Flat list of all terminals, derived from `terminalGroups`.
1321 @Published var terminals : [ UtilityAreaTerminal ] = [ ]
22+
23+ /// List of terminal groups.
24+ /// Automatically updates the flat `terminals` array when changed.
1425 @Published var terminalGroups : [ UtilityAreaTerminalGroup ] = [ ] {
1526 didSet {
1627 self . terminals = terminalGroups. flatMap { $0. terminals }
1728 }
1829 }
1930
31+ /// Set of selected terminal IDs.
2032 @Published var selectedTerminals : Set < UUID > = [ ]
21- @Published var dragOverTerminalID : UUID ? = nil
22- @Published var draggedTerminalID : UUID ? = nil
33+
34+ /// ID of the terminal currently hovered as a drop target.
35+ @Published var dragOverTerminalID : UUID ?
36+
37+ /// ID of the terminal being dragged.
38+ @Published var draggedTerminalID : UUID ?
39+
40+ /// Whether the utility area is currently collapsed.
2341 @Published var isCollapsed : Bool = false
42+
43+ /// Whether the panel collapse/expand action should animate.
2444 @Published var animateCollapse : Bool = true
45+
46+ /// Whether the utility area is maximized.
2547 @Published var isMaximized : Bool = false
48+
49+ /// Current height of the utility area panel.
2650 @Published var currentHeight : Double = 0
27- @Published var editingTerminalID : UUID ? = nil
51+
52+ /// ID of the terminal currently being edited (e.g. for inline title editing).
53+ @Published var editingTerminalID : UUID ?
54+
55+ /// Available tabs in the utility area.
2856 @Published var tabItems : [ UtilityAreaTab ] = UtilityAreaTab . allCases
57+
58+ /// View model for the current tab (e.g. terminal tab).
2959 @Published var tabViewModel = UtilityAreaTabViewModel ( )
30- @Published var editingGroupID : UUID ? = nil
60+
61+ /// ID of the group currently being edited (e.g. renaming).
62+ @Published var editingGroupID : UUID ?
63+
64+ /// Focus state for managing terminal keyboard focus.
3165 @FocusState private var focusedTerminalID : UUID ?
32- // MARK: - Drag Support
3366
67+ // MARK: - Drag-and-Drop Support
68+
69+ /// Previews a terminal move by temporarily updating the groups' array structure.
3470 func previewMoveTerminal( _ terminalID: UUID , toGroup groupID: UUID , before destinationID: UUID ? ) {
35- guard let currentGroupIndex = terminalGroups. firstIndex ( where: { $0. terminals. contains ( where: { $0. id == terminalID } ) } ) ,
36- let currentTerminalIndex = terminalGroups [ currentGroupIndex] . terminals. firstIndex ( where: { $0. id == terminalID } ) else {
71+ guard let currentGroupIndex = terminalGroups. firstIndex ( where: {
72+ $0. terminals. contains ( where: { $0. id == terminalID } )
73+ } ) ,
74+ let currentTerminalIndex = terminalGroups [ currentGroupIndex]
75+ . terminals. firstIndex ( where: { $0. id == terminalID } ) else {
3776 return
3877 }
3978
@@ -50,20 +89,18 @@ class UtilityAreaViewModel: ObservableObject {
5089
5190 if let targetIndex = terminalGroups. firstIndex ( where: { $0. id == groupID } ) {
5291 var group = terminalGroups [ targetIndex]
53-
5492 if let destID = destinationID,
5593 let destIndex = group. terminals. firstIndex ( where: { $0. id == destID } ) {
5694 group. terminals. insert ( terminal, at: destIndex)
5795 } else {
5896 group. terminals. append ( terminal)
5997 }
60-
6198 terminalGroups [ targetIndex] = group
6299 }
63100 }
64101
102+ /// Finalizes a terminal move across or within groups, updating the actual structure.
65103 func finalizeMoveTerminal( _ terminal: UtilityAreaTerminal , toGroup groupID: UUID , before destinationID: UUID ? ) {
66-
67104 let alreadyInGroup = terminalGroups. contains { group in
68105 group. id == groupID &&
69106 group. terminals. count == 1 &&
@@ -72,17 +109,16 @@ class UtilityAreaViewModel: ObservableObject {
72109
73110 guard !alreadyInGroup else { return }
74111
112+ // Remove terminal from all groups
75113 for index in terminalGroups. indices {
76114 terminalGroups [ index] . terminals. removeAll { $0. id == terminal. id }
77115 }
78116
79- // Remove grupos vazios após a remoção
117+ // Remove empty groups
80118 terminalGroups. removeAll { $0. terminals. isEmpty }
81119
82- // Adiciona ao grupo destino
83- guard let groupIndex = terminalGroups. firstIndex ( where: { $0. id == groupID } ) else {
84- return
85- }
120+ // Insert into new group
121+ guard let groupIndex = terminalGroups. firstIndex ( where: { $0. id == groupID } ) else { return }
86122
87123 if let destinationID,
88124 let destinationIndex = terminalGroups [ groupIndex] . terminals. firstIndex ( where: { $0. id == destinationID } ) {
@@ -91,18 +127,18 @@ class UtilityAreaViewModel: ObservableObject {
91127 terminalGroups [ groupIndex] . terminals. append ( terminal)
92128 }
93129
94- // Atualiza seleção
130+ // Update selection
95131 if !selectedTerminals. contains ( terminal. id) {
96132 selectedTerminals = [ terminal. id]
97133 }
98134
99- for index in terminalGroups. indices {
100- if !terminalGroups[ index] . userName {
101- terminalGroups [ index] . name = " \( terminalGroups [ index] . terminals. count) Terminals "
102- }
135+ // Auto-name group if it wasn't named by user
136+ for index in terminalGroups. indices where !terminalGroups[ index] . userName {
137+ terminalGroups [ index] . name = " \( terminalGroups [ index] . terminals. count) Terminals "
103138 }
104139 }
105140
141+ /// Removes a terminal from all groups by ID and returns it.
106142 private func removeTerminal( withID id: UUID ) -> UtilityAreaTerminal ? {
107143 for index in terminalGroups. indices {
108144 if let terminalIndex = terminalGroups [ index] . terminals. firstIndex ( where: { $0. id == id } ) {
@@ -112,20 +148,23 @@ class UtilityAreaViewModel: ObservableObject {
112148 return nil
113149 }
114150
115- // MARK: - State Restoration
151+ // MARK: - Panel State Restoration
116152
153+ /// Restores panel state from the workspace object (collapsed, height, maximized).
117154 func restoreFromState( _ workspace: WorkspaceDocument ) {
118155 isCollapsed = workspace. getFromWorkspaceState ( . utilityAreaCollapsed) as? Bool ?? false
119156 currentHeight = workspace. getFromWorkspaceState ( . utilityAreaHeight) as? Double ?? 300.0
120157 isMaximized = workspace. getFromWorkspaceState ( . utilityAreaMaximized) as? Bool ?? false
121158 }
122159
160+ /// Persists current panel state into the workspace object.
123161 func saveRestorationState( _ workspace: WorkspaceDocument ) {
124162 workspace. addToWorkspaceState ( key: . utilityAreaCollapsed, value: isCollapsed)
125163 workspace. addToWorkspaceState ( key: . utilityAreaHeight, value: currentHeight)
126164 workspace. addToWorkspaceState ( key: . utilityAreaMaximized, value: isMaximized)
127165 }
128166
167+ /// Toggles panel collapse with optional animation.
129168 func togglePanel( animation: Bool = true ) {
130169 self . animateCollapse = animation
131170 self . isMaximized = false
@@ -134,22 +173,21 @@ class UtilityAreaViewModel: ObservableObject {
134173
135174 // MARK: - Terminal Management
136175
176+ /// Removes terminals by their IDs and updates groups and selection.
137177 func removeTerminals( _ ids: Set < UUID > ) {
138178 for index in terminalGroups. indices {
139179 terminalGroups [ index] . terminals. removeAll { ids. contains ( $0. id) }
140180 }
141-
142- // Remove grupos vazios
143181 terminalGroups. removeAll { $0. terminals. isEmpty }
144182
145- // Atualiza seleção
146183 selectedTerminals. subtract ( ids)
147184 if selectedTerminals. isEmpty,
148185 let last = terminalGroups. last? . terminals. last {
149186 selectedTerminals = [ last. id]
150187 }
151188 }
152189
190+ /// Updates a terminal's title, or resets it if `nil`.
153191 func updateTerminal( _ id: UUID , title: String ? ) {
154192 for index in terminalGroups. indices {
155193 if let terminalIndex = terminalGroups [ index] . terminals. firstIndex ( where: { $0. id == id } ) {
@@ -163,15 +201,14 @@ class UtilityAreaViewModel: ObservableObject {
163201 }
164202 }
165203
204+ /// Initializes a default terminal if none exist.
166205 func initializeTerminals( workspaceURL: URL ) {
167206 guard terminalGroups. flatMap ( { $0. terminals } ) . isEmpty else { return }
168207 addTerminal ( rootURL: workspaceURL)
169208 }
170209
210+ /// Adds a new terminal, optionally to a specific group and with a specific shell.
171211 func addTerminal( to groupID: UUID ? = nil , shell: Shell ? = nil , rootURL: URL ? ) {
172-
173- print ( " Did add temrinal " )
174-
175212 let newTerminal = UtilityAreaTerminal (
176213 id: UUID ( ) ,
177214 url: rootURL ?? URL ( filePath: " ~/ " ) ,
@@ -189,6 +226,7 @@ class UtilityAreaViewModel: ObservableObject {
189226 selectedTerminals = [ newTerminal. id]
190227 }
191228
229+ /// Replaces a terminal with a new instance, useful for restarting.
192230 func replaceTerminal( _ replacing: UUID ) {
193231 for index in terminalGroups. indices {
194232 if let idx = terminalGroups [ index] . terminals. firstIndex ( where: { $0. id == replacing } ) {
@@ -211,10 +249,12 @@ class UtilityAreaViewModel: ObservableObject {
211249 }
212250 }
213251
252+ /// Reorders terminals in the flat `terminals` list (UI only).
214253 func reorderTerminals( from source: IndexSet , to destination: Int ) {
215254 terminals. move ( fromOffsets: source, toOffset: destination)
216255 }
217256
257+ /// Moves a terminal to a specific group and index.
218258 func moveTerminal( _ terminal: UtilityAreaTerminal , toGroup targetGroupID: UUID , at index: Int ) {
219259 for index in terminalGroups. indices {
220260 terminalGroups [ index] . terminals. removeAll { $0. id == terminal. id }
@@ -224,6 +264,7 @@ class UtilityAreaViewModel: ObservableObject {
224264 }
225265 }
226266
267+ /// Creates a new terminal group with the given terminals.
227268 func createGroup( with terminals: [ UtilityAreaTerminal ] ) {
228269 terminalGroups. append ( . init( name: " \( terminalGroups. count) Terminals " , terminals: terminals) )
229270 }
0 commit comments