Stage 1 targen: typed TargenConfig + argv builder (-v -d 2/4, -f, -e, -B, optional advanced flags, CMYK-only -l, never -u), ArgyllRunner over ProcessManager, Stage1View with disk-gated Generate. Issue 8: Open Existing resume — .ti1 → Stage 2, .ti2 → Stage 3 shell with persistent "Resumed from .ti2" banner, sibling-.ti1 gate. Stage 2 printtarg: PrinttargConfig/args (-v -u, -i, -p, -R 1 default, -r raster, -t/-T DPI), Stage2View with instrument/page/DPI/label controls, CM warning, stubbed print panel. Issue 10: pretty-manifest JSON parsing, host-side TIFF→PNG gallery previews, per-page stubbed print, failure stays on stage. Issue 11: typed ProfilingPreset schema with legacy CustomPreset migration, four built-ins, save/manage/import/export UI, DPI binding. Tests: 124 Core tests (Swift Testing) + 11 Milestone2UITests (XCTest) with committed fixture sidecars — all green on arm64+x86_64 universal build. No hardware, no downloads. Also: ProcessError LocalizedError conformance; accessibilityElement .contain on container identifiers so child AX ids survive. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
86 lines
2.9 KiB
Swift
86 lines
2.9 KiB
Swift
import AppKit
|
|
import SwiftUI
|
|
import ICCeryCore
|
|
|
|
/// Root layout: 270 pt sidebar + main stage area with the notification
|
|
/// banner pinned to the top (docs/21 §Shell).
|
|
struct RootView: View {
|
|
@Bindable var workflow: TargetWorkflowViewModel
|
|
@State private var showingSettings = false
|
|
@State private var showingAbout = false
|
|
|
|
private var model: WizardViewModel { workflow.wizard }
|
|
|
|
var body: some View {
|
|
HStack(spacing: 0) {
|
|
SidebarView(
|
|
workflow: workflow,
|
|
onOpenSettings: { showingSettings = true },
|
|
onOpenAbout: { showingAbout = true }
|
|
)
|
|
|
|
Rectangle()
|
|
.fill(Theme.border)
|
|
.frame(width: 1)
|
|
|
|
VStack(spacing: 0) {
|
|
if let notice = model.notice {
|
|
NoticeBanner(notice: notice, onClose: model.dismissNotice)
|
|
}
|
|
stageContent
|
|
}
|
|
}
|
|
.frame(minWidth: 1100, minHeight: 700)
|
|
.background(Theme.background)
|
|
// #151: re-probe artefacts when the window regains focus —
|
|
// files deleted in Finder must re-lock stages.
|
|
.onReceive(
|
|
NotificationCenter.default.publisher(
|
|
for: NSWindow.didBecomeKeyNotification
|
|
)
|
|
) { _ in model.windowDidBecomeKey() }
|
|
.sheet(isPresented: $showingSettings) {
|
|
SettingsView()
|
|
}
|
|
.sheet(isPresented: $workflow.showingSavePreset) {
|
|
SavePresetDialog(workflow: workflow)
|
|
}
|
|
.sheet(isPresented: $workflow.showingManagePresets) {
|
|
ManagePresetsDialog(workflow: workflow)
|
|
}
|
|
.alert("ICCery 2.0.0", isPresented: $showingAbout) {
|
|
Button("OK") {}
|
|
} message: {
|
|
Text("Native macOS printer profiling workstation.\nFull About dialog lands in issue #31.")
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var stageContent: some View {
|
|
switch model.stage {
|
|
case .generate:
|
|
Stage1View(workflow: workflow)
|
|
case .layOutPrint:
|
|
Stage2View(workflow: workflow)
|
|
case .measure:
|
|
// Stage 3 stays a shell until M4, but a .ti2 resume still
|
|
// lands here — show the persisted state (#8, issue #140).
|
|
VStack(spacing: 16) {
|
|
if workflow.resumedFromTi2 {
|
|
Label("Resumed from .ti2", systemImage: "arrow.uturn.right")
|
|
.font(.callout)
|
|
.foregroundStyle(Theme.accent)
|
|
.accessibilityIdentifier("stage3LoadedTargetBanner")
|
|
}
|
|
Text(model.basename)
|
|
.font(.title3)
|
|
.foregroundStyle(Theme.text)
|
|
.accessibilityIdentifier("stage3TargetBasename")
|
|
StagePlaceholderView(stage: model.stage)
|
|
}
|
|
default:
|
|
StagePlaceholderView(stage: model.stage)
|
|
}
|
|
}
|
|
}
|