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

Commite25df16

Browse files
committed
feat: add stubbed file sync UI
1 parent173554d commite25df16

18 files changed

+323
-16
lines changed

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ struct DesktopApp: App {
2323
.environmentObject(appDelegate.state)
2424
}
2525
.windowResizability(.contentSize)
26+
Window("File Sync", id:Windows.fileSync.rawValue){
27+
FileSyncConfig<CoderVPNService,MutagenDaemon>()
28+
.environmentObject(appDelegate.state)
29+
.environmentObject(appDelegate.fileSyncDaemon)
30+
.environmentObject(appDelegate.vpn)
31+
}
2632
}
2733
}
2834

@@ -56,9 +62,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5662
awaitself.state.handleTokenExpiry()
5763
}
5864
}, content:{
59-
VPNMenu<CoderVPNService>().frame(width:256)
65+
VPNMenu<CoderVPNService,MutagenDaemon>().frame(width:256)
6066
.environmentObject(self.vpn)
6167
.environmentObject(self.state)
68+
.environmentObject(self.fileSyncDaemon)
6269
}
6370
))
6471
// Subscribe to system VPN updates
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import VPNLib
2+
3+
@MainActor
4+
finalclassPreviewFileSync:FileSyncDaemon{
5+
varstate:DaemonState=.running
6+
7+
init(){}
8+
9+
func start()asyncthrows(DaemonError){
10+
state=.running
11+
}
12+
13+
func stop()async{
14+
state=.stopped
15+
}
16+
17+
func listSessions()asyncthrows->[FileSyncSession]{
18+
[]
19+
}
20+
21+
func createSession(with _:FileSyncSession)asyncthrows{}
22+
}

‎Coder-Desktop/Coder-Desktop/State.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ class AppState: ObservableObject {
6363
}
6464
}
6565

66+
// Temporary feature flag
67+
@PublishedvarshowFileSyncUI:Bool=UserDefaults.standard.bool(forKey:Keys.showFileSyncUI){
68+
didSet{
69+
guard persistentelse{return}
70+
UserDefaults.standard.set(showFileSyncUI, forKey:Keys.showFileSyncUI)
71+
}
72+
}
73+
6674
func tunnelProviderProtocol()->NETunnelProviderProtocol?{
6775
if !hasSession{returnnil}
6876
letproto=NETunnelProviderProtocol()
@@ -164,6 +172,8 @@ class AppState: ObservableObject {
164172
staticletliteralHeaders="LiteralHeaders"
165173
staticletstopVPNOnQuit="StopVPNOnQuit"
166174
staticletstartVPNOnLaunch="StartVPNOnLaunch"
175+
176+
staticletshowFileSyncUI="showFileSyncUI"
167177
}
168178
}
169179

‎Coder-Desktop/Coder-Desktop/VPN/MenuState.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ struct VPNMenuState {
135135
return items.sorted()
136136
}
137137

138+
varonlineAgents:[Agent]{
139+
agents.map(\.value).filter{ $0.primaryHost!=nil}
140+
}
141+
138142
mutatingfunc clear(){
139143
agents.removeAll()
140144
workspaces.removeAll()
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import SwiftUI
2+
import VPNLib
3+
4+
structFileSyncRow:Identifiable{
5+
varid=UUID()
6+
varlocalPath:URL
7+
varworkspace:String
8+
// This is a string as to be host-OS agnostic
9+
varremotePath:String
10+
varstatus:FileSyncStatus
11+
varsize:String
12+
}
13+
14+
enumFileSyncStatus{
15+
case unknown
16+
case error(String)
17+
case okay
18+
case paused
19+
case needsAttention(String)
20+
case working(String)
21+
22+
varcolor:Color{
23+
switchself{
24+
case.okay:
25+
.white
26+
case.paused:
27+
.secondary
28+
case.unknown:
29+
.red
30+
case.error:
31+
.red
32+
case.needsAttention:
33+
.orange
34+
case.working:
35+
.white
36+
}
37+
}
38+
39+
vardescription:String{
40+
switchself{
41+
case.unknown:
42+
"Unknown"
43+
caselet.error(msg):
44+
msg
45+
case.okay:
46+
"OK"
47+
case.paused:
48+
"Paused"
49+
caselet.needsAttention(msg):
50+
msg
51+
caselet.working(msg):
52+
msg
53+
}
54+
}
55+
56+
varbody:someView{
57+
Text(description).foregroundColor(color)
58+
}
59+
}
60+
61+
structFileSyncConfig<VPN:VPNService, FS:FileSyncDaemon>:View{
62+
@EnvironmentObjectvarvpn:VPN
63+
64+
@Stateprivatevarselection:FileSyncRow.ID?
65+
@StateprivatevaraddingNewSession:Bool=false
66+
@Stateprivatevaritems:[FileSyncRow]=[]
67+
68+
varbody:someView{
69+
Group{
70+
Table(items, selection: $selection){
71+
TableColumn("Local Path"){ rowin
72+
Text(row.localPath.path())
73+
}.width(min:200, ideal:240)
74+
TableColumn("Workspace", value: \.workspace)
75+
.width(min:100, ideal:120)
76+
TableColumn("Remote Path", value: \.remotePath)
77+
.width(min:100, ideal:120)
78+
TableColumn("Status"){ $0.status.body}
79+
.width(min:80, ideal:100)
80+
TableColumn("Size"){ itemin
81+
Text(item.size)
82+
}
83+
.width(min:60, ideal:80)
84+
}
85+
.frame(minWidth:400, minHeight:200)
86+
.padding(.bottom,25)
87+
.overlay(alignment:.bottom){
88+
VStack(alignment:.leading, spacing:0){
89+
Divider()
90+
HStack(spacing:0){
91+
Button{
92+
addingNewSession=true
93+
} label:{
94+
Image(systemName:"plus")
95+
.frame(width:24, height:24)
96+
}.disabled(vpn.menuState.agents.isEmpty)
97+
Divider()
98+
Button{
99+
// TODO: Remove from list
100+
} label:{
101+
Image(systemName:"minus").frame(width:24, height:24)
102+
}.disabled(selection==nil)
103+
iflet selection{
104+
iflet selectedSession= items.first(where:{ $0.id== selection}){
105+
Divider()
106+
Button{
107+
// TODO: Pause & Unpause
108+
} label:{
109+
switch selectedSession.status{
110+
case.paused:
111+
Image(systemName:"play").frame(width:24, height:24)
112+
default:
113+
Image(systemName:"pause").frame(width:24, height:24)
114+
}
115+
}
116+
}
117+
}
118+
}
119+
.buttonStyle(.borderless)
120+
}
121+
.background(.primary.opacity(0.04))
122+
.fixedSize(horizontal:false, vertical:true)
123+
}
124+
}.sheet(isPresented: $addingNewSession){
125+
FileSyncSessionModal<VPN,FS>()
126+
.frame(width:550)
127+
}.onTapGesture{
128+
selection=nil
129+
}
130+
}
131+
}
132+
133+
#if DEBUG
134+
#Preview{
135+
FileSyncConfig<PreviewVPN,PreviewFileSync>()
136+
.environmentObject(AppState(persistent:false))
137+
.environmentObject(PreviewVPN())
138+
.environmentObject(PreviewFileSync())
139+
}
140+
#endif
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import SwiftUI
2+
import VPNLib
3+
4+
structFileSyncSessionModal<VPN:VPNService, FS:FileSyncDaemon>:View{
5+
varexistingSession:FileSyncRow?
6+
@Environment(\.dismiss)privatevardismiss
7+
@EnvironmentObjectprivatevarvpn:VPN
8+
@EnvironmentObjectprivatevarfileSync:FS
9+
10+
@StateprivatevarlocalPath:String=""
11+
@Stateprivatevarworkspace:UUID?
12+
@StateprivatevarremotePath:String=""
13+
14+
varbody:someView{
15+
letagents= vpn.menuState.onlineAgents
16+
VStack(spacing:0){
17+
Form{
18+
Section{
19+
HStack{
20+
Text("Local Path")
21+
Text(localPath)
22+
Spacer()
23+
Button{
24+
letpanel=NSOpenPanel()
25+
panel.allowsMultipleSelection=false
26+
panel.canChooseDirectories=true
27+
panel.canChooseFiles=false
28+
if panel.runModal()==.OK{
29+
localPath= panel.url?.path(percentEncoded:false)??"<none>"
30+
}
31+
} label:{
32+
Image(systemName:"folder")
33+
}
34+
}
35+
}
36+
Section{
37+
Picker("Workspace", selection: $workspace){
38+
ForEach(agents){ agentin
39+
Text(agent.primaryHost!).tag(agent.id)
40+
}
41+
}
42+
}
43+
Section{
44+
TextField("Remote Path", text: $remotePath)
45+
}
46+
}.formStyle(.grouped).scrollDisabled(true).padding(.horizontal)
47+
Divider()
48+
HStack{
49+
Spacer()
50+
Button("Cancel", action:{dismiss()}).keyboardShortcut(.cancelAction)
51+
Button(existingSession==nil?"Add":"Save", action: submit)
52+
.keyboardShortcut(.defaultAction)
53+
}.padding(20)
54+
}.onAppear{
55+
if existingSession!=nil{
56+
// TODO: Populate form
57+
}else{
58+
workspace= agents.first?.id
59+
}
60+
}
61+
}
62+
63+
func submit(){
64+
defer{
65+
// TODO: Instruct window to refresh state via gRPC
66+
dismiss()
67+
}
68+
if existingSession!=nil{
69+
// TODO: Delete existing
70+
}
71+
// TODO: Insert
72+
}
73+
}

‎Coder-Desktop/Coder-Desktop/Views/Settings/GeneralTab.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ struct GeneralTab: View {
1818
Text("Start Coder Connect on launch")
1919
}
2020
}
21+
Section{
22+
Toggle(isOn: $state.showFileSyncUI){
23+
Text("Show experimental File Sync UI")
24+
}
25+
}
2126
}.formStyle(.grouped)
2227
}
2328
}

‎Coder-Desktop/Coder-Desktop/Views/Settings/LiteralHeadersSection.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct LiteralHeadersSection<VPN: VPNService>: View {
1515
Toggle(isOn: $state.useLiteralHeaders){
1616
Text("HTTP Headers")
1717
Text("When enabled, these headers will be included on all outgoing HTTP requests.")
18-
if vpn.state!=.disabled{Text("Cannot be modified while Coder Connect is enabled.")}
18+
if!vpn.state.canBeStarted{Text("Cannot be modified while Coder Connect is enabled.")}
1919
}
2020
.controlSize(.large)
2121

@@ -65,7 +65,7 @@ struct LiteralHeadersSection<VPN: VPNService>: View {
6565
LiteralHeaderModal(existingHeader: header)
6666
}.onTapGesture{
6767
selectedHeader=nil
68-
}.disabled(vpn.state!=.disabled)
68+
}.disabled(!vpn.state.canBeStarted)
6969
.onReceive(inspection.notice){ inspection.visit(self, $0)} // ViewInspector
7070
}
7171
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import SwiftUI
2+
3+
structStatusDot:View{
4+
letcolor:Color
5+
6+
varbody:someView{
7+
ZStack{
8+
Circle()
9+
.fill(color.opacity(0.4))
10+
.frame(width:12, height:12)
11+
Circle()
12+
.fill(color.opacity(1.0))
13+
.frame(width:7, height:7)
14+
}
15+
}
16+
}

‎Coder-Desktop/Coder-Desktop/Views/VPNMenu.swiftrenamed to‎Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import SwiftUI
2+
import VPNLib
23

3-
structVPNMenu<VPN:VPNService>:View{
4+
structVPNMenu<VPN:VPNService, FS:FileSyncDaemon>:View{
45
@EnvironmentObjectvarvpn:VPN
6+
@EnvironmentObjectvarfileSync:FS
57
@EnvironmentObjectvarstate:AppState
68
@Environment(\.openSettings)privatevaropenSettings
79
@Environment(\.openWindow)privatevaropenWindow
@@ -60,6 +62,19 @@ struct VPNMenu<VPN: VPNService>: View {
6062
}.buttonStyle(.plain)
6163
TrayDivider()
6264
}
65+
if state.showFileSyncUI, vpn.state==.connected{
66+
Button{
67+
openWindow(id:.fileSync)
68+
} label:{
69+
ButtonRowView{
70+
HStack{
71+
StatusDot(color: fileSync.state.color)
72+
Text("Configure file sync")
73+
}
74+
}
75+
}.buttonStyle(.plain)
76+
TrayDivider()
77+
}
6378
if vpn.state==.failed(.systemExtensionError(.needsUserApproval)){
6479
Button{
6580
openSystemExtensionSettings()
@@ -119,8 +134,9 @@ func openSystemExtensionSettings() {
119134
appState.login(baseAccessURL:URL(string:"http://127.0.0.1:8080")!, sessionToken:"")
120135
// appState.clearSession()
121136

122-
returnVPNMenu<PreviewVPN>().frame(width:256)
137+
returnVPNMenu<PreviewVPN,PreviewFileSync>().frame(width:256)
123138
.environmentObject(PreviewVPN())
124139
.environmentObject(appState)
140+
.environmentObject(PreviewFileSync())
125141
}
126142
#endif

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp