native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #160 from onevcat/fix/sidebar-ui-improvements

fix: sidebar UI improvements

authored by

Wei Wang and committed by
GitHub
b473a3c1 6b05f50e

+72 -52
+3
supacode/Features/Repositories/Views/RepoHeaderRow.swift
··· 5 5 let name: String 6 6 let isRemoving: Bool 7 7 let tabCount: Int 8 + var nameTooltip: String? 8 9 var body: some View { 9 10 HStack { 10 11 Text(name) 11 12 .foregroundStyle(.secondary) 13 + .help(nameTooltip ?? "") 12 14 if isRemoving { 13 15 Text("Removing...") 14 16 .font(.caption) ··· 22 24 .padding(.horizontal, 5) 23 25 .padding(.vertical, 1) 24 26 .background(.quaternary, in: .capsule) 27 + .help("\(tabCount) active \(tabCount == 1 ? "tab" : "tabs")") 25 28 } 26 29 } 27 30 .background {
+8 -22
supacode/Features/Repositories/Views/RepositorySectionView.swift
··· 4 4 struct RepositorySectionView: View { 5 5 private static let debugHeaderLayers = false 6 6 let repository: Repository 7 - let showsTopSeparator: Bool 7 + let hasTopSpacing: Bool 8 8 let isDragActive: Bool 9 9 let hotkeyRows: [WorktreeRowModel] 10 10 let selectedWorktreeIDs: Set<Worktree.ID> ··· 19 19 let state = store.state 20 20 let isExpanded = expandedRepoIDs.contains(repository.id) 21 21 let isRemovingRepository = state.isRemovingRepository(repository) 22 - let isPlainFolderSelected = 23 - repository.kind == .plain 24 - && state.selectedRepositoryID == repository.id 25 22 let openRepoSettings = { 26 23 _ = store.send(.repositoryManagement(.openRepositorySettings(repository.id))) 27 24 } ··· 44 41 tabCount: Self.openTabCount( 45 42 for: repository, 46 43 terminalManager: terminalManager 47 - ) 44 + ), 45 + nameTooltip: repository.capabilities.supportsWorktrees 46 + ? (isExpanded ? "Collapse" : "Expand") 47 + : "Open terminal in folder" 48 48 ) 49 49 .frame(maxWidth: .infinity, alignment: .leading) 50 50 .background { ··· 158 158 } 159 159 } 160 160 } 161 - .frame(maxWidth: .infinity) 162 - .frame(height: headerCellHeight, alignment: .center) 163 - .padding(.top, showsTopSeparator ? 4 : 0) 161 + .frame(maxWidth: .infinity, minHeight: headerCellHeight, maxHeight: .infinity, alignment: .center) 162 + .padding(.top, hasTopSpacing ? 4 : 0) 163 + .padding(.bottom, hasTopSpacing && !repository.capabilities.supportsWorktrees ? 4 : 0) 164 164 .contentShape(.interaction, .rect) 165 165 .background { 166 166 if Self.debugHeaderLayers { ··· 172 172 } 173 173 } 174 174 } 175 - .overlay(alignment: .top) { 176 - if showsTopSeparator && !isPlainFolderSelected && Self.debugHeaderLayers { 177 - Rectangle() 178 - .fill(.blue) 179 - .frame(height: 1) 180 - .frame(maxWidth: .infinity) 181 - .accessibilityHidden(true) 182 - } 183 - } 184 175 .onHover { isHovering = $0 } 185 176 .contentShape(.rect) 186 - .help( 187 - repository.capabilities.supportsWorktrees 188 - ? (isExpanded ? "Collapse" : "Expand") 189 - : "Open folder terminal" 190 - ) 191 177 .contextMenu { 192 178 Button("Repo Settings") { 193 179 openRepoSettings()
+2 -2
supacode/Features/Repositories/Views/SidebarListView.swift
··· 93 93 ForEach(Array(repositories.enumerated()), id: \.element.id) { index, repository in 94 94 RepositorySectionView( 95 95 repository: repository, 96 - showsTopSeparator: index > 0, 96 + hasTopSpacing: index > 0, 97 97 isDragActive: isDragActive, 98 98 hotkeyRows: hotkeyRows, 99 99 selectedWorktreeIDs: selectedWorktreeIDs, ··· 143 143 } else if let repository = repositoriesByID[repositoryID] { 144 144 RepositorySectionView( 145 145 repository: repository, 146 - showsTopSeparator: index > 0, 146 + hasTopSpacing: index > 0, 147 147 isDragActive: isDragActive, 148 148 hotkeyRows: hotkeyRows, 149 149 selectedWorktreeIDs: selectedWorktreeIDs,
+30 -11
supacode/Features/Repositories/Views/WorktreeRow.swift
··· 20 20 let isSelected: Bool 21 21 let archiveAction: (() -> Void)? 22 22 let onDiffTap: (() -> Void)? 23 + let onStopRunScript: (() -> Void)? 23 24 @Environment(\.colorScheme) private var colorScheme 24 25 @Environment(\.resolvedKeybindings) private var resolvedKeybindings 25 26 ··· 73 74 .foregroundStyle(nameColor) 74 75 .lineLimit(1) 75 76 Spacer(minLength: 4) 76 - if isRunScriptRunning { 77 - Image(systemName: "play.fill") 78 - .font(.caption) 79 - .foregroundStyle(.green) 80 - .help("Run script active") 81 - .accessibilityLabel("Run script active") 82 - } 83 - if isHovered { 77 + if isHovered, pinAction != nil { 84 78 Button { 85 79 pinAction?() 86 80 } label: { ··· 91 85 } 92 86 .buttonStyle(.plain) 93 87 .help(isPinned ? "Unpin" : "Pin to top") 94 - .disabled(pinAction == nil) 88 + } 89 + if isHovered, archiveAction != nil { 95 90 Button { 96 91 archiveAction?() 97 92 } label: { ··· 101 96 } 102 97 .buttonStyle(.plain) 103 98 .help("Archive Worktree") 104 - .disabled(archiveAction == nil) 99 + } 100 + if isRunScriptRunning { 101 + RunScriptIndicator(onStop: onStopRunScript) 105 102 } 106 103 if hasChangeCounts, let displayAddedLines, let displayRemovedLines { 107 104 Button { ··· 150 147 } 151 148 } 152 149 150 + private struct RunScriptIndicator: View { 151 + let onStop: (() -> Void)? 152 + @State private var isHovering = false 153 + 154 + var body: some View { 155 + Button { 156 + onStop?() 157 + } label: { 158 + Image(systemName: isHovering ? "stop.fill" : "play.fill") 159 + .font(.caption) 160 + .foregroundStyle(isHovering ? .red : .green) 161 + .contentTransition(.symbolEffect(.replace)) 162 + .accessibilityLabel(isHovering ? "Stop run script" : "Run script active") 163 + } 164 + .buttonStyle(.plain) 165 + .help(isHovering ? "Stop Script" : "Run script active") 166 + .disabled(onStop == nil) 167 + .onHover { isHovering = $0 } 168 + } 169 + } 170 + 153 171 private struct WorktreeRowInfoView: View { 154 172 let worktreeName: String 155 173 let showsPullRequestTag: Bool ··· 274 292 pinAction: {}, 275 293 isSelected: isSelected, 276 294 archiveAction: {}, 277 - onDiffTap: addedLines != nil ? {} : nil 295 + onDiffTap: addedLines != nil ? {} : nil, 296 + onStopRunScript: isRunScriptRunning ? {} : nil 278 297 ) 279 298 .listRowInsets(EdgeInsets()) 280 299 .listRowSeparator(.hidden)
+29 -17
supacode/Features/Repositories/Views/WorktreeRowsView.swift
··· 127 127 resolvedKeybindings: resolvedKeybindings 128 128 ) 129 129 } 130 + let onStopRunScript: (() -> Void)? = 131 + terminalManager.isRunScriptRunning(for: row.id) 132 + ? { _ = terminalManager.stateIfExists(for: row.id)?.stopRunScript() } 133 + : nil 130 134 let config = WorktreeRowViewConfig( 131 135 displayName: displayName, 132 136 worktreeName: worktreeName(for: row), ··· 138 142 pinAction: pinAction, 139 143 archiveAction: archiveAction, 140 144 onDiffTap: onDiffTap, 145 + onStopRunScript: onStopRunScript, 141 146 moveDisabled: moveDisabled, 142 147 ) 143 148 let baseRow = worktreeRowView(row, config: config) ··· 192 197 let pinAction: (() -> Void)? 193 198 let archiveAction: (() -> Void)? 194 199 let onDiffTap: (() -> Void)? 200 + let onStopRunScript: (() -> Void)? 195 201 let moveDisabled: Bool 196 202 } 197 203 ··· 218 224 isSelected: isSelected, 219 225 archiveAction: config.archiveAction, 220 226 onDiffTap: config.onDiffTap, 227 + onStopRunScript: config.onStopRunScript, 221 228 ) 222 229 .tag(SidebarSelection.worktree(row.id)) 223 230 .typeSelectEquivalent("") ··· 241 248 repositoryID: $0.repositoryID 242 249 ) 243 250 } 244 - let deleteTargets = contextRows.map { 245 - RepositoriesFeature.DeleteWorktreeTarget( 246 - worktreeID: $0.id, 247 - repositoryID: $0.repositoryID 248 - ) 249 - } 251 + let deleteTargets = 252 + contextRows 253 + .filter { !$0.isMainWorktree } 254 + .map { 255 + RepositoriesFeature.DeleteWorktreeTarget( 256 + worktreeID: $0.id, 257 + repositoryID: $0.repositoryID 258 + ) 259 + } 250 260 let archiveTitle = 251 261 isBulkSelection 252 262 ? "Archive Selected Worktrees" ··· 272 282 NSPasteboard.general.clearContents() 273 283 NSPasteboard.general.setString(worktree.workingDirectory.path, forType: .string) 274 284 } 275 - Button(archiveTitle) { 276 - archiveWorktrees(archiveTargets) 285 + Button("Reveal in Finder") { 286 + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: worktree.workingDirectory.path) 277 287 } 278 - .help( 279 - archiveTargets.isEmpty 280 - ? "Main worktree can't be archived" 281 - : archiveTitle 282 - ) 283 - .disabled(archiveTargets.isEmpty) 284 - Button(deleteTitle, role: .destructive) { 285 - deleteWorktrees(deleteTargets) 288 + if !row.isMainWorktree || isBulkSelection { 289 + Button(archiveTitle) { 290 + archiveWorktrees(archiveTargets) 291 + } 292 + .help(archiveTitle) 293 + .disabled(archiveTargets.isEmpty) 294 + Button(deleteTitle, role: .destructive) { 295 + deleteWorktrees(deleteTargets) 296 + } 297 + .help(deleteTitle) 298 + .disabled(deleteTargets.isEmpty) 286 299 } 287 - .help(deleteTitle) 288 300 } 289 301 290 302 private func worktreeShortcutHint(for index: Int?) -> String? {