Fix sidebar layout loop in workspace list (#2328)
This commit is contained in:
parent
d95158e69d
commit
9e75355525
1 changed files with 214 additions and 60 deletions
|
|
@ -8492,6 +8492,132 @@ private struct SidebarResizerAccessibilityModifier: ViewModifier {
|
|||
}
|
||||
}
|
||||
|
||||
private struct SidebarTabItemSettingsSnapshot: Equatable {
|
||||
let sidebarShortcutHintXOffset: Double
|
||||
let sidebarShortcutHintYOffset: Double
|
||||
let alwaysShowShortcutHints: Bool
|
||||
let showsGitBranch: Bool
|
||||
let usesVerticalBranchLayout: Bool
|
||||
let showsGitBranchIcon: Bool
|
||||
let showsSSH: Bool
|
||||
let openPullRequestLinksInCmuxBrowser: Bool
|
||||
let openPortLinksInCmuxBrowser: Bool
|
||||
let showsNotificationMessage: Bool
|
||||
let activeTabIndicatorStyle: SidebarActiveTabIndicatorStyle
|
||||
let selectionColorHex: String?
|
||||
let notificationBadgeColorHex: String?
|
||||
let visibleAuxiliaryDetails: SidebarWorkspaceAuxiliaryDetailVisibility
|
||||
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
sidebarShortcutHintXOffset = Self.double(
|
||||
defaults: defaults,
|
||||
key: ShortcutHintDebugSettings.sidebarHintXKey,
|
||||
defaultValue: ShortcutHintDebugSettings.defaultSidebarHintX
|
||||
)
|
||||
sidebarShortcutHintYOffset = Self.double(
|
||||
defaults: defaults,
|
||||
key: ShortcutHintDebugSettings.sidebarHintYKey,
|
||||
defaultValue: ShortcutHintDebugSettings.defaultSidebarHintY
|
||||
)
|
||||
alwaysShowShortcutHints = Self.bool(
|
||||
defaults: defaults,
|
||||
key: ShortcutHintDebugSettings.alwaysShowHintsKey,
|
||||
defaultValue: ShortcutHintDebugSettings.defaultAlwaysShowHints
|
||||
)
|
||||
showsGitBranch = Self.bool(defaults: defaults, key: "sidebarShowGitBranch", defaultValue: true)
|
||||
usesVerticalBranchLayout = SidebarBranchLayoutSettings.usesVerticalLayout(defaults: defaults)
|
||||
showsGitBranchIcon = Self.bool(defaults: defaults, key: "sidebarShowGitBranchIcon", defaultValue: false)
|
||||
showsSSH = Self.bool(defaults: defaults, key: "sidebarShowSSH", defaultValue: true)
|
||||
openPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowser(
|
||||
defaults: defaults
|
||||
)
|
||||
openPortLinksInCmuxBrowser = BrowserLinkOpenSettings.openSidebarPortLinksInCmuxBrowser(
|
||||
defaults: defaults
|
||||
)
|
||||
|
||||
let hidesAllDetails = SidebarWorkspaceDetailSettings.hidesAllDetails(defaults: defaults)
|
||||
let showsNotificationMessageSetting = SidebarWorkspaceDetailSettings.showsNotificationMessage(
|
||||
defaults: defaults
|
||||
)
|
||||
showsNotificationMessage = SidebarWorkspaceDetailSettings.resolvedNotificationMessageVisibility(
|
||||
showNotificationMessage: showsNotificationMessageSetting,
|
||||
hideAllDetails: hidesAllDetails
|
||||
)
|
||||
|
||||
let showsMetadata = Self.bool(defaults: defaults, key: "sidebarShowStatusPills", defaultValue: true)
|
||||
let showsLog = Self.bool(defaults: defaults, key: "sidebarShowLog", defaultValue: true)
|
||||
let showsProgress = Self.bool(defaults: defaults, key: "sidebarShowProgress", defaultValue: true)
|
||||
let showsBranchDirectory = Self.bool(defaults: defaults, key: "sidebarShowBranchDirectory", defaultValue: true)
|
||||
let showsPullRequests = Self.bool(defaults: defaults, key: "sidebarShowPullRequest", defaultValue: true)
|
||||
let showsPorts = Self.bool(defaults: defaults, key: "sidebarShowPorts", defaultValue: true)
|
||||
visibleAuxiliaryDetails = SidebarWorkspaceAuxiliaryDetailVisibility.resolved(
|
||||
showMetadata: showsMetadata,
|
||||
showLog: showsLog,
|
||||
showProgress: showsProgress,
|
||||
showBranchDirectory: showsBranchDirectory,
|
||||
showPullRequests: showsPullRequests,
|
||||
showPorts: showsPorts,
|
||||
hideAllDetails: hidesAllDetails
|
||||
)
|
||||
|
||||
activeTabIndicatorStyle = SidebarActiveTabIndicatorSettings.current(defaults: defaults)
|
||||
selectionColorHex = defaults.string(forKey: "sidebarSelectionColorHex")
|
||||
notificationBadgeColorHex = defaults.string(forKey: "sidebarNotificationBadgeColorHex")
|
||||
}
|
||||
|
||||
private static func bool(
|
||||
defaults: UserDefaults,
|
||||
key: String,
|
||||
defaultValue: Bool
|
||||
) -> Bool {
|
||||
guard defaults.object(forKey: key) != nil else { return defaultValue }
|
||||
return defaults.bool(forKey: key)
|
||||
}
|
||||
|
||||
private static func double(
|
||||
defaults: UserDefaults,
|
||||
key: String,
|
||||
defaultValue: Double
|
||||
) -> Double {
|
||||
guard let value = defaults.object(forKey: key) as? NSNumber else { return defaultValue }
|
||||
return value.doubleValue
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class SidebarTabItemSettingsStore: ObservableObject {
|
||||
@Published private(set) var snapshot: SidebarTabItemSettingsSnapshot
|
||||
|
||||
private let defaults: UserDefaults
|
||||
private var defaultsObserver: NSObjectProtocol?
|
||||
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
self.snapshot = SidebarTabItemSettingsSnapshot(defaults: defaults)
|
||||
defaultsObserver = NotificationCenter.default.addObserver(
|
||||
forName: UserDefaults.didChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor [weak self] in
|
||||
self?.refreshSnapshot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let defaultsObserver {
|
||||
NotificationCenter.default.removeObserver(defaultsObserver)
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshSnapshot() {
|
||||
let nextSnapshot = SidebarTabItemSettingsSnapshot(defaults: defaults)
|
||||
guard nextSnapshot != snapshot else { return }
|
||||
snapshot = nextSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
struct VerticalTabsSidebar: View {
|
||||
@ObservedObject var updateViewModel: UpdateViewModel
|
||||
let onSendFeedback: () -> Void
|
||||
|
|
@ -8503,12 +8629,9 @@ struct VerticalTabsSidebar: View {
|
|||
@StateObject private var modifierKeyMonitor = SidebarShortcutHintModifierMonitor()
|
||||
@StateObject private var dragAutoScrollController = SidebarDragAutoScrollController()
|
||||
@StateObject private var dragFailsafeMonitor = SidebarDragFailsafeMonitor()
|
||||
@StateObject private var tabItemSettingsStore = SidebarTabItemSettingsStore()
|
||||
@State private var draggedTabId: UUID?
|
||||
@State private var dropIndicator: SidebarDropIndicator?
|
||||
@AppStorage(SidebarWorkspaceDetailSettings.hideAllDetailsKey)
|
||||
private var sidebarHideAllDetails = SidebarWorkspaceDetailSettings.defaultHideAllDetails
|
||||
@AppStorage(SidebarWorkspaceDetailSettings.showNotificationMessageKey)
|
||||
private var sidebarShowNotificationMessage = SidebarWorkspaceDetailSettings.defaultShowNotificationMessage
|
||||
@AppStorage(WorkspacePresentationModeSettings.modeKey)
|
||||
private var workspacePresentationMode = WorkspacePresentationModeSettings.defaultMode.rawValue
|
||||
@AppStorage(KeyboardShortcutSettings.Action.selectWorkspaceByNumber.defaultsKey)
|
||||
|
|
@ -8524,10 +8647,7 @@ struct VerticalTabsSidebar: View {
|
|||
}
|
||||
|
||||
private var showsSidebarNotificationMessage: Bool {
|
||||
SidebarWorkspaceDetailSettings.resolvedNotificationMessageVisibility(
|
||||
showNotificationMessage: sidebarShowNotificationMessage,
|
||||
hideAllDetails: sidebarHideAllDetails
|
||||
)
|
||||
tabItemSettingsStore.snapshot.showsNotificationMessage
|
||||
}
|
||||
|
||||
private var workspaceNumberShortcut: StoredShortcut {
|
||||
|
|
@ -8546,9 +8666,22 @@ struct VerticalTabsSidebar: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
let workspaceCount = tabManager.tabs.count
|
||||
let tabs = tabManager.tabs
|
||||
let workspaceCount = tabs.count
|
||||
let canCloseWorkspace = workspaceCount > 1
|
||||
let workspaceNumberShortcut = self.workspaceNumberShortcut
|
||||
let tabItemSettings = tabItemSettingsStore.snapshot
|
||||
let tabIndexById = Dictionary(uniqueKeysWithValues: tabs.enumerated().map {
|
||||
($0.element.id, $0.offset)
|
||||
})
|
||||
let orderedSelectedTabs = tabs.filter { selectedTabIds.contains($0.id) }
|
||||
let selectedContextTargetIds = orderedSelectedTabs.map(\.id)
|
||||
let selectedRemoteContextMenuTargets = orderedSelectedTabs.filter { $0.isRemoteWorkspace }
|
||||
let selectedRemoteContextMenuWorkspaceIds = selectedRemoteContextMenuTargets.map(\.id)
|
||||
let allSelectedRemoteContextMenuTargetsConnecting = !selectedRemoteContextMenuTargets.isEmpty &&
|
||||
selectedRemoteContextMenuTargets.allSatisfy { $0.remoteConnectionState == .connecting }
|
||||
let allSelectedRemoteContextMenuTargetsDisconnected = !selectedRemoteContextMenuTargets.isEmpty &&
|
||||
selectedRemoteContextMenuTargets.allSatisfy { $0.remoteConnectionState == .disconnected }
|
||||
|
||||
VStack(spacing: 0) {
|
||||
GeometryReader { proxy in
|
||||
|
|
@ -8559,14 +8692,21 @@ struct VerticalTabsSidebar: View {
|
|||
.frame(height: trafficLightPadding)
|
||||
|
||||
LazyVStack(spacing: tabRowSpacing) {
|
||||
ForEach(Array(tabManager.tabs.enumerated()), id: \.element.id) { index, tab in
|
||||
let selectedContextIds: Set<UUID> = selectedTabIds.contains(tab.id) ? selectedTabIds : [tab.id]
|
||||
let contextTargetIds = tabManager.tabs.compactMap { workspace in
|
||||
selectedContextIds.contains(workspace.id) ? workspace.id : nil
|
||||
}
|
||||
let remoteContextMenuTargets = tabManager.tabs.filter { workspace in
|
||||
contextTargetIds.contains(workspace.id) && workspace.isRemoteWorkspace
|
||||
}
|
||||
ForEach(tabs, id: \.id) { tab in
|
||||
let index = tabIndexById[tab.id] ?? 0
|
||||
let usesSelectedContextMenuTargets = selectedTabIds.contains(tab.id)
|
||||
let contextMenuWorkspaceIds = usesSelectedContextMenuTargets
|
||||
? selectedContextTargetIds
|
||||
: [tab.id]
|
||||
let remoteContextMenuWorkspaceIds = usesSelectedContextMenuTargets
|
||||
? selectedRemoteContextMenuWorkspaceIds
|
||||
: (tab.isRemoteWorkspace ? [tab.id] : [])
|
||||
let allRemoteContextMenuTargetsConnecting = usesSelectedContextMenuTargets
|
||||
? allSelectedRemoteContextMenuTargetsConnecting
|
||||
: (tab.isRemoteWorkspace && tab.remoteConnectionState == .connecting)
|
||||
let allRemoteContextMenuTargetsDisconnected = usesSelectedContextMenuTargets
|
||||
? allSelectedRemoteContextMenuTargetsDisconnected
|
||||
: (tab.isRemoteWorkspace && tab.remoteConnectionState == .disconnected)
|
||||
TabItemView(
|
||||
tabManager: tabManager,
|
||||
notificationStore: notificationStore,
|
||||
|
|
@ -8598,9 +8738,11 @@ struct VerticalTabsSidebar: View {
|
|||
dragAutoScrollController: dragAutoScrollController,
|
||||
draggedTabId: $draggedTabId,
|
||||
dropIndicator: $dropIndicator,
|
||||
remoteContextMenuWorkspaceIds: remoteContextMenuTargets.map(\.id),
|
||||
allRemoteContextMenuTargetsConnecting: !remoteContextMenuTargets.isEmpty && remoteContextMenuTargets.allSatisfy { $0.remoteConnectionState == .connecting },
|
||||
allRemoteContextMenuTargetsDisconnected: !remoteContextMenuTargets.isEmpty && remoteContextMenuTargets.allSatisfy { $0.remoteConnectionState == .disconnected }
|
||||
contextMenuWorkspaceIds: contextMenuWorkspaceIds,
|
||||
remoteContextMenuWorkspaceIds: remoteContextMenuWorkspaceIds,
|
||||
allRemoteContextMenuTargetsConnecting: allRemoteContextMenuTargetsConnecting,
|
||||
allRemoteContextMenuTargetsDisconnected: allRemoteContextMenuTargetsDisconnected,
|
||||
settings: tabItemSettings
|
||||
)
|
||||
.equatable()
|
||||
}
|
||||
|
|
@ -10998,9 +11140,11 @@ private struct TabItemView: View, Equatable {
|
|||
lhs.latestNotificationText == rhs.latestNotificationText &&
|
||||
lhs.rowSpacing == rhs.rowSpacing &&
|
||||
lhs.showsModifierShortcutHints == rhs.showsModifierShortcutHints &&
|
||||
lhs.contextMenuWorkspaceIds == rhs.contextMenuWorkspaceIds &&
|
||||
lhs.remoteContextMenuWorkspaceIds == rhs.remoteContextMenuWorkspaceIds &&
|
||||
lhs.allRemoteContextMenuTargetsConnecting == rhs.allRemoteContextMenuTargetsConnecting &&
|
||||
lhs.allRemoteContextMenuTargetsDisconnected == rhs.allRemoteContextMenuTargetsDisconnected
|
||||
lhs.allRemoteContextMenuTargetsDisconnected == rhs.allRemoteContextMenuTargetsDisconnected &&
|
||||
lhs.settings == rhs.settings
|
||||
}
|
||||
|
||||
// Use plain references instead of @EnvironmentObject to avoid subscribing
|
||||
|
|
@ -11026,35 +11170,14 @@ private struct TabItemView: View, Equatable {
|
|||
let dragAutoScrollController: SidebarDragAutoScrollController
|
||||
@Binding var draggedTabId: UUID?
|
||||
@Binding var dropIndicator: SidebarDropIndicator?
|
||||
let contextMenuWorkspaceIds: [UUID]
|
||||
let remoteContextMenuWorkspaceIds: [UUID]
|
||||
let allRemoteContextMenuTargetsConnecting: Bool
|
||||
let allRemoteContextMenuTargetsDisconnected: Bool
|
||||
let settings: SidebarTabItemSettingsSnapshot
|
||||
@State private var workspaceObservationGeneration: UInt64 = 0
|
||||
@State private var isHovering = false
|
||||
@State private var rowHeight: CGFloat = 1
|
||||
@AppStorage(ShortcutHintDebugSettings.sidebarHintXKey) private var sidebarShortcutHintXOffset = ShortcutHintDebugSettings.defaultSidebarHintX
|
||||
@AppStorage(ShortcutHintDebugSettings.sidebarHintYKey) private var sidebarShortcutHintYOffset = ShortcutHintDebugSettings.defaultSidebarHintY
|
||||
@AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints
|
||||
@AppStorage("sidebarShowGitBranch") private var sidebarShowGitBranch = true
|
||||
@AppStorage(SidebarBranchLayoutSettings.key) private var sidebarBranchVerticalLayout = SidebarBranchLayoutSettings.defaultVerticalLayout
|
||||
@AppStorage("sidebarShowBranchDirectory") private var sidebarShowBranchDirectory = true
|
||||
@AppStorage("sidebarShowGitBranchIcon") private var sidebarShowGitBranchIcon = false
|
||||
@AppStorage("sidebarShowPullRequest") private var sidebarShowPullRequest = true
|
||||
@AppStorage(BrowserLinkOpenSettings.openSidebarPullRequestLinksInCmuxBrowserKey)
|
||||
private var openSidebarPullRequestLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenSidebarPullRequestLinksInCmuxBrowser
|
||||
@AppStorage(BrowserLinkOpenSettings.openSidebarPortLinksInCmuxBrowserKey)
|
||||
private var openSidebarPortLinksInCmuxBrowser = BrowserLinkOpenSettings.defaultOpenSidebarPortLinksInCmuxBrowser
|
||||
@AppStorage("sidebarShowSSH") private var sidebarShowSSH = true
|
||||
@AppStorage("sidebarShowPorts") private var sidebarShowPorts = true
|
||||
@AppStorage("sidebarShowLog") private var sidebarShowLog = true
|
||||
@AppStorage("sidebarShowProgress") private var sidebarShowProgress = true
|
||||
@AppStorage("sidebarShowStatusPills") private var sidebarShowMetadata = true
|
||||
@AppStorage(SidebarWorkspaceDetailSettings.hideAllDetailsKey)
|
||||
private var sidebarHideAllDetails = SidebarWorkspaceDetailSettings.defaultHideAllDetails
|
||||
@AppStorage(SidebarActiveTabIndicatorSettings.styleKey)
|
||||
private var activeTabIndicatorStyleRaw = SidebarActiveTabIndicatorSettings.defaultStyle.rawValue
|
||||
@AppStorage("sidebarSelectionColorHex") private var sidebarSelectionColorHex: String?
|
||||
@AppStorage("sidebarNotificationBadgeColorHex") private var sidebarNotificationBadgeColorHex: String?
|
||||
|
||||
var isMultiSelected: Bool {
|
||||
selectedTabIds.contains(tab.id)
|
||||
|
|
@ -11064,8 +11187,52 @@ private struct TabItemView: View, Equatable {
|
|||
draggedTabId == tab.id
|
||||
}
|
||||
|
||||
private var sidebarShortcutHintXOffset: Double {
|
||||
settings.sidebarShortcutHintXOffset
|
||||
}
|
||||
|
||||
private var sidebarShortcutHintYOffset: Double {
|
||||
settings.sidebarShortcutHintYOffset
|
||||
}
|
||||
|
||||
private var alwaysShowShortcutHints: Bool {
|
||||
settings.alwaysShowShortcutHints
|
||||
}
|
||||
|
||||
private var sidebarShowGitBranch: Bool {
|
||||
settings.showsGitBranch
|
||||
}
|
||||
|
||||
private var sidebarBranchVerticalLayout: Bool {
|
||||
settings.usesVerticalBranchLayout
|
||||
}
|
||||
|
||||
private var sidebarShowGitBranchIcon: Bool {
|
||||
settings.showsGitBranchIcon
|
||||
}
|
||||
|
||||
private var sidebarShowSSH: Bool {
|
||||
settings.showsSSH
|
||||
}
|
||||
|
||||
private var activeTabIndicatorStyle: SidebarActiveTabIndicatorStyle {
|
||||
SidebarActiveTabIndicatorSettings.resolvedStyle(rawValue: activeTabIndicatorStyleRaw)
|
||||
settings.activeTabIndicatorStyle
|
||||
}
|
||||
|
||||
private var sidebarSelectionColorHex: String? {
|
||||
settings.selectionColorHex
|
||||
}
|
||||
|
||||
private var sidebarNotificationBadgeColorHex: String? {
|
||||
settings.notificationBadgeColorHex
|
||||
}
|
||||
|
||||
private var openSidebarPullRequestLinksInCmuxBrowser: Bool {
|
||||
settings.openPullRequestLinksInCmuxBrowser
|
||||
}
|
||||
|
||||
private var openSidebarPortLinksInCmuxBrowser: Bool {
|
||||
settings.openPortLinksInCmuxBrowser
|
||||
}
|
||||
|
||||
private var titleFontWeight: Font.Weight {
|
||||
|
|
@ -11232,15 +11399,7 @@ private struct TabItemView: View, Equatable {
|
|||
}
|
||||
|
||||
private var visibleAuxiliaryDetails: SidebarWorkspaceAuxiliaryDetailVisibility {
|
||||
SidebarWorkspaceAuxiliaryDetailVisibility.resolved(
|
||||
showMetadata: sidebarShowMetadata,
|
||||
showLog: sidebarShowLog,
|
||||
showProgress: sidebarShowProgress,
|
||||
showBranchDirectory: sidebarShowBranchDirectory,
|
||||
showPullRequests: sidebarShowPullRequest,
|
||||
showPorts: sidebarShowPorts,
|
||||
hideAllDetails: sidebarHideAllDetails
|
||||
)
|
||||
settings.visibleAuxiliaryDetails
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -11658,7 +11817,7 @@ private struct TabItemView: View, Equatable {
|
|||
|
||||
@ViewBuilder
|
||||
private var workspaceContextMenu: some View {
|
||||
let targetIds = contextTargetIds()
|
||||
let targetIds = contextMenuWorkspaceIds
|
||||
let isMulti = targetIds.count > 1
|
||||
let tabColorPalette = WorkspaceTabColorSettings.palette()
|
||||
let shouldPin = !tab.isPinned
|
||||
|
|
@ -11987,11 +12146,6 @@ private struct TabItemView: View, Equatable {
|
|||
setSelectionToTabs()
|
||||
}
|
||||
|
||||
private func contextTargetIds() -> [UUID] {
|
||||
let baseIds: Set<UUID> = selectedTabIds.contains(tab.id) ? selectedTabIds : [tab.id]
|
||||
return tabManager.tabs.compactMap { baseIds.contains($0.id) ? $0.id : nil }
|
||||
}
|
||||
|
||||
private func closeTabs(_ targetIds: [UUID], allowPinned: Bool) {
|
||||
tabManager.closeWorkspacesWithConfirmation(targetIds, allowPinned: allowPinned)
|
||||
syncSelectionAfterMutation()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue