fix(reader): grey out share/permissions and other live-only actions offline
Share, Permissions, Templatize, Duplicate, Unpublish, Archive, Move, New Document, History, Insights (sheet + Viewer Insights toggle), and Download all hit the server directly with no offline path — disabled (with a tooltip on the two toolbar buttons) whenever the app isn't effectively online, instead of failing confusingly on tap. Left enabled: Edit, Pin/Unpin, Star/Unstar, Subscribed, Full Width (all queue via CachingOutlineAPIClient and sync later), Present and Search in Document (both read documents.info, which is cached), and Copy/Print (read already-loaded text directly, no network at all).
This commit is contained in:
@@ -12,6 +12,7 @@ import OutlineKit
|
||||
struct DocumentReaderView: View {
|
||||
@Environment(SessionStore.self) private var session
|
||||
@Environment(StarStore.self) private var starStore
|
||||
@AppStorage(CachingOutlineAPIClient.offlineModeDefaultsKey) private var isOfflineModeEnabled = false
|
||||
|
||||
@State private var viewModel: DocumentReaderViewModel
|
||||
let apiClient: OutlineAPIClient
|
||||
@@ -56,6 +57,15 @@ struct DocumentReaderView: View {
|
||||
self.onDocumentCreated = onDocumentCreated
|
||||
}
|
||||
|
||||
/// Editing, pin/star/subscribe, and Full Width all queue and sync later
|
||||
/// (see `CachingOutlineAPIClient`) — everything else here (sharing,
|
||||
/// permissions, move/archive/delete/duplicate/templatize, history,
|
||||
/// insights, export) hits the server directly with no offline path, so
|
||||
/// it's disabled rather than left to fail confusingly on tap.
|
||||
private var isEffectivelyOnline: Bool {
|
||||
session.networkMonitor.isOnline && !isOfflineModeEnabled
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -110,7 +120,8 @@ struct DocumentReaderView: View {
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.help("Share")
|
||||
.help(isEffectivelyOnline ? "Share" : "Sharing needs an internet connection")
|
||||
.disabled(!isEffectivelyOnline)
|
||||
.popover(isPresented: $isShowingShareSheet, arrowEdge: .bottom) {
|
||||
DocumentShareSheet(apiClient: apiClient, documentId: viewModel.documentId)
|
||||
}
|
||||
@@ -131,7 +142,8 @@ struct DocumentReaderView: View {
|
||||
} label: {
|
||||
Image(systemName: "doc.badge.plus")
|
||||
}
|
||||
.help("New Document")
|
||||
.help(isEffectivelyOnline ? "New Document" : "Creating documents needs an internet connection")
|
||||
.disabled(!isEffectivelyOnline)
|
||||
|
||||
Menu {
|
||||
menuContent
|
||||
@@ -235,7 +247,8 @@ struct DocumentReaderView: View {
|
||||
viewModel.isPinned,
|
||||
viewModel.isInsightsEnabled ?? false,
|
||||
viewModel.isFullWidth,
|
||||
viewModel.isEditing
|
||||
viewModel.isEditing,
|
||||
isEffectivelyOnline
|
||||
].map(String.init).joined(separator: "-")
|
||||
}
|
||||
|
||||
@@ -305,27 +318,33 @@ struct DocumentReaderView: View {
|
||||
Button("Permissions…") {
|
||||
isShowingShareSheet = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Templatize") {
|
||||
Task { await templatize() }
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button("Duplicate") {
|
||||
Task { await duplicate() }
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button("Unpublish") {
|
||||
isShowingUnpublishConfirmation = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button("Archive…") {
|
||||
isShowingArchiveConfirmation = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Move") {
|
||||
isShowingMoveSheet = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
// Multipart file upload is its own subsystem — deferred rather than
|
||||
// half-built here.
|
||||
Button("Import Document…") {}
|
||||
@@ -333,6 +352,7 @@ struct DocumentReaderView: View {
|
||||
Button("New Document") {
|
||||
isShowingNewDocumentSheet = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button(viewModel.isPinned ? "Unpin from Home" : "Pin to Home") {
|
||||
Task { await togglePin() }
|
||||
}
|
||||
@@ -342,9 +362,13 @@ struct DocumentReaderView: View {
|
||||
Button("History") {
|
||||
isShowingHistorySheet = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button("Insights") {
|
||||
isShowingInsightsSheet = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
// Present/Search in Document both read `documents.info`, which is
|
||||
// read-through cached — they work offline on whatever's cached.
|
||||
Button("Present") {
|
||||
isShowingPresentSheet = true
|
||||
}
|
||||
@@ -354,6 +378,7 @@ struct DocumentReaderView: View {
|
||||
Button("Download") {
|
||||
Task { await download() }
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
Button("Copy") {
|
||||
Task { await copyMarkdown() }
|
||||
}
|
||||
@@ -370,6 +395,7 @@ struct DocumentReaderView: View {
|
||||
get: { viewModel.isInsightsEnabled ?? false },
|
||||
set: { _ in Task { await toggleInsights() } }
|
||||
))
|
||||
.disabled(!isEffectivelyOnline)
|
||||
// Confirmed against a live server: there's no per-document embeds
|
||||
// field. Only a workspace-level setting exists, and that's not
|
||||
// reachable via the API either (no `team.update` endpoint in the
|
||||
@@ -386,6 +412,7 @@ struct DocumentReaderView: View {
|
||||
Button("Delete…", role: .destructive) {
|
||||
isShowingDeleteConfirmation = true
|
||||
}
|
||||
.disabled(!isEffectivelyOnline)
|
||||
}
|
||||
|
||||
private func star() async {
|
||||
|
||||
Reference in New Issue
Block a user