Skip to content

Commit ff971a2

Browse files
committed
avoid UI not being in sync with contextmanager
1 parent 844b882 commit ff971a2

File tree

5 files changed

+201
-202
lines changed

5 files changed

+201
-202
lines changed

Flitro/ContextManager/ContextManager.swift

Lines changed: 83 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -88,54 +88,6 @@ struct Context: Identifiable, Codable, Equatable, Hashable {
8888
}
8989
}
9090

91-
// Migration helper for old contexts.json
92-
extension Context {
93-
// Accepts a dictionary from JSONSerialization
94-
static func migrate(from legacy: [String: Any]) -> Context? {
95-
guard let name = legacy["name"] as? String else { return nil }
96-
let id = (legacy["id"] as? String).flatMap { UUID(uuidString: $0) } ?? UUID()
97-
let iconName = legacy["iconName"] as? String
98-
let iconBackgroundColor = legacy["iconBackgroundColor"] as? String
99-
let iconForegroundColor = legacy["iconForegroundColor"] as? String
100-
let createdAt = (legacy["createdAt"] as? String).flatMap { ISO8601DateFormatter().date(from: $0) } ?? Date()
101-
let lastUsed = (legacy["lastUsed"] as? String).flatMap { ISO8601DateFormatter().date(from: $0) } ?? Date()
102-
var items: [ContextItem] = []
103-
if let apps = legacy["applications"] as? [[String: Any]] {
104-
for appDict in apps {
105-
if let data = try? JSONSerialization.data(withJSONObject: appDict),
106-
let app = try? JSONDecoder().decode(AppItem.self, from: data) {
107-
items.append(.application(app))
108-
}
109-
}
110-
}
111-
if let docs = legacy["documents"] as? [[String: Any]] {
112-
for docDict in docs {
113-
if let data = try? JSONSerialization.data(withJSONObject: docDict),
114-
let doc = try? JSONDecoder().decode(DocumentItem.self, from: data) {
115-
items.append(.document(doc))
116-
}
117-
}
118-
}
119-
if let tabs = legacy["browserTabs"] as? [[String: Any]] {
120-
for tabDict in tabs {
121-
if let data = try? JSONSerialization.data(withJSONObject: tabDict),
122-
let tab = try? JSONDecoder().decode(BrowserTab.self, from: data) {
123-
items.append(.browserTab(tab))
124-
}
125-
}
126-
}
127-
if let terms = legacy["terminalSessions"] as? [[String: Any]] {
128-
for termDict in terms {
129-
if let data = try? JSONSerialization.data(withJSONObject: termDict),
130-
let term = try? JSONDecoder().decode(TerminalSession.self, from: data) {
131-
items.append(.terminalSession(term))
132-
}
133-
}
134-
}
135-
return Context(id: id, name: name, items: items, iconName: iconName, iconBackgroundColor: iconBackgroundColor, iconForegroundColor: iconForegroundColor, createdAt: createdAt, lastUsed: lastUsed)
136-
}
137-
}
138-
13991
struct AppItem: Identifiable, Codable, Equatable, Hashable {
14092
var id: UUID = UUID()
14193
var name: String
@@ -212,21 +164,19 @@ class ContextManager: ObservableObject {
212164
saveContexts()
213165
}
214166

215-
func deleteContext(_ context: Context) {
216-
contexts.removeAll { $0.id == context.id }
217-
if activeContexts.contains(where: { $0.id == context.id }) {
218-
activeContexts.removeAll(where: { $0.id == context.id })
219-
}
220-
// Remove all launchers for the context
221-
contextLaunchers.removeValue(forKey: context.id)
167+
func deleteContext(contextID: UUID) {
168+
contexts.removeAll { $0.id == contextID }
169+
activeContexts.removeAll { $0.id == contextID }
170+
contextLaunchers.removeValue(forKey: contextID)
222171
saveContexts()
223172
}
224173

225174
// MARK: - Context Switching
226175

227-
func switchToContext(_ context: Context) {
228-
activeContexts.append(context)
229-
openContext(context)
176+
func switchToContext(contextID: UUID) {
177+
guard let latestContext = contexts.first(where: { $0.id == contextID }) else { return }
178+
activeContexts.append(latestContext)
179+
openContext(contextID: contextID)
230180
saveContexts()
231181
}
232182

@@ -279,31 +229,75 @@ class ContextManager: ObservableObject {
279229
}
280230

281231
/// Open all items in the context, batching by app/bundle where possible, using launchers
282-
private func openContext(_ context: Context) {
232+
private func openContext(contextID: UUID) {
233+
guard let contextToOpen = contexts.first(where: { $0.id == contextID }) else { return }
234+
print("🚀 Opening context '\(contextToOpen.name)' with \(contextToOpen.items.count) items")
235+
283236
var itemsByBundle: [String: [ContextItem]] = [:]
284-
for item in context.items {
237+
for item in contextToOpen.items {
285238
if let bundleId = bundleId(for: item) {
286239
itemsByBundle[bundleId, default: []].append(item)
287240
}
288241
}
242+
289243
var launchers: [ContextApplicationLauncher] = []
290244
for (bundleId, items) in itemsByBundle {
291245
let launcher = launcher(for: bundleId, items: items)
246+
247+
// Trace which launcher is opening which items
248+
let launcherType = String(describing: type(of: launcher))
249+
print("📱 Using \(launcherType) for bundle '\(bundleId)' with \(items.count) items:")
250+
251+
for item in items {
252+
switch item {
253+
case .application(let app):
254+
print(" • App: \(app.name) (\(app.bundleIdentifier))")
255+
if let windowTitle = app.windowTitle {
256+
print(" Window: \(windowTitle)")
257+
}
258+
if let filePath = app.filePath {
259+
print(" File: \(filePath)")
260+
}
261+
case .document(let doc):
262+
print(" • Document: \(doc.name) at \(doc.filePath)")
263+
case .browserTab(let tab):
264+
print(" • Browser Tab: \(tab.title) - \(tab.url)")
265+
case .terminalSession(let session):
266+
print(" • Terminal Session: \(session.title) in \(session.workingDirectory)")
267+
if let command = session.command {
268+
print(" Command: \(command)")
269+
}
270+
}
271+
}
272+
292273
launcher.open()
293274
launchers.append(launcher)
294275
}
276+
295277
// Store launchers for this context
296-
contextLaunchers[context.id] = launchers
278+
contextLaunchers[contextToOpen.id] = launchers
279+
297280
// Open terminal sessions directly (not via launcher abstraction)
298-
for item in context.items {
281+
let terminalSessions = contextToOpen.items.compactMap { item -> TerminalSession? in
282+
if case .terminalSession(let session) = item { return session } else { return nil }
283+
}
284+
285+
if !terminalSessions.isEmpty {
286+
print("🖥️ Opening \(terminalSessions.count) terminal session(s) directly:")
287+
}
288+
289+
for item in contextToOpen.items {
299290
if case .terminalSession(let session) = item {
300291
let commandToRun: String
301292
if let command = session.command, !command.isEmpty {
302293
commandToRun = command
303294
} else {
295+
print(" ⚠️ Skipping terminal session '\(session.title)' - no command specified")
304296
continue // Skip if no command
305297
}
306298
let workingDir = session.workingDirectory
299+
print(" • Terminal: '\(session.title)' in '\(workingDir)' running '\(commandToRun)'")
300+
307301
let script = """
308302
tell application \"Terminal\"
309303
activate
@@ -314,11 +308,15 @@ class ContextManager: ObservableObject {
314308
var error: NSDictionary? = nil
315309
appleScript.executeAndReturnError(&error)
316310
if let error = error {
317-
print("Failed to launch terminal session: \(error)")
311+
print(" ❌ Failed to launch terminal session: \(error)")
312+
} else {
313+
print(" ✅ Terminal session launched successfully")
318314
}
319315
}
320316
}
321317
}
318+
319+
print("✅ Context '\(contextToOpen.name)' opened with \(launchers.count) launcher(s) and \(terminalSessions.count) terminal session(s)")
322320
}
323321

324322
// MARK: - Context Reordering
@@ -330,16 +328,14 @@ class ContextManager: ObservableObject {
330328

331329
// MARK: - Application Management
332330

333-
func closeContext(_ context: Context) {
334-
// Use launchers to close apps/items
335-
if let launchers = contextLaunchers[context.id] {
331+
func closeContext(contextID: UUID) {
332+
if let launchers = contextLaunchers[contextID] {
336333
for launcher in launchers {
337334
launcher.close()
338335
}
339-
contextLaunchers.removeValue(forKey: context.id)
336+
contextLaunchers.removeValue(forKey: contextID)
340337
}
341-
// Remove the context from activeContexts if present
342-
activeContexts.removeAll { $0.id == context.id }
338+
activeContexts.removeAll { $0.id == contextID }
343339
}
344340

345341
func closeAllContexts() {
@@ -372,21 +368,9 @@ class ContextManager: ObservableObject {
372368
private func loadContexts() {
373369
do {
374370
let data = try Data(contentsOf: contextsFileURL)
375-
do {
376-
// Try decoding as new format
377-
let decoded = try JSONDecoder().decode([Context].self, from: data)
378-
self.contexts = decoded
379-
} catch {
380-
// Try to migrate from old format
381-
if let jsonArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
382-
let migrated = jsonArray.compactMap { Context.migrate(from: $0) }
383-
self.contexts = migrated
384-
// Save back in new format
385-
saveContexts()
386-
} else {
387-
print("Failed to decode or migrate contexts.json: \(error)")
388-
}
389-
}
371+
// Try decoding as new format
372+
let decoded = try JSONDecoder().decode([Context].self, from: data)
373+
self.contexts = decoded
390374
} catch {
391375
print("No existing contexts.json found or failed to read: \(error)")
392376
}
@@ -402,8 +386,8 @@ class ContextManager: ObservableObject {
402386
return nil
403387
}
404388

405-
func isActive(context: Context) -> Bool {
406-
return activeContexts.contains(where: { $0.id == context.id })
389+
func isActive(contextID: UUID) -> Bool {
390+
return activeContexts.contains(where: { $0.id == contextID })
407391
}
408392
}
409393

@@ -454,4 +438,16 @@ extension ContextManager {
454438
contexts[idx].moveItems(fromOffsets: fromOffsets, toOffset: toOffset)
455439
saveContexts()
456440
}
457-
}
441+
}
442+
443+
// Helper to move elements in array
444+
extension Array {
445+
mutating func move(fromOffsets: IndexSet, toOffset: Int) {
446+
let elements = fromOffsets.map { self[$0] }
447+
// Remove elements at offsets manually
448+
for offset in fromOffsets.sorted(by: >) {
449+
self.remove(at: offset)
450+
}
451+
self.insert(contentsOf: elements, at: toOffset)
452+
}
453+
}

Flitro/Editor/ContextDetailsView.swift

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ struct ContextDetailsView: View {
111111
let context = contextManager.contexts[contextIdx]
112112
VStack(spacing: 0) {
113113
ZStack {
114-
RoundedRectangle(cornerRadius: 16, style: .continuous)
114+
TopRoundedRectangle(radius: 16)
115115
.fill(Color(.controlBackgroundColor))
116116
.shadow(color: Color.black.opacity(0.07), radius: 8, x: 0, y: 2)
117117
if context.items.isEmpty {
@@ -136,11 +136,6 @@ struct ContextDetailsView: View {
136136
.listStyle(.inset)
137137
.background(Color.clear)
138138
.clipShape(TopRoundedRectangle(radius: 16))
139-
.overlay(
140-
TopRoundedRectangle(radius: 16)
141-
.stroke(Color.gray.opacity(0.13), lineWidth: 1)
142-
)
143-
.shadow(color: Color.black.opacity(0.07), radius: 8, x: 0, y: 2)
144139
.onDrop(of: [UTType.fileURL, UTType.url, UTType.text, UTType.plainText], isTargeted: nil) { providers in
145140
UniversalDropHandler.handleUniversalDrop(providers: providers, contextManager: contextManager, selectedContextID: selectedContextID)
146141
}
@@ -320,7 +315,7 @@ struct ContextButton: View {
320315
}
321316

322317
private var isActive: Bool {
323-
contextManager.isActive(context: context)
318+
contextManager.isActive(contextID: context.id)
324319
}
325320

326321
private var buttonText: String {
@@ -375,12 +370,12 @@ struct ContextButton: View {
375370
if isActive && isOptionPressed {
376371
contextManager.closeAllContexts()
377372
} else if isActive {
378-
contextManager.closeContext(context)
373+
contextManager.closeContext(contextID: context.id)
379374
} else if isOptionPressed {
380375
contextManager.closeAllContexts()
381-
contextManager.switchToContext(context)
376+
contextManager.switchToContext(contextID: context.id)
382377
} else {
383-
contextManager.switchToContext(context)
378+
contextManager.switchToContext(contextID: context.id)
384379
}
385380
}
386381
}

Flitro/FlitroApp.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ struct MenuBarExtraContents: View {
8989
ForEach(contextManager.contexts, id: \.reactiveId) { context in
9090
Menu {
9191
Button("Open") {
92-
contextManager.switchToContext(context)
92+
contextManager.switchToContext(contextID: context.id)
9393
}
9494
Divider()
9595
Button("Close") {
96-
contextManager.closeContext(context)
96+
contextManager.closeContext(contextID: context.id)
9797
}
9898
} label: {
9999
HStack {

Flitro/Sidebar/ContextCardView.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ struct ContextCardView: View {
8383
}
8484
.contextMenu {
8585
Button("Open") {
86-
contextManager.switchToContext(context)
86+
contextManager.switchToContext(contextID: context.id)
8787
}
88-
.disabled(contextManager.isActive(context: context))
88+
.disabled(contextManager.isActive(contextID: context.id))
8989
Button("Close") {
90-
contextManager.closeContext(context)
90+
contextManager.closeContext(contextID: context.id)
9191
}
92-
.disabled(!contextManager.isActive(context: context))
92+
.disabled(!contextManager.isActive(contextID: context.id))
9393
Button("Change Icon...") {
9494
showIconSelector = true
9595
}
@@ -109,4 +109,4 @@ struct ContextCardView: View {
109109
)
110110
}
111111
}
112-
}
112+
}

0 commit comments

Comments
 (0)