secretive/Secretive/Views/ContentView.swift

150 lines
6.8 KiB
Swift
Raw Normal View History

2020-02-19 03:36:41 +00:00
import SwiftUI
2020-02-19 04:52:00 +00:00
import SecretKit
2020-03-22 01:43:26 +00:00
import Brief
2020-02-19 04:52:00 +00:00
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
2020-03-09 03:03:40 +00:00
@ObservedObject var storeList: SecretStoreList
2020-03-15 08:01:40 +00:00
@ObservedObject var updater: UpdaterType
@ObservedObject var agentStatusChecker: AgentStatusCheckerType
var runSetupBlock: (() -> Void)?
@State fileprivate var active: AnySecret.ID?
@State fileprivate var showingDeletion = false
@State fileprivate var deletingSecret: AnySecret?
2020-02-19 03:36:41 +00:00
var body: some View {
2020-03-15 08:01:40 +00:00
VStack {
if updater.update != nil {
updateNotice()
}
if !agentStatusChecker.running {
agentNotice()
}
2020-03-21 03:20:20 +00:00
if storeList.anyAvailable {
2020-03-15 08:01:40 +00:00
NavigationView {
List(selection: $active) {
ForEach(storeList.stores) { store in
if store.isAvailable {
Section(header: Text(store.name)) {
if store.secrets.isEmpty {
if store is AnySecretStoreModifiable {
NavigationLink(destination: EmptyStoreModifiableView(), tag: Constants.emptyStoreModifiableTag, selection: self.$active) {
Text("No Secrets")
}
} else {
NavigationLink(destination: EmptyStoreView(), tag: Constants.emptyStoreTag, selection: self.$active) {
Text("No Secrets")
}
}
} else {
2020-03-15 08:01:40 +00:00
ForEach(store.secrets) { secret in
NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) {
Text(secret.name)
}.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { self.delete(secret: secret) }) {
Text("Delete")
}
}
2020-03-09 03:03:40 +00:00
}
}
2020-03-07 23:42:40 +00:00
}
2020-03-04 07:14:38 +00:00
}
}
}
2020-03-15 08:01:40 +00:00
}.onAppear {
2020-03-15 21:03:57 +00:00
self.active = self.nextDefaultSecret
2020-02-19 04:52:00 +00:00
}
2020-03-15 08:01:40 +00:00
.listStyle(SidebarListStyle())
.frame(minWidth: 100, idealWidth: 240)
2020-02-19 04:52:00 +00:00
}
2020-03-15 08:01:40 +00:00
.navigationViewStyle(DoubleColumnNavigationViewStyle())
.sheet(isPresented: $showingDeletion) {
if self.storeList.modifiableStore != nil {
2020-03-15 21:03:57 +00:00
DeleteSecretView(secret: self.deletingSecret!, store: self.storeList.modifiableStore!) { deleted in
2020-03-15 08:01:40 +00:00
self.showingDeletion = false
2020-03-15 21:03:57 +00:00
if deleted {
self.active = self.nextDefaultSecret
}
2020-03-15 08:01:40 +00:00
}
2020-03-09 03:03:40 +00:00
}
}
2020-03-21 03:20:20 +00:00
} else {
NoStoresView()
}
2020-03-22 01:10:37 +00:00
}.frame(minWidth: 640, minHeight: 320)
2020-02-19 03:36:41 +00:00
}
2020-03-15 08:01:40 +00:00
func updateNotice() -> some View {
guard let update = updater.update else { return AnyView(Spacer()) }
let severity: NoticeView.Severity
let text: String
if update.critical {
severity = .critical
text = "Critical Security Update Required"
} else {
severity = .advisory
text = "Update Available"
}
return AnyView(NoticeView(text: text, severity: severity, actionTitle: "Update") {
NSWorkspace.shared.open(update.html_url)
})
}
func agentNotice() -> some View {
NoticeView(text: "Secret Agent isn't running. Run setup again to fix.", severity: .advisory, actionTitle: "Run Setup") {
self.runSetupBlock?()
}
}
2020-03-09 03:03:40 +00:00
func delete<SecretType: Secret>(secret: SecretType) {
deletingSecret = AnySecret(secret)
self.showingDeletion = true
2020-03-04 07:14:38 +00:00
}
2020-03-15 21:03:57 +00:00
var nextDefaultSecret: AnyHashable? {
let fallback: AnyHashable
if self.storeList.modifiableStore?.isAvailable ?? false {
fallback = Constants.emptyStoreModifiableTag
} else {
fallback = Constants.emptyStoreTag
}
2020-04-05 23:05:45 +00:00
return self.storeList.stores.compactMap(\.secrets.first).first?.id ?? fallback
2020-03-15 21:03:57 +00:00
}
2020-03-04 07:14:38 +00:00
}
2020-03-15 08:01:40 +00:00
fileprivate enum Constants {
static let emptyStoreModifiableTag: AnyHashable = "emptyStoreModifiableTag"
static let emptyStoreTag: AnyHashable = "emptyStoreModifiableTag"
2020-03-11 08:53:31 +00:00
}
2020-03-15 08:01:40 +00:00
2020-03-12 06:07:22 +00:00
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView(storeList: Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)],
modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]),
updater: PreviewUpdater(),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(stores: [Preview.Store()], modifiableStores: [Preview.StoreModifiable()]), updater: PreviewUpdater(),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(stores: [Preview.Store()]), updater: PreviewUpdater(),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(modifiableStores: [Preview.StoreModifiable()]), updater: PreviewUpdater(),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]), updater: PreviewUpdater(update: .advisory),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]), updater: PreviewUpdater(update: .critical),
agentStatusChecker: PreviewAgentStatusChecker())
ContentView(storeList: Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]), updater: PreviewUpdater(update: .critical),
agentStatusChecker: PreviewAgentStatusChecker(running: false))
}
}
}
2020-03-12 06:07:22 +00:00
#endif