diff --git a/Frameworks/Resources/Localizable.xcstrings b/Frameworks/Resources/Localizable.xcstrings index 24a17ca0..765909da 100644 --- a/Frameworks/Resources/Localizable.xcstrings +++ b/Frameworks/Resources/Localizable.xcstrings @@ -26104,7 +26104,6 @@ } }, "The saved password is no longer available." : { - "extractionState" : "stale", "localizations" : { "zh-Hans" : { "stringUnit" : { diff --git a/TFI/Features/Snippets/MobileSnippetsView.swift b/TFI/Features/Snippets/MobileSnippetsView.swift index 625319eb..493d83ec 100644 --- a/TFI/Features/Snippets/MobileSnippetsView.swift +++ b/TFI/Features/Snippets/MobileSnippetsView.swift @@ -238,6 +238,19 @@ struct MobileSnippetsView: View { .frame(width: 44, height: 44) } .accessibilityLabel("More") + } else if case .ungrouped = section.id { + Menu { + Button("New Snippet", systemImage: "curlybraces") { + editorRequest = SnippetEditorRequest(snippetID: nil, packageID: nil) + } + Button("New Package", systemImage: "folder.badge.plus") { + packageEditorRequest = PackageEditorRequest(packageID: nil, parentID: nil) + } + } label: { + Image(systemName: "ellipsis") + .frame(width: 44, height: 44) + } + .accessibilityLabel("More") } } .accessibilityElement(children: .contain) diff --git a/TFI/Features/Vault/MobileVaultView.swift b/TFI/Features/Vault/MobileVaultView.swift index 68ff2853..34223ae6 100644 --- a/TFI/Features/Vault/MobileVaultView.swift +++ b/TFI/Features/Vault/MobileVaultView.swift @@ -483,6 +483,19 @@ struct MobileVaultView: View { .frame(width: 44, height: 44) } .accessibilityLabel("More") + } else if case .ungrouped = section.id { + Menu { + Button("New Host", systemImage: "server.rack") { + editingHost = Host(name: "", host: "") + } + Button("New Group", systemImage: "folder.badge.plus") { + groupEditorRequest = GroupEditorRequest(groupID: nil, parentID: nil) + } + } label: { + Image(systemName: "ellipsis") + .frame(width: 44, height: 44) + } + .accessibilityLabel("More") } } .accessibilityElement(children: .contain) diff --git a/TFM/Features/Hosts/HostsPageView.swift b/TFM/Features/Hosts/HostsPageView.swift index 8eb74ad0..e0d8d4a3 100644 --- a/TFM/Features/Hosts/HostsPageView.swift +++ b/TFM/Features/Hosts/HostsPageView.swift @@ -19,6 +19,7 @@ final class HostsPageState { var query = "" var selectedGroupID: HostGroup.ID? + var isUngroupedSelected = false /// Which tags the grid is narrowed to, ticked in the titlebar search /// field's magnifier menu. Several at once means "carries all of these". var selectedTags: Set = [] @@ -145,7 +146,7 @@ struct HostsPageView: ManagedCardPage { Divider() DestructiveMenuButton("Delete Group") { requestDelete(group) } } - } else { + } else if case .ungrouped = section.id { CardSectionHeader( title: section.title, count: section.items.count, @@ -153,6 +154,22 @@ struct HostsPageView: ManagedCardPage { { app.hostLibrary.setUngroupedExpanded(!expanded) } }, isCollapsed: !(section.isExpanded ?? true), + select: selectUngrouped, + isSelected: state.isUngroupedSelected, + isDropTarget: isDropTarget + ) + .contextMenu { + Button("New Host", systemImage: MenuSymbol.newHost) { + openHostEditor(nil) + } + Button("New Group", systemImage: MenuSymbol.newGroup) { + openWork(.hostGroupEditor(groupID: nil, parentID: nil)) + } + } + } else { + CardSectionHeader( + title: section.title, + count: section.items.count, isDropTarget: isDropTarget ) } @@ -279,15 +296,23 @@ struct HostsPageView: ManagedCardPage { private func setHostSelection(_ ids: Set) { let valid = ids.intersection(Set(app.hostLibrary.hosts.map(\.id))) state.selectedGroupID = nil + state.isUngroupedSelected = false let ordered = visibleHosts.filter { valid.contains($0.id) }.map { VaultSelectionID.host($0.id) } window.setVaultSelection(ordered, for: .hosts) } private func selectGroup(_ id: HostGroup.ID) { window.clearVaultSelection(for: .hosts) + state.isUngroupedSelected = false state.selectedGroupID = id } + private func selectUngrouped() { + window.clearVaultSelection(for: .hosts) + state.selectedGroupID = nil + state.isUngroupedSelected = true + } + private func pruneHostSelection() { setHostSelection(hostSelection) } diff --git a/TFM/Features/Snippets/SnippetsPageView.swift b/TFM/Features/Snippets/SnippetsPageView.swift index 3ddd0c6b..97b542e6 100644 --- a/TFM/Features/Snippets/SnippetsPageView.swift +++ b/TFM/Features/Snippets/SnippetsPageView.swift @@ -36,6 +36,7 @@ final class SnippetsPageState { var query = "" var selectedPackageID: SnippetPackage.ID? + var isUngroupedSelected = false var confirmation: ConfirmationRoute? } @@ -145,8 +146,18 @@ struct SnippetsPageView: ManagedCardPage { ) }, isCollapsed: !app.snippets.isSnippetsUngroupedExpanded, + select: selectUngrouped, + isSelected: state.isUngroupedSelected, isDropTarget: isDropTarget ) + .contextMenu { + Button("New Snippet", systemImage: MenuSymbol.newSnippet) { + openSnippetEditor(snippetID: nil, packageID: nil) + } + Button("New Package", systemImage: MenuSymbol.newPackage) { + openPackageEditor(packageID: nil, parentID: nil) + } + } case .results: CardSectionHeader(title: section.title, count: section.items.count) } @@ -237,6 +248,7 @@ struct SnippetsPageView: ManagedCardPage { private func setSelection(_ ids: Set) { let valid = ids.intersection(Set(app.snippets.snippets.map(\.id))) state.selectedPackageID = nil + state.isUngroupedSelected = false let ordered = visibleSnippets.filter { valid.contains($0.id) } .map { VaultSelectionID.snippet($0.id) } window.setVaultSelection(ordered, for: .snippets) @@ -244,9 +256,16 @@ struct SnippetsPageView: ManagedCardPage { private func selectPackage(_ id: SnippetPackage.ID) { window.clearVaultSelection(for: .snippets) + state.isUngroupedSelected = false state.selectedPackageID = id } + private func selectUngrouped() { + window.clearVaultSelection(for: .snippets) + state.selectedPackageID = nil + state.isUngroupedSelected = true + } + private func saveSnippet(_ snippet: Snippet) { app.snippets.upsert(snippet) window.selectVaultItem(.snippet(snippet.id), on: .snippets)