diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 95d9fca793..c463c1a01f 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -315,7 +315,7 @@ B61A606929F4481A009B43F9 /* MonospacedFontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */; }; B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */; }; B640A99E29E2184700715F20 /* SettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A99D29E2184700715F20 /* SettingsForm.swift */; }; - B640A9A129E2188F00715F20 /* SettingsDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */; }; + B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */; }; B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; }; B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; }; B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */; }; @@ -325,6 +325,8 @@ B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5229C91831004573B4 /* CodeEditCommands.swift */; }; B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5529C918A0004573B4 /* SceneID.swift */; }; B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; + B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; + B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; B6C6A42A297716A500A3D28F /* TabBarItemCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */; }; B6C6A42E29771A8D00A3D28F /* TabBarItemButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */; }; B6C6A43029771F7100A3D28F /* TabBarItemBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42F29771F7100A3D28F /* TabBarItemBackground.swift */; }; @@ -333,7 +335,7 @@ B6E41C7029DD157F0088F9F4 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */; }; B6E41C7429DD40010088F9F4 /* View+HideSidebarToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */; }; B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */; }; - B6E41C7C29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */; }; + B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */; }; B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */; }; B6E41C8F29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */; }; B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */; }; @@ -717,7 +719,7 @@ B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonospacedFontPicker.swift; sourceTree = ""; }; B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = ""; }; B640A99D29E2184700715F20 /* SettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsForm.swift; sourceTree = ""; }; - B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailsView.swift; sourceTree = ""; }; + B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+NavigationBarBackButtonVisible.swift"; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WorkspaceView.swift; sourceTree = ""; tabWidth = 4; }; B658FB3327DA9E1000EA4DBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -732,6 +734,8 @@ B66A4E5229C91831004573B4 /* CodeEditCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditCommands.swift; sourceTree = ""; }; B66A4E5529C918A0004573B4 /* SceneID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneID.swift; sourceTree = ""; }; B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; + B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; + B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemCloseButton.swift; sourceTree = ""; }; B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarItemButtonStyle.swift; sourceTree = ""; }; B6C6A42F29771F7100A3D28F /* TabBarItemBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemBackground.swift; sourceTree = ""; }; @@ -740,7 +744,7 @@ B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = ""; }; B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+HideSidebarToggle.swift"; sourceTree = ""; }; B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSelectionView.swift; sourceTree = ""; }; - B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccoundsSettingsProviderRow.swift; sourceTree = ""; }; + B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsProviderRow.swift; sourceTree = ""; }; B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsSigninView.swift; sourceTree = ""; }; B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsDetailsView.swift; sourceTree = ""; }; B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlAccount.swift; sourceTree = ""; }; @@ -2194,10 +2198,10 @@ children = ( B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */, B640A99D29E2184700715F20 /* SettingsForm.swift */, - B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */, B6EA1FFF29DB7966001BF195 /* SettingsColorPicker.swift */, B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */, B61A606029F188AB009B43F9 /* ExternalLink.swift */, + B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */, B6EA200129DB7F81001BF195 /* View+ConstrainHeightToWindow.swift */, B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */, ); @@ -2209,10 +2213,12 @@ children = ( B6E41C9229DEAE0A0088F9F4 /* Models */, B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */, - B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */, + B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */, + B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */, B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */, B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */, B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */, + B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */, ); path = AccountsSettings; sourceTree = ""; @@ -2758,7 +2764,7 @@ 587B9E8929301D8F00AC7927 /* GitHubGist.swift in Sources */, 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */, 58D01C94293167DC00C5B6B4 /* Color+HEX.swift in Sources */, - B640A9A129E2188F00715F20 /* SettingsDetailsView.swift in Sources */, + B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */, 58798251292E78D80085B254 /* OtherFileView.swift in Sources */, 587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */, 587B9E8029301D8F00AC7927 /* GitHubConfiguration.swift in Sources */, @@ -2781,9 +2787,10 @@ 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */, - B6E41C7C29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift in Sources */, + B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */, + B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */, B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */, 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */, 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, @@ -2805,6 +2812,7 @@ 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */, 587B9E5929301D8F00AC7927 /* GitCheckoutBranchView+CheckoutBranch.swift in Sources */, 58F2EB09292FB2B0004A9BDE /* TerminalSettings.swift in Sources */, + B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */, B6EA200229DB7F81001BF195 /* View+ConstrainHeightToWindow.swift in Sources */, 6C48D8F42972DB1A00D6D205 /* Env+Window.swift in Sources */, 6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */, diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift new file mode 100644 index 0000000000..ed3621cd2a --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift @@ -0,0 +1,36 @@ +// +// AccountsSettingsAccountLink.swift +// CodeEdit +// +// Created by Austin Condiff on 4/30/23. +// + +import SwiftUI + +struct AccountsSettingsAccountLink: View { + @Binding var account: SourceControlAccount + + init(_ account: Binding) { + _account = account + } + + var body: some View { + NavigationLink(destination: AccountsSettingsDetailsView($account)) { + Label { + Text(account.description) + Text(account.name) + .font(.footnote) + .foregroundColor(.secondary) + } icon: { + Image(account.provider.iconName) + .resizable() + .aspectRatio(contentMode: .fill) + .cornerRadius(6) + .frame(width: 26, height: 26) + .padding(.top, 2) + .padding(.bottom, 2) + .padding(.leading, 2) + } + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index a46c0e61e3..64e915932b 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -8,84 +8,160 @@ import SwiftUI struct AccountsSettingsDetailsView: View { + @Environment(\.dismiss) private var dismiss @AppSettings(\.accounts.sourceControlAccounts.sshKey) var sshKey @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) var gitAccounts - @Binding var account: SourceControlAccount - @Environment(\.dismiss) private var dismiss - - @State var cloneUsing: Bool = false + @State var currentAccount: SourceControlAccount @State var deleteConfirmationIsPresented: Bool = false + @State var prevSshKey: String + @State var createSshKeyIsPresented: Bool = false init(_ account: Binding) { _account = account + _currentAccount = State(initialValue: account.wrappedValue) + _prevSshKey = State(initialValue: account.sshKey.wrappedValue) + } + + /// Default instance of the `FileManager` + private let filemanager = FileManager.default + + /// The URL of the users ssh folder. Points to `~/.ssh` + internal var sshURL: URL { + filemanager + .homeDirectoryForCurrentUser + .appendingPathComponent(".ssh", isDirectory: true) + } + + func isPrivateSSHKey(_ contents: String) -> Bool { + if contents.starts(with: "-----BEGIN OPENSSH PRIVATE KEY-----\n") && + contents.hasSuffix("\n-----END OPENSSH PRIVATE KEY-----\n") { + return true + } else { + return false + } + } + + func isPublicSSHKey(_ contents: String) -> Bool { + let sshKeyPattern = "^ssh-(rsa|dss|ed25519)\\s+[A-Za-z0-9+/]+[=]{0,2}(\\s+.+)?$" + do { + let regex = try NSRegularExpression(pattern: sshKeyPattern) + let range = NSRange(location: 0, length: contents.utf16.count) + return regex.firstMatch(in: contents, options: [], range: range) != nil + } catch { + print("Error creating regular expression: \(error.localizedDescription)") + return false + } } var body: some View { - SettingsDetailsView(title: account.description) { - SettingsForm { - Section { - LabeledContent("Account") { - Text(account.name) - } - TextField("Description", text: $account.description) - if account.provider.baseURL == nil { - TextField("Server", text: $account.serverURL) - } + SettingsForm { + Section { + LabeledContent("Account") { + Text(currentAccount.name) } + TextField("Description", text: $currentAccount.description) + if currentAccount.provider.baseURL == nil { + TextField("Server", text: $currentAccount.serverURL) + } + } - Section { - Picker(selection: $cloneUsing) { - Text("HTTPS") - .tag(false) // temporary - Text("SSH") - .tag(true) // temporary - } label: { - Text("Clone Using") - Text("New repositories will be cloned from \(account.provider.name)" - + " using \(cloneUsing ? "SSH" : "HTTPS").") - } - .pickerStyle(.radioGroup) - - Picker("SSH Key", selection: $sshKey) { + Section { + Picker(selection: $currentAccount.urlProtocol) { + Text("HTTPS") + .tag(SourceControlAccount.URLProtocol.https) + Text("SSH") + .tag(SourceControlAccount.URLProtocol.ssh) + } label: { + Text("Clone Using") + Text("New repositories will be cloned from \(currentAccount.provider.name)" + + " using \(currentAccount.urlProtocol.rawValue).") + } + .pickerStyle(.radioGroup) + if currentAccount.urlProtocol == .ssh { + Picker("SSH Key", selection: $currentAccount.sshKey) { Text("None") + .tag("") + Divider() + if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( + ".ssh", + isDirectory: true + ) as URL? { + if let files = try? FileManager.default.contentsOfDirectory( + atPath: sshPath.path + ) { + ForEach(files, id: \.self) { filename in + let fileURL = sshPath.appendingPathComponent(filename) + if let contents = try? String(contentsOf: fileURL) { + if isPublicSSHKey(contents) { + Text(filename.replacingOccurrences(of: ".pub", with: "")) + .tag(fileURL.path) + } + } + } + Divider() + } + } Text("Create New...") + .tag("CREATE_NEW") Text("Choose...") + .tag("CHOOSE") } - } footer: { - HStack { - Button("Delete Account...") { - deleteConfirmationIsPresented.toggle() + .onReceive([currentAccount.sshKey].publisher.first()) { value in + if value == "CREATE_NEW" { + print("Create a new ssh key...") + createSshKeyIsPresented = true + currentAccount.sshKey = prevSshKey + } else if value == "CHOOSE" { + print("Choose a ssh key...") + currentAccount.sshKey = prevSshKey + } else { + // TODO: Validate SSH key and check if it is uploaded to git provider. + // If not provide button to do so } - .alert( - Text("Are you sure you want to delete the account “\(account.description)”?"), - isPresented: $deleteConfirmationIsPresented - ) { - Button("OK") { - // Handle the account delete - handleAccountDelete() - dismiss() - } - Button("Cancel") { - // Handle the cancel, dismiss the alert - deleteConfirmationIsPresented.toggle() - } - } message: { - Text("Deleting this account will remove it from CodeEdit.") + prevSshKey = currentAccount.sshKey + } + .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) + } + } footer: { + HStack { + Button("Delete Account...") { + deleteConfirmationIsPresented.toggle() + } + .alert( + Text("Are you sure you want to delete the account “\(account.description)”?"), + isPresented: $deleteConfirmationIsPresented + ) { + Button("OK") { + // Handle the account delete + handleAccountDelete() + dismiss() } - - Spacer() + Button("Cancel") { + // Handle the cancel, dismiss the alert + deleteConfirmationIsPresented.toggle() + } + } message: { + Text("Deleting this account will remove it from CodeEdit.") } - .padding(.top, 10) + + Spacer() } + .padding(.top, 10) } } + .onChange(of: currentAccount) { newValue in + account = newValue + } + .navigationTitle(currentAccount.description) + .navigationBarBackButtonVisible() } private func handleAccountDelete() { // Delete account by finding the position of the account and remove by position - // We can abort if it is `nil` because account should exist - gitAccounts.remove(at: gitAccounts.firstIndex(of: account)!) + if let gitAccount = gitAccounts.firstIndex(of: account) { + gitAccounts.remove(at: gitAccount) + } } } diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccoundsSettingsProviderRow.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift similarity index 100% rename from CodeEdit/Features/Settings/Pages/AccountsSettings/AccoundsSettingsProviderRow.swift rename to CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift index bb44326904..9e86cfed84 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift @@ -127,6 +127,9 @@ struct AccountsSettingsSigninView: View { } .formStyle(.grouped) .scrollDisabled(true) + .onSubmit { + signin() + } HStack { Button { addAccountSheetPresented.toggle() @@ -202,7 +205,7 @@ struct AccountsSettingsSigninView: View { description: provider.name, provider: provider, serverURL: providerLink, - urlProtocol: true, + urlProtocol: .https, sshKey: "", isTokenValid: true ) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift index 9baefee8b3..6d90c62cc2 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift @@ -21,7 +21,7 @@ struct AccountsSettingsView: View { .foregroundColor(.secondary) .frame(maxWidth: .infinity, alignment: .center) } else { - ForEach($gitAccounts) { $account in + ForEach($gitAccounts, id: \.self) { $account in AccountsSettingsAccountLink($account) } } @@ -53,40 +53,11 @@ struct AccountsSettingsView: View { Button("Close") { addAccountSheetPresented.toggle() selectedProvider = nil - } - .buttonStyle(.borderedProminent) + .buttonStyle(.borderedProminent) } .frame(maxWidth: .infinity, alignment: .trailing) } .padding(20) } } - -struct AccountsSettingsAccountLink: View { - @Binding var account: SourceControlAccount - - init(_ account: Binding) { - _account = account - } - - var body: some View { - NavigationLink(destination: AccountsSettingsDetailsView($account)) { - Label { - Text(account.provider.name) - Text(account.name) - .font(.footnote) - .foregroundColor(.secondary) - } icon: { - Image(account.provider.iconName) - .resizable() - .aspectRatio(contentMode: .fill) - .cornerRadius(6) - .frame(width: 26, height: 26) - .padding(.top, 2) - .padding(.bottom, 2) - .padding(.leading, 2) - } - } - } -} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift new file mode 100644 index 0000000000..81ff28d4f2 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift @@ -0,0 +1,67 @@ +// +// CreateSSHKeyView.swift +// CodeEdit +// +// Created by Austin Condiff on 4/28/23. +// + +import SwiftUI + +struct CreateSSHKeyView: View { + @Environment(\.dismiss) private var dismiss + + enum KeyType: String, CaseIterable { + case ed25519 = "ED25519" + case ecdsa = "ECDSA" + case rsa = "RSA" + case dsa = "DSA" + } + + @State var selectedKeyType: KeyType = .ed25519 + @State var passphrase: String = "" + @State var confirmPassphrase: String = "" + + var body: some View { + VStack { + Form { + Section("Create SSH key") { + Picker("Key Type", selection: $selectedKeyType) { + Text(KeyType.ed25519.rawValue) + .tag(KeyType.ed25519) + Text(KeyType.ecdsa.rawValue) + .tag(KeyType.ecdsa) + Divider() + Group { + Text(KeyType.rsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) + } + .tag(KeyType.rsa) + Group { + Text(KeyType.dsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) + } + .tag(KeyType.dsa) + } + SecureField("Passphrase", text: $passphrase) + if !passphrase.isEmpty { + SecureField("Confirm Passphrase", text: $confirmPassphrase) + } + } + } + .formStyle(.grouped) + .fixedSize() + .scrollDisabled(true) + HStack { + Spacer() + Button("Cancel") { + dismiss() + } + Button("Create") { + // create the ssh key + dismiss() + } + .buttonStyle(.borderedProminent) + } + .padding(.horizontal, 20) + .padding(.bottom, 20) + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift index 646b63222f..27e74dc38b 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift @@ -14,7 +14,7 @@ struct SourceControlAccount: Codable, Identifiable, Hashable { description: String, provider: Provider, serverURL: String, - urlProtocol: Bool, + urlProtocol: URLProtocol, sshKey: String, isTokenValid: Bool ) { @@ -35,10 +35,15 @@ struct SourceControlAccount: Codable, Identifiable, Hashable { var serverURL: String // TODO: Should we use an enum instead of a boolean here: // If true we use the HTTP protocol else if false we use SSH - var urlProtocol: Bool + var urlProtocol: URLProtocol var sshKey: String var isTokenValid: Bool + enum URLProtocol: String, Codable, CaseIterable { + case https = "HTTPS" + case ssh = "SSH" + } + enum Provider: Codable, CaseIterable, Identifiable { case bitbucketCloud case bitbucketServer diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index 2b4235a15c..30e092ff4b 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -59,23 +59,6 @@ struct SettingsView: View { .searchable(text: $searchText, placement: .sidebar, prompt: "Search") .scrollDisabled(true) } -// .safeAreaInset(edge: .top) { -// TextField("Search", text: $searchText, prompt: Text("Search")) -// .textFieldStyle(.roundedBorder) -// .padding(.horizontal, 10) -// .controlSize(.large) -// .introspectTextField { textField in -// let iconImage = NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: nil) -// -// let attachment = NSTextAttachment() -// attachment.contents = iconImage -// -// let cell = NSTextAttachmentCell(imageCell: iconImage) -// attachment.attachmentCell = cell -// -// textField.setCell_(NSSearchFieldCell.alloc().init()) -// } -// } } detail: { Group { switch selectedPage.name { @@ -100,13 +83,13 @@ struct SettingsView: View { .navigationSplitViewColumnWidth(500) .hideSidebarToggle() .onAppear { - model.showingDetails = false + model.backButtonVisible = false } } .navigationTitle(selectedPage.name.rawValue) .toolbar { ToolbarItem(placement: .navigation) { - if !model.showingDetails { + if !model.backButtonVisible { Rectangle() .frame(width: 10) .opacity(0) @@ -121,6 +104,6 @@ struct SettingsView: View { } class SettingsViewModel: ObservableObject { - @Published var showingDetails: Bool = false + @Published var backButtonVisible: Bool = false @Published var scrolledToTop: Bool = false } diff --git a/CodeEdit/Features/Settings/Views/SettingsDetailsView.swift b/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift similarity index 59% rename from CodeEdit/Features/Settings/Views/SettingsDetailsView.swift rename to CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift index 91f0c921a5..9c4dbb4e41 100644 --- a/CodeEdit/Features/Settings/Views/SettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift @@ -1,5 +1,5 @@ // -// SettingsDetailsView.swift +// View+NavigationBarBackButtonVisible.swift // CodeEdit // // Created by Austin Condiff on 4/8/23. @@ -7,18 +7,12 @@ import SwiftUI -struct SettingsDetailsView: View { +struct NavigationBarBackButtonVisible: ViewModifier { @Environment(\.presentationMode) var presentationMode @EnvironmentObject var model: SettingsViewModel - let title: String - - @ViewBuilder - var content: Content - - var body: some View { + func body(content: Content) -> some View { content - .navigationTitle("") .toolbar { ToolbarItem(placement: .navigation) { Button { @@ -27,16 +21,17 @@ struct SettingsDetailsView: View { } label: { Image(systemName: "chevron.left") } - Text(title) } } .hideSidebarToggle() - .task { - let window = NSApp.windows.first { $0.identifier?.rawValue == "settings" }! - window.title = title - } .onAppear { - model.showingDetails = true + model.backButtonVisible = true } } } + +extension View { + func navigationBarBackButtonVisible() -> some View { + modifier(NavigationBarBackButtonVisible()) + } +}