Skip to content

Commit c85654a

Browse files
committed
Support exporting custom commands
1 parent 1100db9 commit c85654a

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

Core/Sources/HostApp/CustomCommandSettings/CustomCommand.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ struct CustomCommandFeature: ReducerProtocol {
1717
case editCommand(CustomCommand)
1818
case editCustomCommand(EditCustomCommand.Action)
1919
case deleteCommand(CustomCommand)
20+
case exportCommand(CustomCommand)
21+
case importCommand(at: URL)
22+
case importCommandClicked
2023
}
2124

2225
@Dependency(\.toast) var toast
@@ -49,6 +52,68 @@ struct CustomCommandFeature: ReducerProtocol {
4952
return .none
5053
case .editCustomCommand:
5154
return .none
55+
case let .exportCommand(command):
56+
return .run { _ in
57+
do {
58+
let data = try JSONEncoder().encode(command)
59+
let filename = "CustomCommand-\(command.name).json"
60+
61+
let url = await withCheckedContinuation { continuation in
62+
Task { @MainActor in
63+
let panel = NSSavePanel()
64+
panel.canCreateDirectories = true
65+
panel.nameFieldStringValue = filename
66+
let result = await panel.begin()
67+
switch result {
68+
case .OK:
69+
continuation.resume(returning: panel.url)
70+
default:
71+
continuation.resume(returning: nil)
72+
}
73+
}
74+
}
75+
76+
if let url {
77+
try data.write(to: url)
78+
toast("Saved!", .info)
79+
}
80+
81+
} catch {
82+
toast(error.localizedDescription, .error)
83+
}
84+
}
85+
86+
case let .importCommand(url):
87+
do {
88+
let data = try Data(contentsOf: url)
89+
var command = try JSONDecoder().decode(CustomCommand.self, from: data)
90+
command.commandId = UUID().uuidString
91+
settings.customCommands.append(command)
92+
toast("Imported custom command \(command.name)!", .info)
93+
} catch {
94+
toast("Failed to import command: \(error.localizedDescription)", .error)
95+
}
96+
return .none
97+
98+
case .importCommandClicked:
99+
return .run { send in
100+
let url = await withCheckedContinuation { continuation in
101+
Task { @MainActor in
102+
let panel = NSOpenPanel()
103+
panel.allowedContentTypes = [.json]
104+
let result = await panel.begin()
105+
if result == .OK {
106+
continuation.resume(returning: panel.url)
107+
} else {
108+
continuation.resume(returning: nil)
109+
}
110+
}
111+
}
112+
113+
if let url {
114+
await send(.importCommand(at: url))
115+
}
116+
}
52117
}
53118
}.ifLet(\.editCustomCommand, action: /Action.editCustomCommand) {
54119
EditCustomCommand(settings: settings)

Core/Sources/HostApp/CustomCommandSettings/CustomCommandView.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import PlusFeatureFlag
44
import Preferences
55
import SharedUIComponents
66
import SwiftUI
7+
import Toast
78

89
extension List {
910
@ViewBuilder
@@ -51,7 +52,7 @@ struct CustomCommandView: View {
5152
@ViewBuilder
5253
var leftPane: some View {
5354
List {
54-
ForEach(settings.customCommands, id: \.name) { command in
55+
ForEach(settings.customCommands, id: \.commandId) { command in
5556
CommandButton(store: store, command: command)
5657
}
5758
.onMove(perform: { indices, newOffset in
@@ -92,6 +93,34 @@ struct CustomCommandView: View {
9293
}
9394
.buttonStyle(.plain)
9495
.padding()
96+
.contextMenu {
97+
Button("Import") {
98+
store.send(.importCommandClicked)
99+
}
100+
}
101+
}
102+
.onDrop(of: [.json], delegate: FileDropDelegate(store: store, toast: toast))
103+
}
104+
105+
struct FileDropDelegate: DropDelegate {
106+
let store: StoreOf<CustomCommandFeature>
107+
let toast: (String, ToastType) -> Void
108+
func performDrop(info: DropInfo) -> Bool {
109+
let jsonFiles = info.itemProviders(for: [.json])
110+
for file in jsonFiles {
111+
file.loadInPlaceFileRepresentation(forTypeIdentifier: "public.json") { url, _, error in
112+
Task { @MainActor in
113+
if let url {
114+
print(url)
115+
store.send(.importCommand(at: url))
116+
} else if let error {
117+
toast(error.localizedDescription, .error)
118+
}
119+
}
120+
}
121+
}
122+
123+
return !jsonFiles.isEmpty
95124
}
96125
}
97126

@@ -143,6 +172,10 @@ struct CustomCommandView: View {
143172
Button("Remove") {
144173
store.send(.deleteCommand(command))
145174
}
175+
176+
Button("Export") {
177+
store.send(.exportCommand(command))
178+
}
146179
}
147180
}
148181
}

0 commit comments

Comments
 (0)