f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
127 lines
3.6 KiB
Swift
127 lines
3.6 KiB
Swift
import SwiftUI
|
|
import DSKit
|
|
|
|
/// DEVONthink-style 3-column shell. RootView only ROUTES; each page owns its own interior treatment
|
|
/// (no shell-level auto-inherit). macOS-only target.
|
|
public struct RootView: View {
|
|
@Environment(AppModel.self) private var model
|
|
@State private var columnVisibility: NavigationSplitViewVisibility = .all
|
|
|
|
public init() {}
|
|
|
|
public var body: some View {
|
|
NavigationSplitView(columnVisibility: $columnVisibility) {
|
|
Sidebar()
|
|
.navigationSplitViewColumnWidth(min: 220, ideal: 250)
|
|
} content: {
|
|
ContentColumn()
|
|
.navigationSplitViewColumnWidth(min: 300, ideal: 380)
|
|
} detail: {
|
|
DetailColumn()
|
|
}
|
|
.navigationSplitViewStyle(.balanced)
|
|
.tint(Sage.brand)
|
|
.task { await model.loadInitial() }
|
|
}
|
|
}
|
|
|
|
struct Sidebar: View {
|
|
@Environment(AppModel.self) private var model
|
|
|
|
var body: some View {
|
|
let selection = Binding<AppModel.Section?>(
|
|
get: { model.section },
|
|
set: { if let v = $0 { model.section = v } }
|
|
)
|
|
List(selection: selection) {
|
|
Section {
|
|
ForEach(AppModel.Section.allCases) { s in
|
|
Text(s.title).tag(s)
|
|
}
|
|
}
|
|
if model.section == .documents, !model.tree.isEmpty {
|
|
Section("도메인") {
|
|
ForEach(model.tree) { node in
|
|
DomainRow(node: node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.sidebar)
|
|
.background(Sage.sidebar)
|
|
}
|
|
}
|
|
|
|
struct DomainRow: View {
|
|
@Environment(AppModel.self) private var model
|
|
let node: DomainTreeNode
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
Circle().fill(Sage.domainColor(node.name)).frame(width: 8, height: 8)
|
|
Text(node.name).font(.callout).foregroundStyle(Sage.ink)
|
|
Spacer()
|
|
Text("\(node.count)").font(.caption).foregroundStyle(Sage.muted)
|
|
}
|
|
.contentShape(Rectangle())
|
|
.onTapGesture { model.section = .documents }
|
|
}
|
|
}
|
|
|
|
struct ContentColumn: View {
|
|
@Environment(AppModel.self) private var model
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch model.section {
|
|
case .dashboard: DashboardView()
|
|
case .documents: DocumentListView()
|
|
case .search: SearchView()
|
|
case .ask: AskView()
|
|
case .memos: MemoListView()
|
|
case .digest: DigestView()
|
|
}
|
|
}
|
|
.navigationTitle(model.section.title)
|
|
}
|
|
}
|
|
|
|
struct DetailColumn: View {
|
|
@Environment(AppModel.self) private var model
|
|
|
|
var body: some View {
|
|
Group {
|
|
switch model.section {
|
|
case .documents:
|
|
if let d = model.documentDetail { DocumentDetailView(detail: d) }
|
|
else { EmptyState(text: "문서를 선택하세요") }
|
|
case .memos:
|
|
if let m = model.memoDetail { MemoDetailView(memo: m) }
|
|
else { EmptyState(text: "메모를 선택하세요") }
|
|
default:
|
|
EmptyState(text: model.section.title)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct EmptyState: View {
|
|
let text: String
|
|
var body: some View {
|
|
Text(text)
|
|
.foregroundStyle(Sage.muted)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(Sage.surface)
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
#Preview("DS App — full shell") {
|
|
@Previewable @State var model = AppModel.preview
|
|
RootView()
|
|
.environment(model)
|
|
.task { await model.loadInitial() }
|
|
.frame(minWidth: 1000, minHeight: 660)
|
|
}
|
|
#endif
|