diff options
Diffstat (limited to 'menubar/panel.swift')
| -rw-r--r-- | menubar/panel.swift | 63 |
1 files changed, 51 insertions, 12 deletions
diff --git a/menubar/panel.swift b/menubar/panel.swift index 85cc81e..5362d32 100644 --- a/menubar/panel.swift +++ b/menubar/panel.swift @@ -5,7 +5,8 @@ // - On or off (the global shortcut and the menu toggle it; off stays off), // and while on: shown always, or only when agents are waiting. // - Layout: compact (waiting agents in full, working and idle as one summary -// line); when shown always, a toggle switches to the full list. +// line, with a count of forgotten drafts that jumps to the first); when +// shown always, a toggle switches to the full list. // - Always dark: herdr's mocha palette with fixed colors on a dark tint, so // text reads the same over any desktop. @@ -85,6 +86,8 @@ struct PanelContent { let full: Bool /// Whether the panel offers the compact/full toggle (shown always). let canToggle: Bool + /// Agents with a forgotten draft not shown as rows, in list order. + let drafts: [Row] /// Sessions to restore, shown first. let offer: RestoreOffer? @@ -107,6 +110,8 @@ struct PanelContent { } working = all.filter { $0.status == .working }.count idle = all.filter { $0.status == .idle }.count + let shown = Set(rows.map(\.paneId)) + drafts = all.filter { $0.forgottenDraft && !shown.contains($0.paneId) } self.full = full self.canToggle = canToggle } @@ -137,6 +142,11 @@ struct PanelContent { var toggleLabel: String { full ? "Show less ▴" : "Show all ▾" } + /// "✎ 2 drafts", when some agents not shown have one. + var draftsLabel: String? { + drafts.isEmpty ? nil : "✎ \(drafts.count) \(drafts.count == 1 ? "draft" : "drafts")" + } + /// The offer row's title: what restore will do, or that it runs. var offerTitle: String? { guard let offer else { return nil } @@ -157,7 +167,8 @@ struct PanelContent { } if overflow > 0 { lines.append("+\(overflow) more waiting") } if showsSummary { - lines.append(summary.string + (canToggle ? "\t[\(toggleLabel)]" : "")) + lines.append(summary.string + (draftsLabel.map { "\t[\($0)]" } ?? "") + + (canToggle ? "\t[\(toggleLabel)]" : "")) } return lines.joined(separator: "\n") } @@ -369,7 +380,7 @@ private final class OfferView: ClickView { } } -/// "Show all ▾" / "Show less ▴". +/// Clickable caption text: "Show all ▾" / "Show less ▴", "Not now", "✎ 2 drafts". private final class ToggleView: ClickView { private let label: NSTextField @@ -550,17 +561,29 @@ final class PanelController { return box } - /// Summary on the left, the compact/full toggle on the right. + /// Summary on the left; on the right, the forgotten drafts (a click + /// jumps to the first) and the compact/full toggle. private func summaryLine(_ content: PanelContent) -> NSView { let box = caption(content.summary) - guard content.canToggle else { return box } - let toggle = ToggleView(text: content.toggleLabel, onClick: onToggleFull) - toggle.translatesAutoresizingMaskIntoConstraints = false - box.addSubview(toggle) - NSLayoutConstraint.activate([ - toggle.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -8), - toggle.centerYAnchor.constraint(equalTo: box.centerYAnchor, constant: 1), - ]) + var trailing = box.trailingAnchor + var gap: CGFloat = -8 + var constraints: [NSLayoutConstraint] = [] + let onClick = self.onClick + let draftsView = zip(content.draftsLabel.map { [$0] } ?? [], content.drafts.prefix(1)).map { label, first in + ToggleView(text: label, onClick: { onClick(first.paneId) }) + } + let toggleView = content.canToggle ? [ToggleView(text: content.toggleLabel, onClick: onToggleFull)] : [] + for view in (toggleView + draftsView) { + view.translatesAutoresizingMaskIntoConstraints = false + box.addSubview(view) + constraints += [ + view.trailingAnchor.constraint(equalTo: trailing, constant: gap), + view.centerYAnchor.constraint(equalTo: box.centerYAnchor, constant: 1), + ] + trailing = view.leadingAnchor + gap = -14 + } + NSLayoutConstraint.activate(constraints) return box } @@ -695,6 +718,22 @@ func panelSelftest() -> [String] { check(full.plainText.hasSuffix("[Show less ▴]") && !compact.plainText.contains("Show"), "toggle offered only when allowed, labelled by layout") + // Forgotten drafts: a draft the user is not looking at. + var drafty = [row("d", .done), row("w", .working), row("i1", .idle), row("i2", .idle), row("i3", .idle)] + drafty[0].draft = "x"; drafty[1].draft = "x"; drafty[3].draft = "x"; drafty[4].draft = "x" + drafty[4].attended = true + check(drafty.map(\.forgottenDraft) == [true, true, false, true, false], "draft: forgotten unless looked at") + check(drafty.sorted(by: listedBefore).map(\.paneId) == ["d", "w", "i2", "i1", "i3"], + "draft: an idle agent with one first among idle agents only") + check(rowTitlePrefix(drafty[3]) == "○ ✎ " && rowTitle(drafty[3]).string == "○ ✎ i2" + && rowTitle(drafty[2]).string == "○ i1", "draft: ✎ before the name") + let draftPanel = PanelContent(rows: drafty.sorted(by: listedBefore), canToggle: true) + check(draftPanel.drafts.map(\.paneId) == ["w", "i2"] && draftPanel.draftsLabel == "✎ 2 drafts", + "draft: compact counts those not shown, in list order") + check(draftPanel.plainText.hasSuffix("○ 3 idle\t[✎ 2 drafts]\t[Show all ▾]"), "draft: the count before the toggle") + check(PanelContent(rows: drafty, full: true).draftsLabel == nil, "draft: none counted when every row shows") + check(PanelContent(rows: [drafty[3]]).draftsLabel == "✎ 1 draft", "draft: one") + let offer = RestoreOffer(saved_at: 0, sessions: [ .init(name: "a", windows: 2, panes: 3, agents: 2), .init(name: "b", windows: 1, panes: 1, agents: 0), ], agents: 2, interrupted: 1) |
