feat(settings): move Outline version to sidebar footer, drop Installation section
Removed the Integrations & Installation category and its lone Installation section entirely — Outline's server version now shows in the settings sidebar footer instead, alongside Outpost's own version (installation.info, fetched once when the sidebar appears). About stays where it was, unaffected.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#if os(macOS)
|
||||
import SwiftUI
|
||||
import OutlineKit
|
||||
|
||||
/// Swapped into the real sidebar's content slot (search field, collections
|
||||
/// tree, account footer) while Settings is open — same sidebar, different
|
||||
@@ -8,13 +9,23 @@ import SwiftUI
|
||||
/// back.
|
||||
///
|
||||
/// Grouped by `SettingsCategory` — `general` (ours) sits under an "Outpost"
|
||||
/// header at the top, then Outline's own Account/Workspace/Integrations &
|
||||
/// Installation groups, matching the settings page structure of the
|
||||
/// Outline web app.
|
||||
/// header at the top, then Outline's own Account/Workspace groups, matching
|
||||
/// the settings page structure of the Outline web app. Outline's own server
|
||||
/// version has no dedicated section (there used to be an Integrations &
|
||||
/// Installation category for just that) — it's cheap enough to show
|
||||
/// unconditionally in the footer here instead, alongside Outpost's own
|
||||
/// version.
|
||||
struct SettingsSidebarList: View {
|
||||
@Binding var selection: SettingsSection?
|
||||
let onDone: () -> Void
|
||||
|
||||
@Environment(SessionStore.self) private var session
|
||||
@State private var outlineVersion: String?
|
||||
|
||||
private var outpostVersion: String {
|
||||
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.1"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
@@ -46,12 +57,36 @@ struct SettingsSidebarList: View {
|
||||
|
||||
Divider()
|
||||
|
||||
versionFooter
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Done", action: onDone)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
.buttonStyle(.borderedProminent)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(12)
|
||||
}
|
||||
.task { await refreshOutlineVersion() }
|
||||
}
|
||||
|
||||
private var versionFooter: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Outpost \(outpostVersion)")
|
||||
if let outlineVersion {
|
||||
Text("Outline \(outlineVersion)")
|
||||
}
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
private func refreshOutlineVersion() async {
|
||||
guard let apiClient = session.apiClient else { return }
|
||||
outlineVersion = try? await apiClient.installationInfo().version
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -44,9 +44,6 @@ struct SettingsView: View {
|
||||
@State private var apiKeys: [OutlineAPIKey] = []
|
||||
@State private var isLoadingApiKeys = false
|
||||
@State private var apiKeysErrorMessage: String?
|
||||
@State private var installationInfo: OutlineInstallationInfo?
|
||||
@State private var isLoadingInstallationInfo = false
|
||||
@State private var installationInfoErrorMessage: String?
|
||||
|
||||
/// Full Local Sync and cache-clearing both need a real connection to be
|
||||
/// safe — clearing while offline (or letting Full Local Sync think it
|
||||
@@ -90,7 +87,6 @@ struct SettingsView: View {
|
||||
case .notifications: notificationsDetail
|
||||
case .passkeys: passkeysDetail
|
||||
case .apiAccess: apiAccessDetail
|
||||
case .installation: installationDetail
|
||||
case .offlineSync: offlineSyncDetail
|
||||
case .advanced: advancedDetail
|
||||
case .about: aboutDetail
|
||||
@@ -98,9 +94,8 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything in Account/Workspace/Integrations & Installation that
|
||||
/// isn't `.profile` — real content lands section by section; this is
|
||||
/// just the nav skeleton until then.
|
||||
/// Everything in Account/Workspace that isn't `.profile` — real content
|
||||
/// lands section by section; this is just the nav skeleton until then.
|
||||
private var comingSoonDetail: some View {
|
||||
VStack(spacing: 16) {
|
||||
sectionHeader
|
||||
@@ -824,55 +819,6 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Installation
|
||||
|
||||
private var installationDetail: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
sectionHeader
|
||||
|
||||
if isLoadingInstallationInfo && installationInfo == nil {
|
||||
ProgressView().controlSize(.small)
|
||||
} else if let installationInfoErrorMessage {
|
||||
Text(installationInfoErrorMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.red)
|
||||
} else if let installationInfo {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
labeledRow("Version", installationInfo.version)
|
||||
Divider()
|
||||
if installationInfo.versionsBehind > 0 {
|
||||
labeledRow("Latest Version", installationInfo.latestVersion)
|
||||
Divider()
|
||||
Label(
|
||||
"\(installationInfo.versionsBehind) version\(installationInfo.versionsBehind == 1 ? "" : "s") behind",
|
||||
systemImage: "exclamationmark.triangle.fill"
|
||||
)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.orange)
|
||||
} else {
|
||||
Label("Up to date", systemImage: "checkmark.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 420, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.task { await refreshInstallationInfo() }
|
||||
}
|
||||
|
||||
private func refreshInstallationInfo() async {
|
||||
guard let apiClient = session.apiClient else { return }
|
||||
isLoadingInstallationInfo = true
|
||||
defer { isLoadingInstallationInfo = false }
|
||||
do {
|
||||
installationInfo = try await apiClient.installationInfo()
|
||||
installationInfoErrorMessage = nil
|
||||
} catch {
|
||||
installationInfoErrorMessage = outlineErrorMessage(error, fallback: "Couldn't load installation info.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Offline & Sync
|
||||
|
||||
private var offlineSyncDetail: some View {
|
||||
|
||||
@@ -8,7 +8,6 @@ enum SettingsCategory: String, CaseIterable, Identifiable {
|
||||
case general
|
||||
case account
|
||||
case workspace
|
||||
case integrationsInstallation
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
@@ -17,19 +16,20 @@ enum SettingsCategory: String, CaseIterable, Identifiable {
|
||||
case .general: return "Outpost"
|
||||
case .account: return "Account"
|
||||
case .workspace: return "Workspace"
|
||||
case .integrationsInstallation: return "Integrations & Installation"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One entry in the Settings sidebar. Mirrors Outline's own settings
|
||||
/// categories (Account/Workspace/Integrations & Installation) so this app's
|
||||
/// settings read as a native counterpart to the web app's, plus a `general`
|
||||
/// group for things that are ours and don't map onto Outline's structure
|
||||
/// (offline/sync, advanced, about, appearance).
|
||||
/// categories (Account/Workspace) so this app's settings read as a native
|
||||
/// counterpart to the web app's, plus a `general` group for things that are
|
||||
/// ours and don't map onto Outline's structure (offline/sync, advanced,
|
||||
/// about, appearance). Outline's own version info moved to the sidebar
|
||||
/// footer (`SettingsSidebarList`) instead of a standalone
|
||||
/// Integrations & Installation section.
|
||||
///
|
||||
/// Most of the Account/Workspace/Integrations cases are navigation-only for
|
||||
/// now — `SettingsView` renders a "Coming Soon" placeholder for anything not
|
||||
/// Most of the Account/Workspace cases are navigation-only for now —
|
||||
/// `SettingsView` renders a "Coming Soon" placeholder for anything not
|
||||
/// explicitly built yet. Content lands section by section.
|
||||
enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
||||
// General (ours)
|
||||
@@ -41,9 +41,6 @@ enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
||||
// Workspace
|
||||
case details, authentication, security, ai, members, groups, templates, emojis, applications, shared, links, webhooks, importData, exportData
|
||||
|
||||
// Integrations & Installation
|
||||
case installation
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var category: SettingsCategory {
|
||||
@@ -54,8 +51,6 @@ enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
||||
return .account
|
||||
case .details, .authentication, .security, .ai, .members, .groups, .templates, .emojis, .applications, .shared, .links, .webhooks, .importData, .exportData:
|
||||
return .workspace
|
||||
case .installation:
|
||||
return .integrationsInstallation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +79,6 @@ enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
||||
case .webhooks: return "Webhooks"
|
||||
case .importData: return "Import"
|
||||
case .exportData: return "Export"
|
||||
case .installation: return "Installation"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,16 +107,15 @@ enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
|
||||
case .webhooks: return "bolt.horizontal"
|
||||
case .importData: return "square.and.arrow.down"
|
||||
case .exportData: return "square.and.arrow.up"
|
||||
case .installation: return "shippingbox"
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything actually built so far — everything else in Account/
|
||||
/// Workspace/Integrations & Installation renders a "Coming Soon"
|
||||
/// placeholder until its content is specified and built.
|
||||
/// Workspace renders a "Coming Soon" placeholder until its content is
|
||||
/// specified and built.
|
||||
var isImplemented: Bool {
|
||||
switch self {
|
||||
case .appearance, .offlineSync, .advanced, .about, .profile, .preferences, .notifications, .passkeys, .apiAccess, .installation:
|
||||
case .appearance, .offlineSync, .advanced, .about, .profile, .preferences, .notifications, .passkeys, .apiAccess:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user