Skip to content

Commit ae1a037

Browse files
committed
refactor(FavoritesKit): improve protocols, switch to URL API, clean imports and docs
1 parent cbe1e2b commit ae1a037

File tree

10 files changed

+1386
-1300
lines changed

10 files changed

+1386
-1300
lines changed
Lines changed: 134 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,156 @@
1-
// DirectoryContextMenu.swift
2-
// MiMiNavigator
3-
//
4-
// Created by Iakov Senatov on 08.10.2025.
5-
// Copyright © 2025-2026 Senatov. All rights reserved.
6-
// Description: Context menu for directories - Finder-style layout
1+
// DirectoryContextMenu.swift
2+
// MiMiNavigator
3+
//
4+
// Created by Iakov Senatov on 08.10.2025.
5+
// Copyright © 2025-2026 Senatov. All rights reserved.
6+
// Description: Context menu for directories - Finder-style layout
77

8-
import FavoritesKit
9-
import SwiftUI
10-
import FileModelKit
8+
import FavoritesKit
9+
import SwiftUI
10+
import FileModelKit
1111

12-
/// Context menu for directory items.
13-
/// Matches Finder's context menu structure for folders.
14-
struct DirectoryContextMenu: View {
15-
let file: CustomFile
16-
let panelSide: PanelSide
17-
let onAction: (DirectoryAction) -> Void
18-
@State private var userFavorites = UserFavoritesStore.shared
12+
/// Context menu for directory items.
13+
/// Matches Finder's context menu structure for folders.
14+
struct DirectoryContextMenu: View {
15+
let file: CustomFile
16+
let panelSide: PanelSide
17+
let onAction: (DirectoryAction) -> Void
18+
@State private var userFavorites = UserFavoritesStore.shared
1919

20-
init(file: CustomFile, panelSide: PanelSide, onAction: @escaping (DirectoryAction) -> Void) {
21-
self.file = file
22-
self.panelSide = panelSide
23-
self.onAction = onAction
24-
}
25-
26-
var body: some View {
27-
Group {
28-
// ═══════════════════════════════════════════
29-
// SECTION 1: Navigation (directory-specific)
30-
// ═══════════════════════════════════════════
31-
menuButton(.open)
32-
menuButton(.openInNewTab)
33-
menuButton(.openInFinder)
34-
menuButton(.openInTerminal)
35-
menuButton(.viewLister)
36-
37-
Divider()
38-
39-
// ═══════════════════════════════════════════
40-
// SECTION 2: Edit actions (clipboard)
41-
// ═══════════════════════════════════════════
42-
menuButton(.cut)
43-
menuButton(.copy)
44-
menuButton(.copyAsPathname)
45-
menuButton(.paste)
46-
menuButton(.duplicate)
47-
48-
Divider()
49-
50-
// ═══════════════════════════════════════════
51-
// SECTION 3: Operations
52-
// ═══════════════════════════════════════════
53-
menuButton(.compress)
54-
menuButton(.share)
55-
56-
Divider()
57-
58-
// ═══════════════════════════════════════════
59-
// SECTION 4: Rename & Delete (danger zone)
60-
// ═══════════════════════════════════════════
61-
menuButton(.rename)
62-
menuButton(.delete)
63-
64-
Divider()
65-
66-
// ═══════════════════════════════════════════
67-
// SECTION 5: Info
68-
// ═══════════════════════════════════════════
69-
menuButton(.getInfo)
20+
init(file: CustomFile, panelSide: PanelSide, onAction: @escaping (DirectoryAction) -> Void) {
21+
self.file = file
22+
self.panelSide = panelSide
23+
self.onAction = onAction
24+
}
25+
26+
var body: some View {
27+
Group {
28+
// ═══════════════════════════════════════════
29+
// SECTION 1: Navigation (directory-specific)
30+
// ═══════════════════════════════════════════
31+
menuButton(.open)
32+
menuButton(.openInNewTab)
33+
menuButton(.openInFinder)
34+
menuButton(.openInTerminal)
35+
menuButton(.viewLister)
36+
37+
Divider()
38+
39+
// ═══════════════════════════════════════════
40+
// SECTION 2: Edit actions (clipboard)
41+
// ═══════════════════════════════════════════
42+
menuButton(.cut)
43+
menuButton(.copy)
44+
menuButton(.copyAsPathname)
45+
menuButton(.paste)
46+
menuButton(.duplicate)
47+
48+
Divider()
49+
50+
// ═══════════════════════════════════════════
51+
// SECTION 3: Operations
52+
// ═══════════════════════════════════════════
53+
menuButton(.compress)
54+
menuButton(.share)
55+
56+
Divider()
57+
58+
// ═══════════════════════════════════════════
59+
// SECTION 4: Rename & Delete (danger zone)
60+
// ═══════════════════════════════════════════
61+
menuButton(.rename)
62+
menuButton(.delete)
63+
64+
Divider()
65+
66+
// ═══════════════════════════════════════════
67+
// SECTION 5: Info
68+
// ═══════════════════════════════════════════
69+
menuButton(.getInfo)
7070

71-
Divider()
71+
Divider()
7272

73-
// ═══════════════════════════════════════════
74-
// SECTION 6: Cross-panel
75-
// ═══════════════════════════════════════════
76-
menuButton(.openOnOtherPanel)
73+
// ═══════════════════════════════════════════
74+
// SECTION 6: Cross-panel
75+
// ═══════════════════════════════════════════
76+
menuButton(.openOnOtherPanel)
7777

78-
Divider()
78+
Divider()
7979

80-
// ═══════════════════════════════════════════
81-
// SECTION 7: Favorites
82-
// ═══════════════════════════════════════════
83-
favoritesToggleButton
80+
// ═══════════════════════════════════════════
81+
// SECTION 7: Favorites
82+
// ═══════════════════════════════════════════
83+
favoritesToggleButton
84+
}
8485
}
85-
}
8686

87-
// MARK: - Add / Remove Favorites toggle
87+
// MARK: - Add / Remove Favorites toggle
8888

89-
@ViewBuilder
90-
private var favoritesToggleButton: some View {
91-
let isInFavorites = userFavorites.contains(path: file.pathStr)
92-
if isInFavorites {
93-
Button(role: .destructive) {
94-
userFavorites.remove(path: file.pathStr)
95-
log.info("[Favorites] directory removed via context menu: \(file.pathStr)")
96-
} label: {
97-
Label("Remove from Favorites", systemImage: "star.slash.fill")
89+
@ViewBuilder
90+
private var favoritesToggleButton: some View {
91+
let isInFavorites = userFavorites.contains(url: file.urlValue)
92+
if isInFavorites {
93+
Button(role: .destructive) {
94+
userFavorites.remove(url: file.urlValue)
95+
log.info("[Favorites] directory removed via context menu: \(file.urlValue.path)")
96+
} label: {
97+
Label("Remove from Favorites", systemImage: "star.slash.fill")
98+
}
99+
} else {
100+
menuButton(.addToFavorites)
98101
}
99-
} else {
100-
menuButton(.addToFavorites)
101102
}
102-
}
103103

104-
// MARK: - Menu Button Builder
104+
// MARK: - Menu Button Builder
105105

106-
@ViewBuilder
107-
private func menuButton(_ action: DirectoryAction) -> some View {
108-
Button {
109-
log.debug("\(#function) action=\(action.rawValue) dir='\(file.nameStr)'")
110-
onAction(action)
111-
} label: {
112-
Label {
113-
HStack {
114-
Text(action.title)
115-
Spacer()
116-
if let shortcut = action.shortcutHint {
117-
Text(shortcut)
118-
.font(.caption)
119-
.foregroundStyle(.secondary)
106+
@ViewBuilder
107+
private func menuButton(_ action: DirectoryAction) -> some View {
108+
Button {
109+
log.debug("\(#function) action=\(action.rawValue) dir='\(file.nameStr)'")
110+
onAction(action)
111+
} label: {
112+
Label {
113+
HStack {
114+
Text(action.title)
115+
Spacer()
116+
if let shortcut = action.shortcutHint {
117+
Text(shortcut)
118+
.font(.caption)
119+
.foregroundStyle(.secondary)
120+
}
120121
}
122+
} icon: {
123+
Image(systemName: action.systemImage)
121124
}
122-
} icon: {
123-
Image(systemName: action.systemImage)
124125
}
126+
.disabled(isActionDisabled(action))
125127
}
126-
.disabled(isActionDisabled(action))
127-
}
128-
129-
// MARK: - Action State
130-
131-
private func isActionDisabled(_ action: DirectoryAction) -> Bool {
132-
switch action {
133-
case .paste:
134-
return !ClipboardManager.shared.hasContent
135-
default:
136-
return false
128+
129+
// MARK: - Action State
130+
131+
private func isActionDisabled(_ action: DirectoryAction) -> Bool {
132+
switch action {
133+
case .paste:
134+
return !ClipboardManager.shared.hasContent
135+
default:
136+
return false
137+
}
137138
}
138139
}
139-
}
140140

141-
// MARK: - Preview
142-
#Preview {
143-
VStack {
144-
Text("Right-click for directory menu")
145-
}
146-
.frame(width: 300, height: 200)
147-
.contextMenu {
148-
DirectoryContextMenu(
149-
file: CustomFile(path: "/Users"),
150-
panelSide: .left,
151-
onAction: { action in
152-
log.debug("Action: \(action)")
153-
}
154-
)
141+
// MARK: - Preview
142+
#Preview {
143+
VStack {
144+
Text("Right-click for directory menu")
145+
}
146+
.frame(width: 300, height: 200)
147+
.contextMenu {
148+
DirectoryContextMenu(
149+
file: CustomFile(path: "/Users"),
150+
panelSide: .left,
151+
onAction: { action in
152+
log.debug("Action: \(action)")
153+
}
154+
)
155+
}
155156
}
156-
}

GUI/Sources/ContextMenu/Menus/FileContextMenu.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
let panelSide: PanelSide
1919
let onAction: (FileAction) -> Void
2020
@Environment(\.dismiss) private var dismiss
21-
@State private var userFavorites = UserFavoritesStore.shared
21+
private let userFavorites = UserFavoritesStore.shared
2222
// Pre-loaded once at init — prevents re-init of OpenWithSubmenu on every body re-evaluation
2323
@State private var openWithApps: [AppInfo]
2424

@@ -117,11 +117,11 @@
117117

118118
@ViewBuilder
119119
private var favoritesToggleButton: some View {
120-
let isInFavorites = userFavorites.contains(path: file.pathStr)
120+
let isInFavorites = userFavorites.contains(url: file.urlValue)
121121
if isInFavorites {
122122
Button(role: .destructive) {
123-
userFavorites.remove(path: file.pathStr)
124-
log.info("[Favorites] file removed via context menu: \(file.pathStr)")
123+
userFavorites.remove(url: file.urlValue)
124+
log.info("[Favorites] file removed via context menu: \(file.urlValue.path)")
125125
} label: {
126126
Label("Remove from Favorites", systemImage: "star.slash.fill")
127127
}
@@ -175,7 +175,10 @@
175175
.frame(width: 300, height: 200)
176176
.contextMenu {
177177
FileContextMenu(
178-
file: CustomFile(path: "/test/document.txt"),
178+
file: CustomFile(
179+
url: URL(fileURLWithPath: "/test/document.txt"),
180+
resourceValues: URLResourceValues()
181+
),
179182
panelSide: .left,
180183
onAction: { action in
181184
log.debug("Action: \(action)")

0 commit comments

Comments
 (0)