Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

File tree

8 files changed

+446
-2
lines changed

8 files changed

+446
-2
lines changed

‎Coder-Desktop/Coder-Desktop/Info.plist

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
<!DOCTYPEplist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plistversion="1.0">
44
<dict>
5+
<key>NSAppTransportSecurity</key>
6+
<dict>
7+
<!--
8+
Required to make HTTP (not HTTPS) requests to workspace agents
9+
(i.e. workspace.coder:4). These are already encrypted over wireguard.
10+
-->
11+
<key>NSAllowsArbitraryLoads</key>
12+
<true/>
13+
</dict>
514
<key>NetworkExtension</key>
615
<dict>
716
<key>NEMachServiceName</key>
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import CoderSDK
2+
import Foundation
3+
import SwiftUI
4+
5+
structFilePicker:View{
6+
@Environment(\.dismiss)vardismiss
7+
@StateObjectprivatevarmodel:FilePickerModel
8+
@Stateprivatevarselection:FilePickerEntryModel?
9+
10+
@BindingvaroutputAbsPath:String
11+
12+
letinspection=Inspection<Self>()
13+
14+
init(
15+
host:String,
16+
outputAbsPath:Binding<String>
17+
){
18+
_model=StateObject(wrappedValue:FilePickerModel(host: host))
19+
_outputAbsPath= outputAbsPath
20+
}
21+
22+
varbody:someView{
23+
VStack(spacing:0){
24+
if model.rootIsLoading{
25+
Spacer()
26+
ProgressView()
27+
.controlSize(.large)
28+
Spacer()
29+
}elseiflet loadError= model.error{
30+
Text("\(loadError.description)")
31+
.font(.headline)
32+
.foregroundColor(.red)
33+
.multilineTextAlignment(.center)
34+
.frame(maxWidth:.infinity, maxHeight:.infinity)
35+
.padding()
36+
}else{
37+
List(selection: $selection){
38+
ForEach(model.rootEntries){ entryin
39+
FilePickerEntry(entry: entry).tag(entry)
40+
}
41+
}.contextMenu(
42+
forSelectionType:FilePickerEntryModel.self,
43+
menu:{ _in},
44+
primaryAction:{ selectionsin
45+
// Per the type of `selection`, this will only ever be a set of
46+
// one entry.
47+
selections.forEach{ entryinwithAnimation{ entry.isExpanded.toggle()}}
48+
}
49+
).listStyle(.sidebar)
50+
}
51+
Divider()
52+
HStack{
53+
Spacer()
54+
Button("Cancel", action:{dismiss()}).keyboardShortcut(.cancelAction)
55+
Button("Select", action: submit).keyboardShortcut(.defaultAction).disabled(selection==nil)
56+
}.padding(20)
57+
}
58+
.onAppear{
59+
model.loadRoot()
60+
}
61+
.onReceive(inspection.notice){ inspection.visit(self, $0)} // ViewInspector
62+
}
63+
64+
privatefunc submit(){
65+
guardlet selectionelse{return}
66+
outputAbsPath= selection.absolute_path
67+
dismiss()
68+
}
69+
}
70+
71+
@MainActor
72+
classFilePickerModel:ObservableObject{
73+
@PublishedvarrootEntries:[FilePickerEntryModel]=[]
74+
@PublishedvarrootIsLoading:Bool=false
75+
@Publishedvarerror:ClientError?
76+
77+
// It's important that `AgentClient` is a reference type (class)
78+
// as we were having performance issues with a struct (unless it was a binding).
79+
letclient:AgentClient
80+
81+
init(host:String){
82+
client=AgentClient(agentHost: host)
83+
}
84+
85+
func loadRoot(){
86+
error=nil
87+
rootIsLoading=true
88+
Task{
89+
defer{ rootIsLoading=false}
90+
dothrows(ClientError){
91+
rootEntries=tryawait client
92+
.listAgentDirectory(.init(path:[], relativity:.root))
93+
.toModels(client: client)
94+
} catch{
95+
self.error= error
96+
}
97+
}
98+
}
99+
}
100+
101+
structFilePickerEntry:View{
102+
@ObservedObjectvarentry:FilePickerEntryModel
103+
104+
varbody:someView{
105+
Group{
106+
if entry.dir{
107+
directory
108+
}else{
109+
Label(entry.name, systemImage:"doc")
110+
.help(entry.absolute_path)
111+
.selectionDisabled()
112+
.foregroundColor(.secondary)
113+
}
114+
}
115+
}
116+
117+
privatevardirectory:someView{
118+
DisclosureGroup(isExpanded: $entry.isExpanded){
119+
iflet entries= entry.entries{
120+
ForEach(entries){ entryin
121+
FilePickerEntry(entry: entry).tag(entry)
122+
}
123+
}
124+
} label:{
125+
Label{
126+
Text(entry.name)
127+
ZStack{
128+
ProgressView().controlSize(.small).opacity(entry.isLoading && entry.error==nil?1:0)
129+
Image(systemName:"exclamationmark.triangle.fill")
130+
.opacity(entry.error!=nil?1:0)
131+
}
132+
} icon:{
133+
Image(systemName:"folder")
134+
}.help(entry.error!=nil? entry.error!.description: entry.absolute_path)
135+
}
136+
}
137+
}
138+
139+
@MainActor
140+
classFilePickerEntryModel:Identifiable,Hashable,ObservableObject{
141+
nonisolatedletid:[String]
142+
letname:String
143+
// Components of the path as an array
144+
letpath:[String]
145+
letabsolute_path:String
146+
letdir:Bool
147+
148+
letclient:AgentClient
149+
150+
@Publishedvarentries:[FilePickerEntryModel]?
151+
@PublishedvarisLoading=false
152+
@Publishedvarerror:ClientError?
153+
@PublishedprivatevarinnerIsExpanded=false
154+
varisExpanded:Bool{
155+
get{ innerIsExpanded}
156+
set{
157+
if !newValue{
158+
withAnimation{self.innerIsExpanded=false}
159+
}else{
160+
Task{
161+
self.loadEntries()
162+
}
163+
}
164+
}
165+
}
166+
167+
init(
168+
name:String,
169+
client:AgentClient,
170+
absolute_path:String,
171+
path:[String],
172+
dir:Bool=false,
173+
entries:[FilePickerEntryModel]?=nil
174+
){
175+
self.name= name
176+
self.client= client
177+
self.path= path
178+
self.dir= dir
179+
self.absolute_path= absolute_path
180+
self.entries= entries
181+
182+
// Swift Arrays are copy on write
183+
id= path
184+
}
185+
186+
func loadEntries(){
187+
self.error=nil
188+
withAnimation{ isLoading=true}
189+
Task{
190+
defer{
191+
withAnimation{
192+
isLoading=false
193+
innerIsExpanded=true
194+
}
195+
}
196+
dothrows(ClientError){
197+
entries=tryawait client
198+
.listAgentDirectory(.init(path: path, relativity:.root))
199+
.toModels(client: client)
200+
} catch{
201+
self.error= error
202+
}
203+
}
204+
}
205+
206+
nonisolatedstaticfunc==(lhs:FilePickerEntryModel, rhs:FilePickerEntryModel)->Bool{
207+
lhs.id== rhs.id
208+
}
209+
210+
nonisolatedfunc hash(into hasher:inoutHasher){
211+
hasher.combine(id)
212+
}
213+
}
214+
215+
extensionLSResponse{
216+
@MainActor
217+
func toModels(client:AgentClient)->[FilePickerEntryModel]{
218+
contents.compactMap{ entryin
219+
// Filter dotfiles from the picker
220+
guard !entry.name.hasPrefix(".")else{returnnil}
221+
222+
returnFilePickerEntryModel(
223+
name: entry.name,
224+
client: client,
225+
absolute_path: entry.absolute_path_string,
226+
path:self.absolute_path+[entry.name],
227+
dir: entry.is_dir,
228+
entries:nil
229+
)
230+
}
231+
}
232+
}

‎Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
1313

1414
@Stateprivatevarloading:Bool=false
1515
@StateprivatevarcreateError:DaemonError?
16+
@StateprivatevarpickingRemote:Bool=false
1617

1718
varbody:someView{
1819
letagents= vpn.menuState.onlineAgents
@@ -46,7 +47,16 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
4647
}
4748
}
4849
Section{
49-
TextField("Remote Path", text: $remotePath)
50+
HStack(spacing:5){
51+
TextField("Remote Path", text: $remotePath)
52+
Spacer()
53+
Button{
54+
pickingRemote=true
55+
} label:{
56+
Image(systemName:"folder")
57+
}.disabled(remoteHostname==nil)
58+
.help(remoteHostname==nil?"Select a workspace first":"Open File Picker")
59+
}
5060
}
5161
}.formStyle(.grouped).scrollDisabled(true).padding(.horizontal)
5262
Divider()
@@ -72,6 +82,9 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
7282
set:{if !$0{ createError=nil}}
7383
)){} message:{
7484
Text(createError?.description??"An unknown error occurred.")
85+
}.sheet(isPresented: $pickingRemote){
86+
FilePicker(host: remoteHostname!, outputAbsPath: $remotePath)
87+
.frame(width:300, height:400)
7588
}
7689
}
7790

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp