Refine Card
This commit is contained in:
1 parent
e267fa657f
commit
ca486ae4da
3 files changed
+55
-81
No files matched your search
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user