Skip to content

Commit ae921ee

Browse files
committed
Refactor SelectionsHistory to URL-based API and fix history deletion
• Replace String paths with URL in SelectionsHistory API • Make recentSelections observable (remove @ObservationIgnored) • Fix deletion by comparing normalized URL paths • Ensure clear() resets recent selections • Update HistoryWindowContent to use URL identity instead of String • Fix SwiftUI list updates when removing history items
1 parent 0669936 commit ae921ee

File tree

18 files changed

+1919
-1653
lines changed

18 files changed

+1919
-1653
lines changed

GUI/Sources/ContextMenu/Services/Coordinator/PanelBgActionsHandler.swift

Lines changed: 170 additions & 170 deletions
Large diffs are not rendered by default.

GUI/Sources/Favorites/ButtonFavTopPanel.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
// Copyright © 2026 Senatov. All rights reserved.
88

99
import AppKit
10-
import FileModelKit
1110
import FavoritesKit
11+
import FileModelKit
1212
import SwiftUI
1313

1414
// MARK: - Navigation Panel with Favorites Button
@@ -88,7 +88,7 @@ struct ButtonFavTopPanel: View {
8888
navigationAdapter?.navigateUp(panel: panelSide.toFavPanelSide)
8989
}) {
9090
Image(systemName: "arrowshape.up").symbolRenderingMode(.hierarchical)
91-
.foregroundStyle(iconColor)
91+
.foregroundStyle(iconColor)
9292
}
9393
.buttonStyle(.plain)
9494
.shadow(color: .gray, radius: 7.0, x: 1, y: 1)
@@ -122,9 +122,9 @@ struct ButtonFavTopPanel: View {
122122
private func historyButton() -> some View {
123123
Button(action: { openHistoryWindow() }) {
124124
Image(systemName: "clock.arrow.circlepath")
125-
.font(.system(size: 13, weight: .medium))
126-
.symbolRenderingMode(.hierarchical)
127-
.foregroundStyle(iconColor)
125+
.font(.system(size: 15, weight: .semibold))
126+
.symbolRenderingMode(.monochrome)
127+
.foregroundStyle(Color(nsColor: .systemGreen))
128128
}
129129
.buttonStyle(ToolbarIconButtonStyle())
130130
.help("Show navigation history")
@@ -134,9 +134,9 @@ struct ButtonFavTopPanel: View {
134134
private func favoritesButton() -> some View {
135135
Button(action: { openFavoritesWindow() }) {
136136
Image(systemName: panelSide == .left ? "sidebar.left" : "sidebar.right")
137-
.font(.system(size: 13, weight: .medium))
138-
.symbolRenderingMode(.hierarchical)
139-
.foregroundStyle(iconColor)
137+
.font(.system(size: 15, weight: .semibold))
138+
.symbolRenderingMode(.monochrome)
139+
.foregroundStyle(Color(nsColor: .systemTeal))
140140
}
141141
.buttonStyle(ToolbarIconButtonStyle())
142142
.help("Navigation between favorites — \(String(describing: panelSide))")
Lines changed: 112 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,146 @@
1-
//
2-
// FavoritesNavigationAdapter.swift
3-
// MiMiNavigator
4-
//
5-
// Created by Iakov Senatov on 17.01.2026.
6-
// Copyright © 2026 Senatov. All rights reserved.
7-
//
8-
// Adapter to connect FavoritesKit with MiMiNavigator's AppState
9-
10-
import FavoritesKit
11-
import FileModelKit
12-
import Foundation
13-
14-
// MARK: - Adapter connecting FavoritesKit to AppState
15-
@MainActor
16-
final class FavoritesNavigationAdapter: FavoritesNavigationDelegate {
17-
18-
private let appState: AppState
19-
20-
init(appState: AppState) {
21-
self.appState = appState
22-
}
1+
//
2+
// FavoritesNavigationAdapter.swift
3+
// MiMiNavigator
4+
//
5+
// Created by Iakov Senatov on 17.01.2026.
6+
// Copyright © 2026 Senatov. All rights reserved.
7+
//
8+
// Adapter to connect FavoritesKit with MiMiNavigator's AppState
9+
10+
import FavoritesKit
11+
import FileModelKit
12+
import Foundation
13+
14+
// MARK: - Adapter connecting FavoritesKit to AppState
15+
@MainActor
16+
final class FavoritesNavigationAdapter: FavoritesNavigationDelegate {
17+
18+
private let appState: AppState
19+
20+
init(appState: AppState) {
21+
self.appState = appState
22+
}
2323

24-
// MARK: - FavoritesNavigationDelegate
24+
// MARK: - FavoritesNavigationDelegate
2525

26-
var focusedPanel: FavPanelSide {
27-
appState.focusedPanel == .left ? .left : .right
28-
}
26+
var focusedPanel: FavPanelSide {
27+
appState.focusedPanel == .left ? .left : .right
28+
}
2929

30-
func setFocusedPanel(_ panel: FavPanelSide) {
31-
appState.focusedPanel = panel == .left ? .left : .right
32-
}
30+
func setFocusedPanel(_ panel: FavPanelSide) {
31+
appState.focusedPanel = panel == .left ? .left : .right
32+
}
3333

34-
func navigate(to url: URL, panel: FavPanelSide) async {
35-
let targetPanel: PanelSide = panel == .left ? .left : .right
34+
func navigate(to url: URL, panel: FavPanelSide) async {
35+
let targetPanel: PanelSide = panel == .left ? .left : .right
3636

37-
log.info("\(#function) url=\(url.path) panel=\(panel)")
37+
log.info("\(#function) url=\(url.path) panel=\(panel)")
3838

39-
// Update path
40-
appState.updatePath(url.path, for: targetPanel)
39+
// Update path
40+
appState.updatePath(url.path, for: targetPanel)
4141

42-
// Update selectedDir for UI consistency
43-
let file = CustomFile(name: url.lastPathComponent, path: url.path)
44-
appState.selectedDir.selectedFSEntity = file
42+
// Update selectedDir for UI consistency
43+
let file = CustomFile(name: url.lastPathComponent, path: url.path)
44+
appState.selectedDir.selectedFSEntity = file
4545

46-
// Refresh files
47-
await appState.scanner.resetRefreshTimer(for: targetPanel)
48-
await appState.scanner.refreshFiles(currSide: targetPanel)
49-
}
46+
// Refresh files
47+
await appState.scanner.resetRefreshTimer(for: targetPanel)
48+
await appState.scanner.refreshFiles(currSide: targetPanel)
49+
}
5050

51-
func navigateBack(panel: FavPanelSide) {
52-
log.info("\(#function) panel=\(panel)")
51+
func navigateBack(panel: FavPanelSide) {
52+
log.info("\(#function) panel=\(panel)")
5353

54-
let targetPanel: PanelSide = panel == .left ? .left : .right
55-
let history = appState.navigationHistory(for: targetPanel)
54+
let targetPanel: PanelSide = panel == .left ? .left : .right
55+
let history = appState.navigationHistory(for: targetPanel)
5656

57-
guard let path = history.goBack() else {
58-
log.debug("\(#function) no history to go back to")
59-
return
60-
}
57+
guard let path = history.goBack() else {
58+
log.debug("\(#function) no history to go back to")
59+
return
60+
}
6161

62-
log.debug("\(#function) goBack returned path=\(path)")
62+
log.debug("\(#function) goBack returned path=\(path)")
6363

64-
Task {
65-
appState.isNavigatingFromHistory = true
66-
log.debug("\(#function) set isNavigatingFromHistory=true")
67-
await navigate(to: URL(fileURLWithPath: path), panel: panel)
68-
appState.isNavigatingFromHistory = false
69-
log.debug("\(#function) set isNavigatingFromHistory=false")
64+
Task {
65+
appState.isNavigatingFromHistory = true
66+
log.debug("\(#function) set isNavigatingFromHistory=true")
67+
await navigate(to: path, panel: panel)
68+
appState.isNavigatingFromHistory = false
69+
log.debug("\(#function) set isNavigatingFromHistory=false")
70+
}
7071
}
71-
}
7272

73-
func navigateForward(panel: FavPanelSide) {
74-
log.info("\(#function) panel=\(panel)")
73+
func navigateForward(panel: FavPanelSide) {
74+
log.info("\(#function) panel=\(panel)")
7575

76-
let targetPanel: PanelSide = panel == .left ? .left : .right
77-
let history = appState.navigationHistory(for: targetPanel)
76+
let targetPanel: PanelSide = panel == .left ? .left : .right
77+
let history = appState.navigationHistory(for: targetPanel)
7878

79-
guard let path = history.goForward() else {
80-
log.debug("\(#function) no history to go forward to")
81-
return
82-
}
79+
guard let path = history.goForward() else {
80+
log.debug("\(#function) no history to go forward to")
81+
return
82+
}
8383

84-
log.debug("\(#function) goForward returned path=\(path)")
84+
log.debug("\(#function) goForward returned path=\(path)")
8585

86-
Task {
87-
appState.isNavigatingFromHistory = true
88-
log.debug("\(#function) set isNavigatingFromHistory=true")
89-
await navigate(to: URL(fileURLWithPath: path), panel: panel)
90-
appState.isNavigatingFromHistory = false
91-
log.debug("\(#function) set isNavigatingFromHistory=false")
86+
Task {
87+
appState.isNavigatingFromHistory = true
88+
log.debug("\(#function) set isNavigatingFromHistory=true")
89+
await navigate(to: path, panel: panel)
90+
appState.isNavigatingFromHistory = false
91+
log.debug("\(#function) set isNavigatingFromHistory=false")
92+
}
9293
}
93-
}
9494

95-
func navigateUp(panel: FavPanelSide) {
96-
log.info("\(#function) panel=\(panel)")
95+
func navigateUp(panel: FavPanelSide) {
96+
log.info("\(#function) panel=\(panel)")
9797

98-
let currentPath = panel == .left ? appState.leftPath : appState.rightPath
99-
let parentURL = URL(fileURLWithPath: currentPath).deletingLastPathComponent()
98+
let currentPath = panel == .left ? appState.leftPath : appState.rightPath
99+
let parentURL = URL(fileURLWithPath: currentPath).deletingLastPathComponent()
100100

101-
guard parentURL.path != currentPath else {
102-
log.debug("\(#function) already at root")
103-
return
104-
}
101+
guard parentURL.path != currentPath else {
102+
log.debug("\(#function) already at root")
103+
return
104+
}
105105

106-
Task {
107-
await navigate(to: parentURL, panel: panel)
106+
Task {
107+
await navigate(to: parentURL, panel: panel)
108+
}
108109
}
109-
}
110110

111-
func navigateToURL(_ url: URL, panel: FavoritesKit.FavPanelSide) async {
112-
// Forward to the main navigation implementation
113-
await navigate(to: url, panel: panel)
114-
}
111+
func navigateToURL(_ url: URL, panel: FavoritesKit.FavPanelSide) async {
112+
// Forward to the main navigation implementation
113+
await navigate(to: url, panel: panel)
114+
}
115115

116-
func currentURL(for panel: FavoritesKit.FavPanelSide) -> URL {
117-
// Convert AppState path to URL
118-
let path = panel == .left ? appState.leftPath : appState.rightPath
119-
return URL(fileURLWithPath: path)
120-
}
116+
func currentURL(for panel: FavoritesKit.FavPanelSide) -> URL {
117+
// Convert AppState path to URL
118+
let path = panel == .left ? appState.leftPath : appState.rightPath
119+
return URL(fileURLWithPath: path)
120+
}
121121

122-
func canGoBack(panel: FavPanelSide) -> Bool {
123-
let targetPanel: PanelSide = panel == .left ? .left : .right
124-
return appState.navigationHistory(for: targetPanel).canGoBack
125-
}
122+
func canGoBack(panel: FavPanelSide) -> Bool {
123+
let targetPanel: PanelSide = panel == .left ? .left : .right
124+
return appState.navigationHistory(for: targetPanel).canGoBack
125+
}
126126

127-
func canGoForward(panel: FavPanelSide) -> Bool {
128-
let targetPanel: PanelSide = panel == .left ? .left : .right
129-
return appState.navigationHistory(for: targetPanel).canGoForward
130-
}
127+
func canGoForward(panel: FavPanelSide) -> Bool {
128+
let targetPanel: PanelSide = panel == .left ? .left : .right
129+
return appState.navigationHistory(for: targetPanel).canGoForward
130+
}
131131

132-
func currentPath(for panel: FavPanelSide) -> String {
133-
panel == .left ? appState.leftPath : appState.rightPath
132+
func currentPath(for panel: FavPanelSide) -> String {
133+
panel == .left ? appState.leftPath : appState.rightPath
134+
}
134135
}
135-
}
136136

137-
// MARK: - Extension to convert between PanelSide and FavPanelSide
138-
extension PanelSide {
139-
var toFavPanelSide: FavPanelSide {
140-
self == .left ? .left : .right
141-
}
137+
// MARK: - Extension to convert between PanelSide and FavPanelSide
138+
extension PanelSide {
139+
var toFavPanelSide: FavPanelSide {
140+
self == .left ? .left : .right
141+
}
142142

143-
init(from favPanel: FavPanelSide) {
144-
self = favPanel == .left ? .left : .right
143+
init(from favPanel: FavPanelSide) {
144+
self = favPanel == .left ? .left : .right
145+
}
145146
}
146-
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// AsyncFileIconView.swift
3+
// MiMiNavigator
4+
//
5+
// Created by Iakov Senatov on 11.03.2026.
6+
// Copyright © 2026 Senatov. All rights reserved.
7+
//
8+
9+
import FileModelKit
10+
import SwiftUI
11+
12+
struct AsyncFileIconView: View {
13+
14+
let file: CustomFile
15+
16+
@State private var icon: NSImage?
17+
18+
var body: some View {
19+
Group {
20+
if let icon {
21+
Image(nsImage: icon)
22+
.resizable()
23+
.interpolation(.high)
24+
} else {
25+
Image(systemName: "doc")
26+
.symbolRenderingMode(.hierarchical)
27+
}
28+
}
29+
.task(id: file.urlValue.path) {
30+
await loadIcon()
31+
}
32+
}
33+
34+
private func loadIcon() async {
35+
let path = file.urlValue.path
36+
37+
let icon = await MainActor.run {
38+
FileIconCache.shared.icon(for: path)
39+
}
40+
41+
self.icon = icon
42+
}
43+
}

0 commit comments

Comments
 (0)