Merge pull request #3653 from omnivore-app/fix/ios-reading-position

iOS read position and view hierarchy fixes
This commit is contained in:
Jackson Harper
2024-03-08 17:55:42 +08:00
committed by GitHub
15 changed files with 282 additions and 198 deletions

View File

@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/nathantannar4/Engine",
"state" : {
"revision" : "31949c114698e4fd43fd76290913bca415fa87bc",
"version" : "1.1.0"
"revision" : "e9867eb6df013abc65c3437d295e594077469a13",
"version" : "1.5.1"
}
},
{
@ -180,24 +180,6 @@
"version" : "1.0.2"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms",
"state" : {
"revision" : "da4e36f86544cdf733a40d59b3a2267e3a7bbf36",
"version" : "1.0.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
"version" : "1.0.6"
}
},
{
"identity" : "swift-graphql",
"kind" : "remoteSourceControl",
@ -257,8 +239,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/nathantannar4/Transmission",
"state" : {
"revision" : "9517912f8f528c777f86f7896b5c35d7e43fa916",
"version" : "1.0.1"
"revision" : "3dac53ae4bddc7ab99e6374622a9c5eefbe50eed",
"version" : "1.1.4"
}
},
{
@ -266,8 +248,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/nathantannar4/Turbocharger",
"state" : {
"revision" : "b4201ba0bc094facf6cabe3b36fd3763b51ccfc8",
"version" : "1.0.1"
"revision" : "095344c0cac57873e1552f30d3561ab1bec5ae35",
"version" : "1.1.4"
}
},
{

View File

@ -71,8 +71,9 @@ var dependencies: [Package.Dependency] {
.package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.2.2"),
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.0.0"),
.package(url: "https://github.com/PostHog/posthog-ios.git", from: "2.0.0"),
.package(url: "https://github.com/nathantannar4/Transmission", from: "1.0.1"),
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0")
.package(url: "https://github.com/nathantannar4/Engine", exact: "1.5.1"),
.package(url: "https://github.com/nathantannar4/Turbocharger", exact: "1.1.4"),
.package(url: "https://github.com/nathantannar4/Transmission", from: "1.1.4")
]
// Comment out following line for macOS build
deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", from: "13.1.0"))

View File

@ -14,7 +14,7 @@ struct MacFeedCardNavigationLink: View {
var body: some View {
ZStack {
LibraryItemCard(item: LibraryItemData.make(from: item), viewer: dataService.currentViewer)
LibraryItemCard(item: item, viewer: dataService.currentViewer)
NavigationLink(destination: LinkItemDetailView(
linkedItemObjectID: item.objectID,
isPDF: item.isPDF
@ -36,7 +36,7 @@ struct LibraryItemListNavigationLink: View {
Button(action: {
viewModel.presentItem(item: item)
}, label: {
LibraryItemCard(item: LibraryItemData.make(from: item), viewer: dataService.currentViewer)
LibraryItemCard(item: item, viewer: dataService.currentViewer)
})
}
}
@ -54,7 +54,7 @@ struct LibraryItemGridCardNavigationLink: View {
Button(action: {
viewModel.presentItem(item: item)
}, label: {
GridCard(item: LibraryItemData.make(from: item))
GridCard(item: item)
})
.buttonStyle(.plain)
.aspectRatio(1.0, contentMode: .fill)

View File

@ -150,13 +150,15 @@ struct EmptyState: View {
return AnyView(Group {
Spacer()
VStack(alignment: .center, spacing: 20) {
Text("No results found for this query")
.font(Font.system(size: 18, weight: .bold))
if viewModel.showLoadingBar == .none {
VStack(alignment: .center, spacing: 20) {
Text("No results found for this query")
.font(Font.system(size: 18, weight: .bold))
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
Spacer()
})
@ -493,7 +495,8 @@ struct AnimatingCellHeight: AnimatableModifier {
options: PresentationLinkTransition.SlideTransitionOptions(edge: .trailing,
options:
PresentationLinkTransition.Options(
modalPresentationCapturesStatusBarAppearance: true
modalPresentationCapturesStatusBarAppearance: true,
preferredPresentationBackgroundColor: ThemeManager.currentBgColor
))),
isPresented: $viewModel.presentWebContainer,
destination: {
@ -715,15 +718,6 @@ struct AnimatingCellHeight: AnimatableModifier {
}
}
var redactedItems: some View {
ForEach(Array(fakeLibraryItems(dataService: dataService).enumerated()), id: \.1.id) { _, item in
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
LibraryItemCard(item: item, viewer: dataService.currentViewer)
.listRowSeparatorTint(Color.thBorderColor)
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
}.redacted(reason: .placeholder)
}
var listItems: some View {
ForEach(Array(viewModel.fetcher.items.enumerated()), id: \.1.unwrappedID) { idx, item in
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
@ -814,9 +808,7 @@ struct AnimatingCellHeight: AnimatableModifier {
}
}
if viewModel.showLoadingBar == .redacted {
redactedItems
} else if viewModel.showLoadingBar == .simple {
if viewModel.showLoadingBar == .redacted || viewModel.showLoadingBar == .simple {
VStack {
ProgressView()
}
@ -843,7 +835,9 @@ struct AnimatingCellHeight: AnimatableModifier {
}, header: {
filtersHeader
})
BottomView(viewModel: viewModel)
if viewModel.showLoadingBar == .none {
BottomView(viewModel: viewModel)
}
}
.padding(0)
.listStyle(.plain)
@ -1001,14 +995,7 @@ struct AnimatingCellHeight: AnimatableModifier {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325, maximum: 400), spacing: 16)], alignment: .center, spacing: 30) {
if viewModel.showLoadingBar == .redacted {
ForEach(fakeLibraryItems(dataService: dataService), id: \.id) { item in
GridCard(item: item)
.aspectRatio(1.0, contentMode: .fill)
.background(Color.systemBackground)
.cornerRadius(6)
}.redacted(reason: .placeholder)
} else if viewModel.showLoadingBar == .simple {
if viewModel.showLoadingBar == .redacted || viewModel.showLoadingBar == .simple {
VStack {
ProgressView()
}
@ -1056,7 +1043,7 @@ struct AnimatingCellHeight: AnimatableModifier {
}
}
if viewModel.fetcher.items.isEmpty {
if viewModel.fetcher.items.isEmpty || viewModel.showLoadingBar == .redacted || viewModel.showLoadingBar == .simple {
EmptyState(viewModel: viewModel)
} else {
HStack {
@ -1125,31 +1112,6 @@ struct LinkDestination: View {
}
}
func fakeLibraryItems(dataService _: DataService) -> [LibraryItemData] {
Array(
repeatElement(0, count: 20)
.map { _ in
LibraryItemData(
id: UUID().uuidString,
title: "fake title that is kind of long so it looks better",
pageURLString: "",
isArchived: false,
author: "fake author",
deepLink: nil,
hasLabels: false,
noteText: nil,
readingProgress: 10,
wordsCount: 10,
isPDF: false,
highlights: nil,
sortedLabels: [],
imageURL: nil,
publisherDisplayName: "fake publisher",
descriptionText: "This is a fake description"
)
})
}
struct BottomView: View {
@ObservedObject var viewModel: HomeFeedViewModel
@EnvironmentObject var dataService: DataService

View File

@ -73,7 +73,12 @@ enum LoadingBarStyle {
self.linkIsActive = true
}
}
func pushLinkedRequest(request: LinkRequest) {
self.linkRequest = request
self.presentWebContainer = true
}
private var filterState: FetcherFilterState? {
if let appliedFilter = appliedFilter {
return FetcherFilterState(

View File

@ -101,7 +101,8 @@
Spacer()
Image(systemName: "chevron.right")
}.onTapGesture {
viewModel.linkRequest = LinkRequest(id: UUID(), serverID: item.id)
homeFeedViewModel.pushLinkedRequest(request: LinkRequest(id: UUID(), serverID: item.id))
dismiss()
}
}
}

View File

@ -4,47 +4,120 @@
import SwiftUI
import Utils
import Views
import Transmission
@MainActor final class PushNotificationSettingsViewModel: ObservableObject {
@Published var isLoading = false
@Published var emails = [NewsletterEmail]()
@Published var desiredNotificationsEnabled = false
@AppStorage(UserDefaultKey.notificationsEnabled.rawValue) var notificationsEnabled = false
@MainActor final class PushNotificationSettingsViewModel: ObservableObject {
@Published var isLoading = false
@Published var isLoadingRules = true
func checkPushNotificationsStatus() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = settings.alertSetting == UNNotificationSetting.enabled
}
@Published var emails = [NewsletterEmail]()
@Published var desiredNotificationsEnabled = false
@Published var allSubscriptionsNotificationRule: Rule?
@Published var hasSubscriptionsNotifyRule = false
@Published var showOperationToast = false
@Published var operationStatus: OperationStatus = .none
@Published var operationMessage: String?
let subscriptionRuleName = "system.autoNotify.subscriptions"
@AppStorage(UserDefaultKey.notificationsEnabled.rawValue) var notificationsEnabled = false
func checkPushNotificationsStatus() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = settings.alertSetting == UNNotificationSetting.enabled
}
}
}
func tryUpdateToDesired(dataService: DataService) {
UserDefaults.standard.set(desiredNotificationsEnabled, forKey: UserDefaultKey.notificationsEnabled.rawValue)
func tryUpdateToDesired(dataService: DataService) {
UserDefaults.standard.set(desiredNotificationsEnabled, forKey: UserDefaultKey.notificationsEnabled.rawValue)
if desiredNotificationsEnabled {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = granted
Task {
if let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) {
_ = try? await dataService.syncDeviceToken(
deviceTokenOperation: DeviceTokenOperation.addToken(token: savedToken))
}
NotificationCenter.default.post(name: Notification.Name("ReconfigurePushNotifications"), object: nil)
if desiredNotificationsEnabled {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = granted
Task {
if let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) {
_ = try? await dataService.syncDeviceToken(
deviceTokenOperation: DeviceTokenOperation.addToken(token: savedToken))
}
NotificationCenter.default.post(name: Notification.Name("ReconfigurePushNotifications"), object: nil)
}
}
} else {
if let tokenID = UserDefaults.standard.string(forKey: UserDefaultKey.deviceTokenID.rawValue) {
Task {
try? await Services().dataService.syncDeviceToken(deviceTokenOperation: .deleteToken(tokenID: tokenID))
}
}
} else {
if let tokenID = UserDefaults.standard.string(forKey: UserDefaultKey.deviceTokenID.rawValue) {
Task {
try? await Services().dataService.syncDeviceToken(deviceTokenOperation: .deleteToken(tokenID: tokenID))
}
}
}
}
func loadRule(dataService: DataService) async {
do {
let rule = try await dataService.rules().filter { $0.name == subscriptionRuleName }.first
setAllSubscriptionRule(rule: rule)
} catch {
print("error fetching", error)
setAllSubscriptionRule(rule: nil)
}
}
func setAllSubscriptionRule(rule: Rule?) {
allSubscriptionsNotificationRule = rule
hasSubscriptionsNotifyRule = allSubscriptionsNotificationRule != nil
isLoadingRules = false
}
func createSubscriptionNotificationRule(dataService: DataService) {
if allSubscriptionsNotificationRule != nil {
return
}
operationMessage = "Creating notification rule..."
operationStatus = .isPerforming
showOperationToast = true
Task {
do {
let rule = try await dataService.createNotificationRule(
name: subscriptionRuleName,
filter: "in:all has:subscription"
)
setAllSubscriptionRule(rule: rule)
operationMessage = "Rule created"
operationStatus = .success
} catch {
print("error creating notification rule: ", error)
operationMessage = "Failed to create notification rule"
operationStatus = .failure
}
}
}
func deleteSubscriptionNotificationRule(dataService: DataService) {
operationMessage = "Creating label rule..."
operationStatus = .isPerforming
showOperationToast = true
Task {
do {
if let allSubscriptionsNotificationRule = allSubscriptionsNotificationRule {
_ = try await dataService.deleteRule(ruleID: allSubscriptionsNotificationRule.id)
setAllSubscriptionRule(rule: nil)
operationMessage = "Notification rule deleted"
operationStatus = .success
}
} catch {
operationMessage = "Failed to create label rule"
operationStatus = .failure
}
}
}
}
struct PushNotificationSettingsView: View {
@EnvironmentObject var dataService: DataService
@Environment(\.dismiss) private var dismiss
@ -54,6 +127,12 @@
var body: some View {
Group {
WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $viewModel.showOperationToast) {
OperationToast(operationMessage: $viewModel.operationMessage, showOperationToast: $viewModel.showOperationToast, operationStatus: $viewModel.operationStatus)
} label: {
EmptyView()
}.buttonStyle(.plain)
#if os(iOS)
Form {
innerBody
@ -68,7 +147,10 @@
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in
dismiss()
}
.task { viewModel.checkPushNotificationsStatus() }
.task {
viewModel.checkPushNotificationsStatus()
await viewModel.loadRule(dataService: dataService)
}
}
private var notificationsText: some View {
@ -84,6 +166,14 @@
.accentColor(.blue)
}
private var rulesSection: some View {
if viewModel.isLoadingRules {
AnyView(EmptyView())
} else {
AnyView(Toggle("Notify me when new items arrive from my subscriptions", isOn: $viewModel.hasSubscriptionsNotifyRule))
}
}
private var innerBody: some View {
Group {
Section {
@ -96,12 +186,35 @@
notificationsText
}
Section {
rulesSection
}
Section {
NavigationLink("Devices") {
PushNotificationDevicesView()
}
}
}
.onChange(of: viewModel.hasSubscriptionsNotifyRule) { newValue in
print("has notification rule: \(newValue)")
if viewModel.isLoadingRules {
return
}
if newValue {
viewModel.createSubscriptionNotificationRule(dataService: dataService)
} else {
viewModel.deleteSubscriptionNotificationRule(dataService: dataService)
}
}
.onChange(of: viewModel.operationStatus) { newValue in
if newValue == .success || newValue == .failure {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
viewModel.showOperationToast = false
}
}
}
.navigationTitle(LocalText.pushNotificationsGeneric)
}
}

View File

@ -273,7 +273,7 @@ struct SubscriptionsView: View {
await viewModel.cancelSubscription(dataService: dataService, subscription: subscription)
}
}
)
).background(Color.systemBackground)
} label: {
SubscriptionCell(subscription: subscription)
}
@ -526,6 +526,25 @@ struct SubscriptionSettingsView: View {
}
}
}
//
// var notificationRuleRow: some View {
// HStack {
// Text("Add Labels")
// Spacer()
// if isLoadingRule || viewModel.rules != nil {
// Button(action: { showLabelsSelector = true }, label: {
// if let ruleLabels = ruleLabels {
// let labelNames = ruleLabels.map(\.unwrappedName)
// Text("[\(labelNames.joined(separator: ","))]")
// } else {
// Text("Create Rule")
// }
// }).tint(Color.blue)
// } else {
// ProgressView()
// }
// }
// }
var body: some View {
VStack {

View File

@ -10,6 +10,11 @@ import Views
@Published var errorMessage: String?
func loadItem(dataService: DataService, username: String, requestID: String) async {
if let cached = Models.LibraryItem.lookup(byID: requestID, inContext: dataService.viewContext) {
item = cached
return
}
guard let objectID = try? await dataService.loadItemContentUsingRequestID(username: username,
requestID: requestID)
else {
@ -57,8 +62,6 @@ public struct WebReaderLoadingContainer: View {
PDFWrapperView(pdfURL: pdfURL)
}
#endif
} else if item.state == "CONTENT_NOT_FETCHED" {
ProgressView()
} else {
WebReaderContainerView(item: item)
#if os(iOS)
@ -72,11 +75,7 @@ public struct WebReaderLoadingContainer: View {
} else {
ProgressView()
.task {
if let username = dataService.currentViewer?.username {
await viewModel.loadItem(dataService: dataService, username: username, requestID: requestID)
} else {
viewModel.errorMessage = "You are not logged in."
}
await viewModel.loadItem(dataService: dataService, username: "me", requestID: requestID)
}
}
}

View File

@ -89,4 +89,51 @@ public extension DataService {
}
}
}
func createNotificationRule(name: String, filter: String) async throws -> Rule {
enum MutationResult {
case result(rule: Rule)
case error(errorMessage: String)
}
let selection = Selection<MutationResult, Unions.SetRuleResult> {
try $0.on(
setRuleError: .init { .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") },
setRuleSuccess: .init { .result(rule: try $0.rule(selection: ruleSelection)) }
)
}
let mutation = Selection.Mutation {
try $0.setRule(
input: InputObjects.SetRuleInput(
actions: [InputObjects.RuleActionInput(params: [], type: .sendNotification)],
enabled: true,
eventTypes: [.pageCreated],
filter: filter,
id: OptionalArgument(nil),
name: name
),
selection: selection
)
}
let path = appEnvironment.graphqlPath
let headers = networker.defaultHeaders
return try await withCheckedThrowingContinuation { continuation in
send(mutation, to: path, headers: headers) { queryResult in
guard let payload = try? queryResult.get() else {
continuation.resume(throwing: BasicError.message(messageText: "network error"))
return
}
switch payload.data {
case let .result(rule: rule):
continuation.resume(returning: rule)
case let .error(errorMessage: errorMessage):
continuation.resume(throwing: BasicError.message(messageText: errorMessage))
}
}
}
}
}

View File

@ -11,10 +11,10 @@ public enum GridCardAction {
}
public struct GridCard: View {
let item: LibraryItemData
@ObservedObject var item: Models.LibraryItem
public init(
item: LibraryItemData
item: Models.LibraryItem
) {
self.item = item
}
@ -67,7 +67,7 @@ public struct GridCard: View {
var fallbackImage: some View {
GeometryReader { geo in
HStack {
Text(item.title)
Text(item.title ?? "")
.font(fallbackFont)
.frame(alignment: .center)
.multilineTextAlignment(.leading)
@ -232,7 +232,7 @@ public struct GridCard: View {
.dynamicTypeSize(.xSmall ... .medium)
.padding(.horizontal, 15)
Text(item.title)
Text(item.title ?? "")
.lineLimit(2)
.font(.appHeadline)
.foregroundColor(.appGrayTextContrast)
@ -246,7 +246,7 @@ public struct GridCard: View {
// Link description and image
HStack(alignment: .top) {
Text(item.descriptionText ?? item.title)
Text(item.descriptionText ?? item.title ?? "")
.font(.appSubheadline)
.foregroundColor(.appGrayTextContrast)
.lineLimit(2)

View File

@ -32,11 +32,11 @@ enum FlairLabels: String {
}
public extension View {
func draggableItem(item: LibraryItemData) -> some View {
func draggableItem(item: Models.LibraryItem) -> some View {
#if os(iOS)
if #available(iOS 16.0, *), let url = item.deepLink {
return AnyView(self.draggable(url) {
Label(item.title, systemImage: "link")
Label(item.title ?? "", systemImage: "link")
})
}
#endif
@ -44,75 +44,12 @@ public extension View {
}
}
public struct LibraryItemData {
public var id: String
public let title: String
public let pageURLString: String
public var isArchived: Bool
public let author: String?
public let deepLink: URL?
public let hasLabels: Bool
public let noteText: String?
public let readingProgress: Double
public let wordsCount: Int64
public let isPDF: Bool
public let highlights: NSSet?
public let sortedLabels: [LinkedItemLabel]
public let imageURL: URL?
public let publisherDisplayName: String?
public let descriptionText: String?
public init(id: String, title: String, pageURLString: String, isArchived: Bool, author: String?,
deepLink: URL?, hasLabels: Bool, noteText: String?,
readingProgress: Double, wordsCount: Int64, isPDF: Bool, highlights: NSSet?,
sortedLabels: [LinkedItemLabel], imageURL: URL?, publisherDisplayName: String?, descriptionText: String?)
{
self.id = id
self.title = title
self.pageURLString = pageURLString
self.isArchived = isArchived
self.author = author
self.deepLink = deepLink
self.hasLabels = hasLabels
self.noteText = noteText
self.readingProgress = readingProgress
self.wordsCount = wordsCount
self.isPDF = isPDF
self.highlights = highlights
self.sortedLabels = sortedLabels
self.imageURL = imageURL
self.publisherDisplayName = publisherDisplayName
self.descriptionText = descriptionText
}
public static func make(from item: Models.LibraryItem) -> LibraryItemData {
LibraryItemData(
id: item.unwrappedID,
title: item.unwrappedTitle,
pageURLString: item.unwrappedPageURLString,
isArchived: item.isArchived,
author: item.author,
deepLink: item.deepLink,
hasLabels: item.hasLabels,
noteText: item.noteText,
readingProgress: item.readingProgress,
wordsCount: item.wordsCount,
isPDF: item.isPDF,
highlights: item.highlights,
sortedLabels: item.sortedLabels,
imageURL: item.imageURL,
publisherDisplayName: item.publisherDisplayName,
descriptionText: item.descriptionText
)
}
}
public struct LibraryItemCard: View {
let viewer: Viewer?
var item: LibraryItemData
@ObservedObject var item: Models.LibraryItem
@State var noteLineLimit: Int? = 3
public init(item: LibraryItemData, viewer: Viewer?) {
public init(item: Models.LibraryItem, viewer: Viewer?) {
self.item = item
self.viewer = viewer
}
@ -362,7 +299,7 @@ public struct LibraryItemCard: View {
readInfo
.dynamicTypeSize(.xSmall ... .medium)
Text(item.title)
Text(item.title ?? "")
.font(.body).fontWeight(.semibold)
.lineSpacing(1.25)
.foregroundColor(.appGrayTextContrast)

View File

@ -55,7 +55,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
let userInfo = notification.request.content.userInfo
UIApplication.shared.applicationIconBadgeNumber = 0
print(userInfo) // extract data sent along with PN
print("push data", userInfo) // extract data sent along with PN
completionHandler([[.banner, .sound]])
}

View File

@ -70,9 +70,13 @@ const sendNotification = async (obj: RuleActionObj) => {
const message = {
title: item.author || item.siteName || 'Omnivore',
body: item.title,
image: item.thumbnail,
}
const data = {
libraryItemId: item.id,
}
return sendPushNotifications(obj.userId, message, 'rule')
return sendPushNotifications(obj.userId, message, 'rule', data)
}
const getRuleAction = (actionType: RuleActionType): RuleActionFunc => {

View File

@ -19,8 +19,18 @@ type ConfirmationModalProps = {
}
export function ConfirmationModal(props: ConfirmationModalProps): JSX.Element {
const safeOnOpenChange = useCallback(
(open: boolean) => {
props.onOpenChange(open)
setTimeout(() => {
document.body.style.removeProperty('pointer-events')
}, 200)
},
[props]
)
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
<ModalRoot defaultOpen onOpenChange={safeOnOpenChange}>
<ModalOverlay />
<ModalContent css={{ bg: '$grayBg', maxWidth: '20em', zIndex: '20' }}>
<VStack alignment="center" distribution="center" css={{ p: '15px' }}>
@ -46,11 +56,15 @@ export function ConfirmationModal(props: ConfirmationModalProps): JSX.Element {
</Button>
<Button
style="ctaDarkYellow"
onClick={props.onAccept}
onClick={() => {
props.onAccept()
document.body.style.removeProperty('pointer-events')
}}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault()
props.onAccept()
document.body.style.removeProperty('pointer-events')
}
}}
>