Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
332
apps/macos/Sources/OpenClaw/SettingsRootView.swift
Normal file
332
apps/macos/Sources/OpenClaw/SettingsRootView.swift
Normal file
@@ -0,0 +1,332 @@
|
||||
import AppKit
|
||||
import Observation
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsRootView: View {
|
||||
@Bindable var state: AppState
|
||||
private let permissionMonitor = PermissionMonitor.shared
|
||||
@State private var monitoringPermissions = false
|
||||
@State private var selectedTab: SettingsTab = .general
|
||||
@State private var cachedTabs: Set<SettingsTab>
|
||||
@State private var columnVisibility: NavigationSplitViewVisibility = .all
|
||||
@State private var snapshotPaths: (configPath: String?, stateDir: String?) = (nil, nil)
|
||||
let updater: UpdaterProviding?
|
||||
private let isPreview = ProcessInfo.processInfo.isPreview
|
||||
private let isNixMode = ProcessInfo.processInfo.isNixMode
|
||||
|
||||
init(state: AppState, updater: UpdaterProviding?, initialTab: SettingsTab? = nil) {
|
||||
let initial = initialTab ?? .general
|
||||
self.state = state
|
||||
self.updater = updater
|
||||
self._selectedTab = State(initialValue: initial)
|
||||
self._cachedTabs = State(initialValue: [initial])
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView(columnVisibility: self.animatedColumnVisibility) {
|
||||
List(selection: self.sidebarSelection) {
|
||||
ForEach(self.visibleGroups) { group in
|
||||
Section(group.title) {
|
||||
ForEach(group.tabs) { tab in
|
||||
Label(tab.title, systemImage: tab.systemImage)
|
||||
.tag(tab as SettingsTab?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
.navigationSplitViewColumnWidth(SettingsLayout.sidebarWidth)
|
||||
} detail: {
|
||||
self.detailContainer
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.onReceive(NotificationCenter.default.publisher(for: .openclawSelectSettingsTab)) { note in
|
||||
if let tab = note.object as? SettingsTab {
|
||||
withAnimation(.spring(response: 0.32, dampingFraction: 0.85)) {
|
||||
self.selectedTab = self.validTab(for: tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if let pending = SettingsTabRouter.consumePending() {
|
||||
self.selectedTab = self.validTab(for: pending)
|
||||
}
|
||||
self.cacheSelectedTab()
|
||||
self.updatePermissionMonitoring(for: self.selectedTab)
|
||||
}
|
||||
.onChange(of: self.state.debugPaneEnabled) { _, enabled in
|
||||
if !enabled, self.selectedTab == .debug {
|
||||
self.selectedTab = .general
|
||||
}
|
||||
}
|
||||
.onChange(of: self.selectedTab) { _, newValue in
|
||||
self.cachedTabs.insert(newValue)
|
||||
self.updatePermissionMonitoring(for: newValue)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
guard self.selectedTab == .permissions else { return }
|
||||
Task { await self.refreshPerms() }
|
||||
}
|
||||
.onDisappear { self.stopPermissionMonitoring() }
|
||||
.task {
|
||||
guard !self.isPreview else { return }
|
||||
await self.refreshPerms()
|
||||
}
|
||||
.task(id: self.state.connectionMode) {
|
||||
guard !self.isPreview else { return }
|
||||
await self.refreshSnapshotPaths()
|
||||
}
|
||||
}
|
||||
|
||||
private var visibleGroups: [SettingsTabGroup] {
|
||||
SettingsTabGroup.defaultGroups(showDebug: self.state.debugPaneEnabled)
|
||||
}
|
||||
|
||||
private var sidebarSelection: Binding<SettingsTab?> {
|
||||
Binding(
|
||||
get: { self.selectedTab },
|
||||
set: { tab in
|
||||
guard let tab else { return }
|
||||
self.selectedTab = self.validTab(for: tab)
|
||||
})
|
||||
}
|
||||
|
||||
private var animatedColumnVisibility: Binding<NavigationSplitViewVisibility> {
|
||||
Binding(
|
||||
get: { self.columnVisibility },
|
||||
set: { visibility in
|
||||
withAnimation(.easeInOut(duration: 0.22)) {
|
||||
self.columnVisibility = visibility
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var detailContainer: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
if self.isNixMode {
|
||||
self.nixManagedBanner
|
||||
}
|
||||
self.cachedDetailViews
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.padding(.horizontal, SettingsLayout.detailHorizontalPadding)
|
||||
.padding(.vertical, SettingsLayout.detailVerticalPadding)
|
||||
}
|
||||
|
||||
private var cachedDetailTabs: [SettingsTab] {
|
||||
let cached = self.cachedTabs.union([self.selectedTab])
|
||||
return self.visibleGroups.flatMap(\.tabs).filter { cached.contains($0) }
|
||||
}
|
||||
|
||||
private var nixManagedBanner: some View {
|
||||
// Prefer gateway-resolved paths; fall back to local env defaults if disconnected.
|
||||
let configPath = self.snapshotPaths.configPath ?? OpenClawPaths.configURL.path
|
||||
let stateDir = self.snapshotPaths.stateDir ?? OpenClawPaths.stateDirURL.path
|
||||
|
||||
return VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "gearshape.2.fill")
|
||||
.foregroundStyle(.secondary)
|
||||
Text("Managed by Nix")
|
||||
.font(.callout.weight(.semibold))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Config: \(configPath)")
|
||||
Text("State: \(stateDir)")
|
||||
}
|
||||
.font(.caption.monospaced())
|
||||
.foregroundStyle(.secondary)
|
||||
.textSelection(.enabled)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 10)
|
||||
.background(Color.gray.opacity(0.12))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
private var cachedDetailViews: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
ForEach(self.cachedDetailTabs) { tab in
|
||||
self.detailView(for: tab)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.opacity(tab == self.selectedTab ? 1 : 0)
|
||||
.allowsHitTesting(tab == self.selectedTab)
|
||||
.disabled(tab != self.selectedTab)
|
||||
.accessibilityHidden(tab != self.selectedTab)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func detailView(for tab: SettingsTab) -> AnyView {
|
||||
switch tab {
|
||||
case .general:
|
||||
AnyView(GeneralSettings(state: self.state, page: .general, isActive: self.selectedTab == tab))
|
||||
case .connection:
|
||||
AnyView(GeneralSettings(state: self.state, page: .connection, isActive: self.selectedTab == tab))
|
||||
case .permissions:
|
||||
AnyView(PermissionsSettings(
|
||||
status: self.permissionMonitor.status,
|
||||
refresh: self.refreshPerms,
|
||||
showOnboarding: { DebugActions.restartOnboarding() }))
|
||||
case .voiceWake:
|
||||
AnyView(VoiceWakeSettings(state: self.state, isActive: self.selectedTab == .voiceWake))
|
||||
case .channels:
|
||||
AnyView(ChannelsSettings(isActive: self.selectedTab == tab))
|
||||
case .skills:
|
||||
AnyView(SkillsSettings(state: self.state))
|
||||
case .cron:
|
||||
AnyView(CronSettings(isActive: self.selectedTab == tab))
|
||||
case .execApprovals:
|
||||
AnyView(ExecApprovalsSettings())
|
||||
case .sessions:
|
||||
AnyView(SessionsSettings())
|
||||
case .instances:
|
||||
AnyView(InstancesSettings(isActive: self.selectedTab == tab))
|
||||
case .config:
|
||||
AnyView(ConfigSettings())
|
||||
case .debug:
|
||||
AnyView(DebugSettings(state: self.state))
|
||||
case .about:
|
||||
AnyView(AboutSettings(updater: self.updater))
|
||||
}
|
||||
}
|
||||
|
||||
private func validTab(for requested: SettingsTab) -> SettingsTab {
|
||||
if requested == .debug, !self.state.debugPaneEnabled { return .general }
|
||||
return requested
|
||||
}
|
||||
|
||||
private func cacheSelectedTab() {
|
||||
self.cachedTabs.insert(self.selectedTab)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func refreshSnapshotPaths() async {
|
||||
let paths = await GatewayConnection.shared.snapshotPaths()
|
||||
self.snapshotPaths = paths
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func refreshPerms() async {
|
||||
guard !self.isPreview else { return }
|
||||
await self.permissionMonitor.refreshNow()
|
||||
}
|
||||
|
||||
private func updatePermissionMonitoring(for tab: SettingsTab) {
|
||||
guard !self.isPreview else { return }
|
||||
PermissionMonitoringSupport.setMonitoring(tab == .permissions, monitoring: &self.monitoringPermissions)
|
||||
}
|
||||
|
||||
private func stopPermissionMonitoring() {
|
||||
PermissionMonitoringSupport.stopMonitoring(&self.monitoringPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
private struct SettingsTabGroup: Identifiable {
|
||||
let title: String
|
||||
let tabs: [SettingsTab]
|
||||
|
||||
var id: String {
|
||||
self.title
|
||||
}
|
||||
|
||||
static func defaultGroups(showDebug: Bool) -> [SettingsTabGroup] {
|
||||
var groups = [
|
||||
SettingsTabGroup(title: "Basics", tabs: [.general, .connection, .permissions, .voiceWake]),
|
||||
SettingsTabGroup(title: "Automation", tabs: [.channels, .skills, .cron, .execApprovals]),
|
||||
SettingsTabGroup(title: "Data", tabs: [.sessions, .instances]),
|
||||
SettingsTabGroup(title: "Advanced", tabs: [.config]),
|
||||
SettingsTabGroup(title: "OpenClaw", tabs: [.about]),
|
||||
]
|
||||
|
||||
if showDebug {
|
||||
groups.insert(SettingsTabGroup(title: "Developer", tabs: [.debug]), at: groups.count - 1)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
}
|
||||
|
||||
enum SettingsTab: CaseIterable, Identifiable, Hashable {
|
||||
case general, connection, permissions, voiceWake, channels, skills, cron
|
||||
case execApprovals, sessions, instances, config, debug, about
|
||||
static let windowWidth: CGFloat = 1120
|
||||
static let windowHeight: CGFloat = 790
|
||||
|
||||
var id: Self {
|
||||
self
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .general: "General"
|
||||
case .connection: "Connection"
|
||||
case .permissions: "Permissions"
|
||||
case .voiceWake: "Voice & Talk"
|
||||
case .channels: "Channels"
|
||||
case .skills: "Skills"
|
||||
case .cron: "Cron Jobs"
|
||||
case .execApprovals: "Exec Approvals"
|
||||
case .sessions: "Sessions"
|
||||
case .instances: "Instances"
|
||||
case .config: "Config"
|
||||
case .debug: "Debug"
|
||||
case .about: "About"
|
||||
}
|
||||
}
|
||||
|
||||
var systemImage: String {
|
||||
switch self {
|
||||
case .general: "gearshape"
|
||||
case .connection: "point.3.connected.trianglepath.dotted"
|
||||
case .permissions: "lock.shield"
|
||||
case .voiceWake: "waveform.circle"
|
||||
case .channels: "link"
|
||||
case .skills: "sparkles"
|
||||
case .cron: "calendar.badge.clock"
|
||||
case .execApprovals: "terminal"
|
||||
case .sessions: "clock.arrow.circlepath"
|
||||
case .instances: "network"
|
||||
case .config: "slider.horizontal.3"
|
||||
case .debug: "ant"
|
||||
case .about: "info.circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum SettingsTabRouter {
|
||||
private static var pending: SettingsTab?
|
||||
|
||||
static func request(_ tab: SettingsTab) {
|
||||
self.pending = tab
|
||||
}
|
||||
|
||||
static func consumePending() -> SettingsTab? {
|
||||
defer { self.pending = nil }
|
||||
return self.pending
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let openclawSelectSettingsTab = Notification.Name("openclawSelectSettingsTab")
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct SettingsRootView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ForEach(SettingsTab.allCases, id: \.self) { tab in
|
||||
SettingsRootView(state: .preview, updater: DisabledUpdaterController(), initialTab: tab)
|
||||
.previewDisplayName(tab.title)
|
||||
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user