Better handling of errors in javascript while waiting in swift

Two main changes here:

- Wait for JS highlight methods to complete, if an error occurs
post that error back to Swift.

- When creating a highlight with a note, post a message back to
Swift to signify success. We wait for that message instead of
closing immediately, so that a user doesn't lose their note if
creating the highlight failed.
This commit is contained in:
Jackson Harper 2022-11-30 21:09:00 +08:00
parent 6f0f0ec8b5
commit 1b57d879ed
5 changed files with 101 additions and 38 deletions

View file

@ -13,6 +13,9 @@ struct HighlightsListCard: View {
let onDeleteHighlight: () -> Void
let onSetLabels: (String) -> Void
@State var errorAlertMessage: String?
@State var showErrorAlertMessage = false
var contextMenuView: some View {
Group {
Button(
@ -131,7 +134,9 @@ struct HighlightsListCard: View {
},
onCancel: {
showAnnotationModal = false
}
},
errorAlertMessage: $errorAlertMessage,
showErrorAlertMessage: $showErrorAlertMessage
)
}
}

View file

@ -87,9 +87,6 @@ struct WebReader: PlatformViewRepresentable {
context.coordinator.lastSavedAnnotationID = annotationSaveTransactionID
do {
try (webView as? OmnivoreWebView)?.dispatchEvent(.saveAnnotation(annotation: annotation))
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
showHighlightAnnotationModal = false
}
} catch {
showInSnackbar("Error saving note.")
}

View file

@ -27,6 +27,8 @@ struct WebReaderContainerView: View {
@State var annotation = String()
@State var showBottomBar = false
@State private var bottomBarOpacity = 0.0
@State private var errorAlertMessage: String?
@State private var showErrorAlertMessage = false
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@ -73,6 +75,11 @@ struct WebReaderContainerView: View {
case "annotate":
annotation = messageBody["annotation"] ?? ""
showHighlightAnnotationModal = true
case "noteCreated":
showHighlightAnnotationModal = false
case "highlightError":
errorAlertMessage = messageBody["error"] ?? "An error occurred."
showErrorAlertMessage = true
case "setHighlightLabels":
annotation = messageBody["highlightID"] ?? ""
showHighlightLabelsModal = true
@ -334,6 +341,12 @@ struct WebReaderContainerView: View {
SafariView(url: $0.url)
}
#endif
.alert(errorAlertMessage ?? "An error occurred", isPresented: $showErrorAlertMessage) {
Button("Ok", role: .cancel, action: {
errorAlertMessage = nil
showErrorAlertMessage = false
})
}
.sheet(isPresented: $showHighlightAnnotationModal) {
HighlightAnnotationSheet(
annotation: $annotation,
@ -342,7 +355,9 @@ struct WebReaderContainerView: View {
},
onCancel: {
showHighlightAnnotationModal = false
}
},
errorAlertMessage: $errorAlertMessage,
showErrorAlertMessage: $showErrorAlertMessage
)
}
.sheet(isPresented: $showHighlightLabelsModal) {

View file

@ -3,6 +3,8 @@ import SwiftUI
public struct HighlightAnnotationSheet: View {
@Binding var annotation: String
@Binding var errorAlertMessage: String?
@Binding var showErrorAlertMessage: Bool
let onSave: () -> Void
let onCancel: () -> Void
@ -10,11 +12,15 @@ public struct HighlightAnnotationSheet: View {
public init(
annotation: Binding<String>,
onSave: @escaping () -> Void,
onCancel: @escaping () -> Void
onCancel: @escaping () -> Void,
errorAlertMessage: Binding<String?>,
showErrorAlertMessage: Binding<Bool>
) {
self._annotation = annotation
self.onSave = onSave
self.onCancel = onCancel
self._errorAlertMessage = errorAlertMessage
self._showErrorAlertMessage = showErrorAlertMessage
}
public var body: some View {
@ -43,5 +49,11 @@ public struct HighlightAnnotationSheet: View {
Spacer()
}
.padding()
.alert(errorAlertMessage ?? "An error occurred", isPresented: $showErrorAlertMessage) {
Button("Ok", role: .cancel, action: {
errorAlertMessage = nil
showErrorAlertMessage = false
})
}
}
}

View file

@ -196,6 +196,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
props.articleMutations
)
if (result.errorMessage) {
throw 'Failed to create highlight: ' + result.errorMessage
}
if (!result.highlights || result.highlights.length == 0) {
// TODO: show an error message
console.error('Failed to create highlight')
@ -218,26 +222,18 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (!selectionData) {
return
}
const result = await createHighlightFromSelection(
selectionData,
annotation
)
if (!result) {
showErrorToast('Error saving highlight', { position: 'bottom-right' })
try {
const result = await createHighlightFromSelection(
selectionData,
annotation
)
if (!result) {
showErrorToast('Error saving highlight', { position: 'bottom-right' })
throw 'Error creating highlight'
}
} catch (error) {
throw error
}
// if (successAction === 'share' && canShareNative) {
// handleNativeShare(highlight.shortId)
// return
// } else {
// setFocusedHighlight(undefined)
// }
// if (successAction === 'addComment') {
// openNoteModal({
// highlightModalAction: 'addComment',
// highlight,
// })
// }
},
[
handleNativeShare,
@ -328,13 +324,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
}, [handleClickHighlight])
const handleAction = useCallback(
(action: HighlightAction) => {
async (action: HighlightAction) => {
switch (action) {
case 'delete':
removeHighlightCallback()
await removeHighlightCallback()
break
case 'create':
createHighlightCallback('none')
await createHighlightCallback('none')
break
case 'comment':
if (props.highlightBarDisabled || focusedHighlight) {
@ -375,7 +371,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
})
}
} else {
createHighlightCallback('share')
await createHighlightCallback('share')
}
break
case 'unshare':
@ -403,21 +399,49 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
]
)
const dispatchHighlightError = (action: string, error: unknown) => {
if (props.isAppleAppEmbed) {
window?.webkit?.messageHandlers.highlightAction?.postMessage({
actionID: 'highlightError',
highlightAction: action,
highlightID: focusedHighlight?.id,
error: typeof error === 'string' ? error : JSON.stringify(error),
})
}
}
const dispatchHighlightMessage = (actionID: string) => {
if (props.isAppleAppEmbed) {
window?.webkit?.messageHandlers.highlightAction?.postMessage({
actionID: actionID,
highlightID: focusedHighlight?.id,
})
}
}
useEffect(() => {
const annotate = () => {
handleAction('comment')
const safeHandleAction = async (action: HighlightAction) => {
try {
await handleAction(action)
} catch (error) {
dispatchHighlightError(action, error)
}
}
const highlight = () => {
handleAction('create')
const annotate = async () => {
await safeHandleAction('comment')
}
const share = () => {
handleAction('share')
const highlight = async () => {
await safeHandleAction('create')
}
const remove = () => {
handleAction('delete')
const share = async () => {
await safeHandleAction('share')
}
const remove = async () => {
await safeHandleAction('delete')
}
const dismissHighlight = () => {
@ -465,10 +489,20 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
'failed to change annotation for highlight with id',
focusedHighlight.id
)
dispatchHighlightError(
'saveAnnotation',
'Failed to create highlight.'
)
}
setFocusedHighlight(undefined)
dispatchHighlightMessage('noteCreated')
} else {
createHighlightCallback('none', event.annotation)
try {
await createHighlightCallback('none', event.annotation)
dispatchHighlightMessage('noteCreated')
} catch (error) {
dispatchHighlightError('saveAnnotation', error)
}
}
}