WIP: show source initials in card badge
This commit is contained in:
@@ -63,6 +63,7 @@ Use this checklist before a release or after changes to panel, pasteboard, setti
|
|||||||
30. Confirm the Pinned empty state points to the Pin action instead of a plain-key shortcut.
|
30. Confirm the Pinned empty state points to the Pin action instead of a plain-key shortcut.
|
||||||
31. Confirm each card's source or type badge reads as an attached header-corner tile instead of a small floating icon.
|
31. Confirm each card's source or type badge reads as an attached header-corner tile instead of a small floating icon.
|
||||||
32. Confirm built-in collection chips use recognizable glyphs, while custom collection chips keep color-dot swatches.
|
32. Confirm built-in collection chips use recognizable glyphs, while custom collection chips keep color-dot swatches.
|
||||||
|
33. Confirm cards from known apps show app identity in the header tile, falling back to source initials when an app icon is unavailable.
|
||||||
|
|
||||||
## Copy And Paste
|
## Copy And Paste
|
||||||
|
|
||||||
|
|||||||
@@ -1375,6 +1375,10 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
|||||||
cardViews.map(\.debugHeaderBadgeSymbol)
|
cardViews.map(\.debugHeaderBadgeSymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var debugCardHeaderBadgeTexts: [String] {
|
||||||
|
cardViews.map(\.debugHeaderBadgeText)
|
||||||
|
}
|
||||||
|
|
||||||
var debugFirstCardHeaderTitle: String {
|
var debugFirstCardHeaderTitle: String {
|
||||||
cardViews.first?.debugHeaderTitle ?? ""
|
cardViews.first?.debugHeaderTitle ?? ""
|
||||||
}
|
}
|
||||||
@@ -2724,6 +2728,7 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
|||||||
private(set) var debugPreviewSummary = ""
|
private(set) var debugPreviewSummary = ""
|
||||||
private(set) var debugPreviewStyle = ""
|
private(set) var debugPreviewStyle = ""
|
||||||
private(set) var debugHeaderBadgeSymbol = ""
|
private(set) var debugHeaderBadgeSymbol = ""
|
||||||
|
private(set) var debugHeaderBadgeText = ""
|
||||||
private(set) var debugHeaderTitle = ""
|
private(set) var debugHeaderTitle = ""
|
||||||
private(set) var debugHeaderSubtitle = ""
|
private(set) var debugHeaderSubtitle = ""
|
||||||
private(set) var debugHeaderColorHex = ""
|
private(set) var debugHeaderColorHex = ""
|
||||||
@@ -2941,6 +2946,16 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func sourceMonogram(from value: String?) -> String? {
|
||||||
|
guard let text = presentSourceText(value) else { return nil }
|
||||||
|
let words = text
|
||||||
|
.components(separatedBy: CharacterSet.alphanumerics.inverted)
|
||||||
|
.map(\.clipboardTrimmed)
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
let initials = words.prefix(2).compactMap(\.first).map { String($0).uppercased() }.joined()
|
||||||
|
return initials.isEmpty ? nil : initials
|
||||||
|
}
|
||||||
|
|
||||||
private func availableCollectionNames() -> [String] {
|
private func availableCollectionNames() -> [String] {
|
||||||
var names: [String] = []
|
var names: [String] = []
|
||||||
var seen = Set<String>()
|
var seen = Set<String>()
|
||||||
@@ -4042,6 +4057,9 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
|||||||
badge.translatesAutoresizingMaskIntoConstraints = false
|
badge.translatesAutoresizingMaskIntoConstraints = false
|
||||||
if let bundleId = item.sourceAppBundleId,
|
if let bundleId = item.sourceAppBundleId,
|
||||||
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) {
|
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) {
|
||||||
|
#if DEBUG
|
||||||
|
debugHeaderBadgeText = ""
|
||||||
|
#endif
|
||||||
let icon = NSImageView(image: NSWorkspace.shared.icon(forFile: appURL.path))
|
let icon = NSImageView(image: NSWorkspace.shared.icon(forFile: appURL.path))
|
||||||
icon.imageScaling = .scaleProportionallyUpOrDown
|
icon.imageScaling = .scaleProportionallyUpOrDown
|
||||||
icon.translatesAutoresizingMaskIntoConstraints = false
|
icon.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@@ -4052,7 +4070,28 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
|||||||
icon.topAnchor.constraint(equalTo: badge.topAnchor, constant: headerBadgeIconInset),
|
icon.topAnchor.constraint(equalTo: badge.topAnchor, constant: headerBadgeIconInset),
|
||||||
icon.bottomAnchor.constraint(equalTo: badge.bottomAnchor, constant: -headerBadgeIconInset)
|
icon.bottomAnchor.constraint(equalTo: badge.bottomAnchor, constant: -headerBadgeIconInset)
|
||||||
])
|
])
|
||||||
|
} else if let monogram = Self.sourceMonogram(from: itemSourceAppName) {
|
||||||
|
#if DEBUG
|
||||||
|
debugHeaderBadgeText = monogram
|
||||||
|
#endif
|
||||||
|
let label = NSTextField(labelWithString: monogram)
|
||||||
|
label.font = .systemFont(ofSize: layout.isCompact ? 15 : 17, weight: .heavy)
|
||||||
|
label.textColor = accentColor(for: item.kind)
|
||||||
|
label.alignment = .center
|
||||||
|
label.lineBreakMode = .byClipping
|
||||||
|
label.maximumNumberOfLines = 1
|
||||||
|
label.setAccessibilityLabel(itemSourceAppName ?? monogram)
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
badge.addSubview(label)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
label.leadingAnchor.constraint(equalTo: badge.leadingAnchor, constant: headerBadgeIconInset),
|
||||||
|
label.trailingAnchor.constraint(equalTo: badge.trailingAnchor, constant: -headerBadgeIconInset),
|
||||||
|
label.centerYAnchor.constraint(equalTo: badge.centerYAnchor)
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
|
#if DEBUG
|
||||||
|
debugHeaderBadgeText = ""
|
||||||
|
#endif
|
||||||
let image = NSImage(systemSymbolName: headerBadgeSymbol(for: item.kind), accessibilityDescription: kindLabel(for: item.kind))
|
let image = NSImage(systemSymbolName: headerBadgeSymbol(for: item.kind), accessibilityDescription: kindLabel(for: item.kind))
|
||||||
?? NSImage(systemSymbolName: "doc.on.clipboard", accessibilityDescription: kindLabel(for: item.kind))
|
?? NSImage(systemSymbolName: "doc.on.clipboard", accessibilityDescription: kindLabel(for: item.kind))
|
||||||
?? NSImage()
|
?? NSImage()
|
||||||
|
|||||||
@@ -632,10 +632,24 @@ final class ClipboardPanelViewTests: XCTestCase {
|
|||||||
|
|
||||||
func testCardHeaderUsesKindSymbolBadgeWhenSourceIconIsUnavailable() {
|
func testCardHeaderUsesKindSymbolBadgeWhenSourceIconIsUnavailable() {
|
||||||
let fixture = makePanelFixture()
|
let fixture = makePanelFixture()
|
||||||
fixture.store.upsert(makeItem(kind: .url, text: "https://example.com", store: fixture.store))
|
var item = makeItem(kind: .url, text: "https://example.com", store: fixture.store)
|
||||||
|
item.sourceApp = nil
|
||||||
|
fixture.store.upsert(item)
|
||||||
drainMainQueue()
|
drainMainQueue()
|
||||||
|
|
||||||
XCTAssertEqual(fixture.view.debugCardHeaderBadgeSymbols, ["link"])
|
XCTAssertEqual(fixture.view.debugCardHeaderBadgeSymbols, ["link"])
|
||||||
|
XCTAssertEqual(fixture.view.debugCardHeaderBadgeTexts, [""])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCardHeaderUsesSourceMonogramWhenAppIconIsUnavailable() {
|
||||||
|
let fixture = makePanelFixture()
|
||||||
|
var item = makeTextItem("Copied from a named app", store: fixture.store)
|
||||||
|
item.sourceApp = "Arc Browser"
|
||||||
|
|
||||||
|
fixture.store.upsert(item)
|
||||||
|
drainMainQueue()
|
||||||
|
|
||||||
|
XCTAssertEqual(fixture.view.debugCardHeaderBadgeTexts, ["AB"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCollectionRailShowsLiveCounts() {
|
func testCollectionRailShowsLiveCounts() {
|
||||||
|
|||||||
Reference in New Issue
Block a user