Files
openagent/ios_dashboard/OpenAgentDashboard/Views/Control/NewMissionSheet.swift
Thomas Marchand 3d0b4d19b7 Th0rgal/update branding (#32)
* feat: chroots

* wip

* Update workspace templates and Playwright tests

* Fix thinking panel close button not working during active thinking

The auto-show useEffect was including showThinkingPanel in its dependency
array, causing the panel to immediately reopen when closed since the state
change would trigger the effect while hasActiveThinking was still true.

Changed to use a ref to track previous state and only auto-show on
transition from inactive to active thinking.

* wip

* wip

* wip

* Cleanup web search tool and remove hardcoded OAuth credentials

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* Ralph iteration 3: work in progress

* Ralph iteration 4: work in progress

* Ralph iteration 5: work in progress

* Ralph iteration 6: work in progress

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* Ralph iteration 3: work in progress

* Ralph iteration 4: work in progress

* Ralph iteration 5: work in progress

* Ralph iteration 6: work in progress

* Ralph iteration 7: work in progress

* Ralph iteration 1: work in progress

* Ralph iteration 2: work in progress

* improve readme

* fix: remove unused file

* feat: hero screenshot

* Update README with cleaner vision and hero screenshot

Simplified the vision section with "what if" framing, removed
architecture diagram, added hero screenshot showing mission view.
2026-01-12 14:45:05 -08:00

199 lines
7.0 KiB
Swift

//
// NewMissionSheet.swift
// OpenAgentDashboard
//
// Sheet for creating a new mission with workspace selection
//
import SwiftUI
struct NewMissionSheet: View {
let workspaces: [Workspace]
@Binding var selectedWorkspaceId: String?
let onCreate: (String?) -> Void
let onCancel: () -> Void
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Header
VStack(spacing: 8) {
Image(systemName: "plus.circle.fill")
.font(.system(size: 48))
.foregroundStyle(Theme.accent)
Text("New Mission")
.font(.title2.weight(.semibold))
.foregroundStyle(Theme.textPrimary)
Text("Select a workspace for execution")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary)
}
.padding(.top, 24)
.padding(.bottom, 32)
// Workspace list
ScrollView {
VStack(spacing: 12) {
if workspaces.isEmpty {
VStack(spacing: 8) {
Image(systemName: "server.rack")
.font(.system(size: 32))
.foregroundStyle(Theme.textMuted)
Text("No workspaces available")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary)
}
.padding(.vertical, 32)
} else {
ForEach(workspaces) { workspace in
WorkspaceRow(
workspace: workspace,
isSelected: selectedWorkspaceId == workspace.id,
onSelect: {
selectedWorkspaceId = workspace.id
HapticService.selectionChanged()
}
)
}
}
}
.padding(.horizontal, 16)
}
Spacer()
// Action buttons
VStack(spacing: 12) {
Button {
onCreate(selectedWorkspaceId)
} label: {
HStack {
Image(systemName: "play.fill")
Text("Start Mission")
}
.font(.body.weight(.semibold))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Theme.accent)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.disabled(workspaces.isEmpty)
Button {
onCancel()
} label: {
Text("Cancel")
.font(.body)
.foregroundStyle(Theme.textSecondary)
}
}
.padding(.horizontal, 16)
.padding(.bottom, 24)
}
.background(Theme.backgroundSecondary)
}
}
}
struct WorkspaceRow: View {
let workspace: Workspace
let isSelected: Bool
let onSelect: () -> Void
var body: some View {
Button(action: onSelect) {
HStack(spacing: 12) {
// Icon
ZStack {
Circle()
.fill(workspace.workspaceType == .host ? Theme.success.opacity(0.15) : Theme.accent.opacity(0.15))
.frame(width: 40, height: 40)
Image(systemName: workspace.workspaceType.icon)
.font(.system(size: 16, weight: .medium))
.foregroundStyle(workspace.workspaceType == .host ? Theme.success : Theme.accent)
}
// Info
VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 6) {
Text(workspace.name)
.font(.body.weight(.medium))
.foregroundStyle(Theme.textPrimary)
if workspace.isDefault {
Text("Default")
.font(.caption2.weight(.medium))
.foregroundStyle(Theme.textSecondary)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Theme.backgroundTertiary)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
}
Text(workspace.shortDescription)
.font(.caption)
.foregroundStyle(Theme.textSecondary)
}
Spacer()
// Selection indicator
ZStack {
Circle()
.stroke(isSelected ? Theme.accent : Theme.borderSubtle, lineWidth: 2)
.frame(width: 22, height: 22)
if isSelected {
Circle()
.fill(Theme.accent)
.frame(width: 14, height: 14)
}
}
}
.padding(12)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isSelected ? Theme.accent.opacity(0.08) : Theme.backgroundTertiary)
)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(isSelected ? Theme.accent.opacity(0.3) : Color.clear, lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
#Preview {
NewMissionSheet(
workspaces: [
Workspace(
id: "00000000-0000-0000-0000-000000000000",
name: "host",
workspaceType: .host,
path: "/root",
status: .ready,
errorMessage: nil,
createdAt: "2025-01-05T12:00:00Z"
),
Workspace(
id: "1",
name: "project-a",
workspaceType: .chroot,
path: "/var/lib/openagent/containers/project-a",
status: .ready,
errorMessage: nil,
createdAt: "2025-01-05T12:00:00Z"
)
],
selectedWorkspaceId: .constant("00000000-0000-0000-0000-000000000000"),
onCreate: { _ in },
onCancel: {}
)
}