Refine Card

This commit is contained in:
Kaede Akino committed 2026-09-20 21:52:01 +08:00
1 parent e267fa657f
commit ca486ae4da
3 files changed
+55 -81

No files matched your search

+6 -26
View File
@@ -18,8 +18,6 @@ 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<String> = []
@@ -126,13 +124,12 @@ struct HostsPageView: ManagedCardPage {
) -> some View {
if case .group(let groupID) = section.id, let group = app.hostLibrary.group(groupID) {
let isExpanded = app.hostLibrary.isHostGroupExpanded(group.id)
let toggle = { app.hostLibrary.setExpanded(!isExpanded, for: group.id) }
CardSectionHeader(
title: section.title,
count: section.items.count,
toggle: { app.hostLibrary.setExpanded(!isExpanded, for: group.id) },
toggle: toggle,
isCollapsed: !isExpanded,
select: { selectGroup(group.id) },
isSelected: state.selectedGroupID == group.id,
isDropTarget: isDropTarget
)
.contextMenu {
@@ -147,15 +144,13 @@ struct HostsPageView: ManagedCardPage {
DestructiveMenuButton("Delete Group") { requestDelete(group) }
}
} else if case .ungrouped = section.id {
let isExpanded = section.isExpanded ?? true
let toggle = { app.hostLibrary.setUngroupedExpanded(!isExpanded) }
CardSectionHeader(
title: section.title,
count: section.items.count,
toggle: section.isExpanded.map { expanded in
{ app.hostLibrary.setUngroupedExpanded(!expanded) }
},
isCollapsed: !(section.isExpanded ?? true),
select: selectUngrouped,
isSelected: state.isUngroupedSelected,
toggle: toggle,
isCollapsed: !isExpanded,
isDropTarget: isDropTarget
)
.contextMenu {
@@ -269,7 +264,6 @@ struct HostsPageView: ManagedCardPage {
state.confirmation = nil
pruneHostSelection()
if state.selectedGroupID == group.id { state.selectedGroupID = nil }
window.pruneVaultSelections(in: app.hostLibrary)
}
@@ -295,24 +289,10 @@ struct HostsPageView: ManagedCardPage {
private func setHostSelection(_ ids: Set<Host.ID>) {
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)
}
+8 -39
View File
@@ -35,8 +35,6 @@ final class SnippetsPageState {
}
var query = ""
var selectedPackageID: SnippetPackage.ID?
var isUngroupedSelected = false
var confirmation: ConfirmationRoute?
}
@@ -109,16 +107,13 @@ struct SnippetsPageView: ManagedCardPage {
case .package(let id):
if let package = app.snippets.snippetPackage(id) {
let isExpanded = app.snippets.isSnippetPackageExpanded(id)
let toggle = {
app.snippets.setSnippetPackageExpanded(!isExpanded, for: package.id)
}
CardSectionHeader(
title: section.title, count: section.items.count,
toggle: {
app.snippets.setSnippetPackageExpanded(
!isExpanded, for: package.id
)
},
toggle: toggle,
isCollapsed: !isExpanded,
select: { selectPackage(package.id) },
isSelected: state.selectedPackageID == package.id,
isDropTarget: isDropTarget
)
.contextMenu {
@@ -138,16 +133,12 @@ struct SnippetsPageView: ManagedCardPage {
}
}
case .ungrouped:
let isExpanded = app.snippets.isSnippetsUngroupedExpanded
let toggle = { app.snippets.setSnippetsUngroupedExpanded(!isExpanded) }
CardSectionHeader(
title: section.title, count: section.items.count,
toggle: {
app.snippets.setSnippetsUngroupedExpanded(
!app.snippets.isSnippetsUngroupedExpanded
)
},
isCollapsed: !app.snippets.isSnippetsUngroupedExpanded,
select: selectUngrouped,
isSelected: state.isUngroupedSelected,
toggle: toggle,
isCollapsed: !isExpanded,
isDropTarget: isDropTarget
)
.contextMenu {
@@ -247,25 +238,11 @@ struct SnippetsPageView: ManagedCardPage {
private func setSelection(_ ids: Set<Snippet.ID>) {
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)
}
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)
@@ -276,16 +253,8 @@ struct SnippetsPageView: ManagedCardPage {
}
private func deletePackage(_ package: SnippetPackage, keepSnippets: Bool) {
let deletedPackageIDs = Set(
[package.id] + app.snippets.descendantSnippetPackages(of: package.id).map(\.id)
)
app.snippets.delete(snippetPackageID: package.id, keepSnippets: keepSnippets)
state.confirmation = nil
if let selectedPackageID = state.selectedPackageID,
deletedPackageIDs.contains(selectedPackageID)
{
state.selectedPackageID = nil
}
window.pruneVaultSelections(in: app.snippets)
}
+41 -16
View File
@@ -386,17 +386,46 @@ struct CardSectionHeader: View {
@State private var isHovering = false
var body: some View {
Group {
if select == nil, let toggle {
Button(action: toggle) { paddedLabel }
.buttonStyle(.plain)
.accessibilityLabel(Text(title))
.accessibilityHint(Text(isCollapsed ? "Expand" : "Collapse"))
} else {
paddedLabel.onTapGesture { select?() }
}
}
// Same fill as `SidebarRow`, and fixed for the same reason: derived from
// the text color rather than the accent, so it does not change with the
// accent or with which view happens to hold focus.
.background(fill, in: .rect(cornerRadius: Metrics.Radius.medium))
.contentShape(.rect(cornerRadius: Metrics.Radius.medium))
.cardInteractiveFrame()
.onHover { isHovering = $0 }
.accessibilityElement(children: .combine)
.accessibilityAddTraits(isSelected ? [.isSelected, .isButton] : [])
}
private var label: some View {
HStack(spacing: Metrics.Space.s) {
if let toggle {
Button(action: toggle) {
if select == nil {
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
.rotationEffect(.degrees(isCollapsed ? 0 : 90))
.contentShape(.rect)
} else {
Button(action: toggle) {
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
.rotationEffect(.degrees(isCollapsed ? 0 : 90))
.contentShape(.rect)
}
.buttonStyle(.plain)
.accessibilityLabel(Text(isCollapsed ? "Expand" : "Collapse"))
}
.buttonStyle(.plain)
.accessibilityLabel(Text(isCollapsed ? "Expand" : "Collapse"))
}
Text(title)
@@ -411,20 +440,16 @@ struct CardSectionHeader: View {
.background(.quaternary.opacity(0.6), in: .capsule)
}
}
.padding(.horizontal, select == nil ? 0 : Metrics.Space.s)
.padding(.vertical, select == nil ? 0 : Metrics.Space.xs)
// Same fill as `SidebarRow`, and fixed for the same reason: derived from
// the text color rather than the accent, so it does not change with the
// accent or with which view happens to hold focus.
.background(fill, in: .rect(cornerRadius: Metrics.Radius.medium))
.contentShape(.rect(cornerRadius: Metrics.Radius.medium))
.onTapGesture { select?() }
.cardInteractiveFrame()
.onHover { isHovering = $0 }
.accessibilityElement(children: .combine)
.accessibilityAddTraits(isSelected ? [.isSelected, .isButton] : [])
}
private var paddedLabel: some View {
label
.padding(.horizontal, isInteractive ? Metrics.Space.s : 0)
.padding(.vertical, isInteractive ? Metrics.Space.xs : 0)
}
private var isInteractive: Bool { select != nil || toggle != nil }
private var fill: Color {
if isDropTarget {
Color.accentColor.opacity(0.16)