diff --git a/supacode/Features/Repositories/Views/AddToProwlView.swift b/supacode/Features/Repositories/Views/AddToProwlView.swift new file mode 100644 index 00000000..a09b5a60 --- /dev/null +++ b/supacode/Features/Repositories/Views/AddToProwlView.swift @@ -0,0 +1,168 @@ +import AppKit +import SwiftUI + +struct AddToProwlView: View { + let onBrowse: () -> Void + let onWorkspace: () -> Void + let onDrop: ([URL]) -> Void + @Environment(\.dismiss) private var dismiss + @State private var isDragTargeted = false + @State private var isWorkspaceHovered = false + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 11) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 30, height: 30) + Text("Add to Prowl") + .font(.system(size: 16, weight: .semibold)) + } + .padding(.bottom, 6) + + Text("Bring a project in for an agent to work on.") + .font(.system(size: 12.5)) + .foregroundStyle(.secondary) + .padding(.leading, 41) + .padding(.bottom, 18) + + dropZone + + orDivider + .padding(.top, 16) + .padding(.bottom, 12) + .padding(.horizontal, 2) + + workspaceRow + + HStack { + Spacer() + Button("Cancel") { + dismiss() + } + } + .padding(.top, 16) + } + .padding(EdgeInsets(top: 26, leading: 26, bottom: 20, trailing: 26)) + .frame(width: 400) + } + + private var dropZone: some View { + Button { + dismiss() + onBrowse() + } label: { + VStack(spacing: 2) { + Image(systemName: "folder.badge.plus") + .font(.system(size: 28)) + .symbolRenderingMode(.hierarchical) + .foregroundStyle(Color.accentColor) + .frame(width: 52, height: 52) + .background( + Color.accentColor.opacity(isDragTargeted ? 0.2 : 0.1), + in: .rect(cornerRadius: 14) + ) + .scaleEffect(isDragTargeted ? 1.06 : 1) + + Text(isDragTargeted ? "Release to add" : "Drag a repo here") + .font(.system(size: 15, weight: .semibold)) + .padding(.top, 10) + + Text("a Git repository or any folder — opens one project root") + .font(.system(size: 12)) + .foregroundStyle(.secondary) + + Text("Browse…") + .font(.system(size: 11, weight: .semibold)) + .foregroundStyle(.white) + .padding(.vertical, 5) + .padding(.horizontal, 13) + .background(Color.accentColor, in: .rect(cornerRadius: 7)) + .padding(.top, 14) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 26) + .padding(.horizontal, 20) + .background(.quaternary.opacity(0.3), in: .rect(cornerRadius: 15)) + .overlay { + RoundedRectangle(cornerRadius: 15) + .strokeBorder( + isDragTargeted ? Color.accentColor : Color.secondary.opacity(0.3), + style: StrokeStyle(lineWidth: 1.5, dash: [6, 4]) + ) + } + .contentShape(.rect(cornerRadius: 15)) + } + .buttonStyle(.plain) + .dropDestination(for: URL.self) { urls, _ in + let fileURLs = urls.filter(\.isFileURL) + guard !fileURLs.isEmpty else { return false } + dismiss() + onDrop(fileURLs) + return true + } isTargeted: { targeted in + withAnimation(.easeOut(duration: 0.18)) { + isDragTargeted = targeted + } + } + } + + private var orDivider: some View { + HStack(spacing: 12) { + Rectangle().fill(.separator).frame(height: 1) + Text("OR") + .font(.system(size: 10.5, weight: .semibold)) + .foregroundStyle(.tertiary) + Rectangle().fill(.separator).frame(height: 1) + } + } + + private var workspaceRow: some View { + Button { + dismiss() + onWorkspace() + } label: { + HStack(spacing: 13) { + Image(systemName: "rectangle.stack") + .font(.system(size: 18)) + .foregroundStyle(.secondary) + .frame(width: 38, height: 38) + .background(.quaternary, in: .rect(cornerRadius: 10)) + + VStack(alignment: .leading, spacing: 1) { + Text("Add Workspace") + .font(.system(size: 13.5, weight: .semibold)) + Text("A shared task folder spanning multiple repos for one agent.") + .font(.system(size: 11.5)) + .foregroundStyle(.secondary) + .lineLimit(2) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(.tertiary) + .offset(x: isWorkspaceHovered ? 2 : 0) + } + .padding(.vertical, 13) + .padding(.horizontal, 14) + .background( + .quaternary.opacity(isWorkspaceHovered ? 0.7 : 0.4), + in: .rect(cornerRadius: 13) + ) + .overlay { + RoundedRectangle(cornerRadius: 13) + .strokeBorder(.separator, lineWidth: 0.5) + } + .contentShape(.rect(cornerRadius: 13)) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(.easeOut(duration: 0.15)) { + isWorkspaceHovered = hovering + } + } + } +} diff --git a/supacode/Features/Repositories/Views/EmptyStateView.swift b/supacode/Features/Repositories/Views/EmptyStateView.swift index 1208e8cd..7c1ffc68 100644 --- a/supacode/Features/Repositories/Views/EmptyStateView.swift +++ b/supacode/Features/Repositories/Views/EmptyStateView.swift @@ -17,6 +17,19 @@ struct EmptyStateView: View { Button("Add...") { isAddChoicePresented = true } + .popover(isPresented: $isAddChoicePresented) { + AddToProwlView( + onBrowse: { + store.send(.setOpenPanelPresented(true)) + }, + onWorkspace: { + store.send(.workspaceCreation(.promptRequested)) + }, + onDrop: { urls in + store.send(.repositoryManagement(.openRepositories(urls))) + } + ) + } .modifier( KeyboardShortcutModifier( shortcut: resolvedKeybindings.keyboardShortcut(for: AppShortcuts.CommandID.openRepository) @@ -29,25 +42,6 @@ struct EmptyStateView: View { in: resolvedKeybindings )) } - .confirmationDialog( - "Add to Prowl", - isPresented: $isAddChoicePresented, - titleVisibility: .visible - ) { - Button("Add Local Repository/Folder") { - store.send(.setOpenPanelPresented(true)) - } - Button("Add Workspace") { - store.send(.workspaceCreation(.promptRequested)) - } - Button("Cancel", role: .cancel) {} - } message: { - Text( - "A local repository or folder opens one project root. " - + "A workspace creates one shared task folder " - + "containing multiple repositories for one agent to work across." - ) - } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(nsColor: .windowBackgroundColor)) } diff --git a/supacode/Features/Repositories/Views/SidebarListView.swift b/supacode/Features/Repositories/Views/SidebarListView.swift index 30c69c34..6f4a8cc5 100644 --- a/supacode/Features/Repositories/Views/SidebarListView.swift +++ b/supacode/Features/Repositories/Views/SidebarListView.swift @@ -203,27 +203,21 @@ struct SidebarListView: View { Label("Add...", systemImage: "folder.badge.plus") } .help("Add Repository or Workspace") + .popover(isPresented: $isAddChoicePresented) { + AddToProwlView( + onBrowse: { + store.send(.setOpenPanelPresented(true)) + }, + onWorkspace: { + store.send(.workspaceCreation(.promptRequested)) + }, + onDrop: { urls in + store.send(.repositoryManagement(.openRepositories(urls))) + } + ) + } } } - .confirmationDialog( - "Add to Prowl", - isPresented: $isAddChoicePresented, - titleVisibility: .visible - ) { - Button("Add Local Repository/Folder") { - store.send(.setOpenPanelPresented(true)) - } - Button("Add Workspace") { - store.send(.workspaceCreation(.promptRequested)) - } - Button("Cancel", role: .cancel) {} - } message: { - Text( - "A local repository or folder opens one project root. " - + "A workspace creates one shared task folder " - + "containing multiple repositories for one agent to work across." - ) - } } // ScrollViewReader }