Skip to content

Commit f0b73cd

Browse files
authored
Improve error and document handling (#1221)
* Simplify stale document rendering * Remove Swift version compile-time checks
1 parent d6268d8 commit f0b73cd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+55
-490
lines changed

Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -138,42 +138,27 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
138138

139139
state = .connecting
140140

141-
let html: String
142-
do {
143-
html = try await fetchDOM(url: self.url)
144-
} catch {
145-
state = .connectionFailed(error)
146-
return
147-
}
148-
149-
let domValues: DOMValues
150141
do {
142+
let html = try await fetchDOM(url: self.url)
143+
151144
let doc = try SwiftSoup.parse(html, self.url.absoluteString, SwiftSoup.Parser.xmlParser().settings(.init(true, true)))
152-
domValues = try self.extractDOMValues(doc)
145+
let domValues = try self.extractDOMValues(doc)
153146
// extract the root layout, removing anything within the `<div data-phx-main>`.
154147
let mainDiv = try doc.select("div[data-phx-main]")[0]
155148
try mainDiv.replaceWith(doc.createElement("phx-main"))
156149
self.rootLayout = try LiveViewNativeCore.Document.parse(doc.outerHtml())
157-
} catch {
158-
state = .connectionFailed(error)
159-
return
160-
}
161-
162-
self.domValues = domValues
163-
164-
if socket == nil {
165-
do {
150+
151+
self.domValues = domValues
152+
153+
if socket == nil {
166154
try await self.connectSocket(domValues)
167-
} catch {
168-
state = .connectionFailed(error)
169-
return
170155
}
171-
}
172-
173-
do {
156+
174157
try await navigationPath.first!.coordinator.connect(domValues: domValues, redirect: false)
175158
} catch {
176159
self.state = .connectionFailed(error)
160+
logger.log(level: .error, "\(error.localizedDescription)")
161+
return
177162
}
178163
}
179164

@@ -371,8 +356,8 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
371356
case .push:
372357
navigationPath.append(entry)
373358
case .replace:
374-
// If there is nothing to replace, change the root URL.
375359
if !navigationPath.isEmpty {
360+
coordinator.document = navigationPath.last!.coordinator.document
376361
await navigationPath.last?.coordinator.disconnect()
377362
navigationPath[navigationPath.count - 1] = entry
378363
try await coordinator.connect(domValues: self.domValues, redirect: true)
@@ -387,7 +372,6 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
387372
case .push:
388373
navigationPath.append(entry)
389374
case .replace:
390-
// If there is nothing to replace, change the root URL.
391375
if !navigationPath.isEmpty {
392376
navigationPath[navigationPath.count - 1] = entry
393377
}

Sources/LiveViewNative/Coordinators/LiveSessionState.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ public enum LiveSessionState {
2929
return false
3030
}
3131
}
32+
33+
/// Is the enum in the `connected` state.
34+
var isConnected: Bool {
35+
if case .connected = self {
36+
return true
37+
} else {
38+
return false
39+
}
40+
}
3241
}

Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
4747
}
4848
return subject
4949
}
50-
private var rendered: Root!
50+
internal var rendered: Root!
5151
internal let builder = ViewTreeBuilder<R>()
5252

5353
private var currentConnectionToken: ConnectionAttemptToken?
@@ -56,23 +56,9 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
5656
private(set) internal var eventSubject = PassthroughSubject<(String, Payload), Never>()
5757
private(set) internal var eventHandlers = Set<AnyCancellable>()
5858

59-
init(session: LiveSessionCoordinator<R>, url: URL, from other: LiveViewCoordinator<R>? = nil) {
59+
init(session: LiveSessionCoordinator<R>, url: URL) {
6060
self.session = session
6161
self.url = url
62-
if let other {
63-
self.channel = other.channel
64-
if let rendered = other.rendered {
65-
// create a new document that won't be merged into.
66-
// should core have a "clone document" ability?
67-
self.document = try! Document.parse(rendered.buildString())
68-
}
69-
self.rendered = other.rendered
70-
self.currentConnectionToken = other.currentConnectionToken
71-
self.currentConnectionTask = other.currentConnectionTask
72-
self.eventSubject = other.eventSubject
73-
self.eventHandlers = other.eventHandlers
74-
self.internalState = other.internalState
75-
}
7662

7763
self.handleEvent("native_redirect") { [weak self] payload in
7864
guard let self,
@@ -301,18 +287,14 @@ public class LiveViewCoordinator<R: RootRegistry>: ObservableObject {
301287
for try await joinResult in join(channel: channel) {
302288
switch joinResult {
303289
case .rendered(let payload):
304-
await MainActor.run {
305-
self.internalState = .connecting
306-
}
307290
await MainActor.run {
308291
self.handleJoinPayload(renderedPayload: payload)
292+
self.internalState = .connected
309293
}
310294
case .redirect(let liveRedirect):
311295
self.url = liveRedirect.to
312296
try await self.connect(domValues: domValues, redirect: true)
313297
}
314-
315-
self.internalState = .connected
316298
}
317299
}
318300

Sources/LiveViewNative/LiveView.swift

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import Foundation
99
import SwiftUI
1010
import Combine
1111

12-
#if swift(>=5.9)
1312
/// Create a ``LiveView`` with a list of addons.
1413
///
1514
/// Use this macro to automatically register any addons.
@@ -27,7 +26,6 @@ public macro LiveView<Host: LiveViewHost>(
2726
configuration: LiveSessionConfiguration = .init(),
2827
addons: [any CustomRegistry<EmptyRegistry>.Type]
2928
) -> AnyView = #externalMacro(module: "LiveViewNativeMacros", type: "LiveViewMacro")
30-
#endif
3129

3230
/// The SwiftUI root view for a Phoenix LiveView.
3331
///
@@ -87,15 +85,11 @@ public struct LiveView<R: RootRegistry>: View {
8785
fatalError()
8886
case .notConnected:
8987
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) {
90-
#if swift(>=5.9)
9188
SwiftUI.ContentUnavailableView {
9289
SwiftUI.Label("No Connection", systemImage: "network.slash")
9390
} description: {
9491
SwiftUI.Text("The app will reconnect when network connection is regained.")
9592
}
96-
#else
97-
SwiftUI.Text("Not Connected")
98-
#endif
9993
}
10094
case .connecting:
10195
SwiftUI.ProgressView("Connecting")
@@ -106,22 +100,19 @@ public struct LiveView<R: RootRegistry>: View {
106100
ErrorView<R>(html: trace)
107101
} else {
108102
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) {
109-
#if swift(>=5.9)
110103
SwiftUI.ContentUnavailableView {
111104
SwiftUI.Label("No Connection", systemImage: "network.slash")
112105
} description: {
113-
SwiftUI.Text("The app will reconnect when network connection is regained.")
114-
}
115-
#else
116-
SwiftUI.VStack {
117-
SwiftUI.Text("Connection Failed")
118-
.font(.subheadline)
106+
#if DEBUG
119107
SwiftUI.Text(error.localizedDescription)
108+
.monospaced()
109+
#else
110+
SwiftUI.Text("The app will reconnect when network connection is regained.")
111+
#endif
120112
}
121-
#endif
122113
} else {
123114
SwiftUI.VStack {
124-
SwiftUI.Text("Connection Failed")
115+
SwiftUI.Text("No Connection")
125116
.font(.subheadline)
126117
SwiftUI.Text(error.localizedDescription)
127118
}

Sources/LiveViewNative/NavStackEntryView.swift

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,17 @@ struct NavStackEntryView<R: RootRegistry>: View {
3333
private var elementTree: some View {
3434
SwiftUI.Group {
3535
if coordinator.url == entry.url {
36-
switch coordinator.state {
37-
case .connected:
38-
coordinator.builder.fromNodes(coordinator.document![coordinator.document!.root()].children(), coordinator: coordinator, url: coordinator.url)
39-
.environment(\.coordinatorEnvironment, CoordinatorEnvironment(coordinator, document: coordinator.document!))
36+
if coordinator.state.isConnected || coordinator.state.isPending,
37+
let document = coordinator.document
38+
{
39+
coordinator.builder.fromNodes(document[document.root()].children(), coordinator: coordinator, url: coordinator.url)
40+
.environment(\.coordinatorEnvironment, CoordinatorEnvironment(coordinator, document: document))
41+
.disabled(coordinator.state.isPending)
4042
.transition(coordinator.session.configuration.transition ?? .identity)
41-
default:
43+
.id(ObjectIdentifier(document))
44+
} else {
4245
SwiftUI.Group {
43-
if coordinator.state.isPending,
44-
let document = coordinator.document
45-
{
46-
coordinator.builder.fromNodes(document[document.root()].children(), coordinator: coordinator, url: coordinator.url)
47-
.environment(\.coordinatorEnvironment, CoordinatorEnvironment(coordinator, document: document))
48-
.disabled(true)
49-
} else if R.LoadingView.self == Never.self {
46+
if R.LoadingView.self == Never.self {
5047
switch coordinator.state {
5148
case .connected:
5249
fatalError()
@@ -69,12 +66,6 @@ struct NavStackEntryView<R: RootRegistry>: View {
6966
}
7067
}
7168
}
72-
.animation(coordinator.session.configuration.transition.map({ _ in .default }), value: { () -> Bool in
73-
if case .connected = coordinator.state {
74-
return true
75-
} else {
76-
return false
77-
}
78-
}())
69+
.animation(coordinator.session.configuration.transition.map({ _ in .default }), value: coordinator.state.isConnected)
7970
}
8071
}

Sources/LiveViewNative/Property Wrappers/Event.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,29 @@ public struct Event: DynamicProperty, Decodable {
4949
@Environment(\.coordinatorEnvironment) private var coordinatorEnvironment
5050
@StateObject var handler = Handler()
5151
/// The name of the event to send.
52-
#if swift(>=5.8)
5352
@_documentation(visibility: public)
54-
#endif
5553
private let event: String?
5654
private let name: AttributeName?
5755
/// The type of event, such as `click` or `focus`.
58-
#if swift(>=5.8)
5956
@_documentation(visibility: public)
60-
#endif
6157
private let type: String
6258
/// The target `LiveView` or `LiveComponent`.
6359
///
6460
/// Pass `@myself` in a component to send the event to the component instead of its parent `LiveView`.
65-
#if swift(>=5.8)
6661
@_documentation(visibility: public)
67-
#endif
6862
private let target: Int?
6963
/// A duration in milliseconds for the debounce interval.
7064
///
7165
/// - Note: ``debounce`` takes precedence over ``throttle``.
72-
#if swift(>=5.8)
7366
@_documentation(visibility: public)
74-
#endif
7567
private let debounce: Double?
7668
/// A duration in milliseconds for the throttle interval.
7769
///
7870
/// - Note: ``debounce`` takes precedence over ``throttle``.
79-
#if swift(>=5.8)
8071
@_documentation(visibility: public)
81-
#endif
8272
private let throttle: Double?
8373
/// Custom values to send with the event.
84-
#if swift(>=5.8)
8574
@_documentation(visibility: public)
86-
#endif
8775
private let params: Any?
8876

8977
@Attribute("phx-debounce") private var debounceAttribute: Double?

Sources/LiveViewNative/Registries/AggregateRegistry.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import Foundation
99
import SwiftUI
1010
import LiveViewNativeStylesheet
1111

12-
#if swift(>=5.9)
1312
/// A macro that combines multiple registries together.
1413
///
1514
/// ```swift
@@ -23,7 +22,6 @@ import LiveViewNativeStylesheet
2322
/// ```
2423
@freestanding(declaration, names: named(Registries))
2524
public macro Registries() = #externalMacro(module: "LiveViewNativeMacros", type: "RegistriesMacro")
26-
#endif
2725

2826
/// An aggregate registry combines multiple other registries together, allowing you to use tags/modifiers declared by any of them.
2927
///

Sources/LiveViewNative/Utils/Font.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ import LiveViewNativeCore
2222
/// * `footnote`
2323
/// * `caption`
2424
/// * `caption2`
25-
#if swift(>=5.8)
2625
@_documentation(visibility: public)
27-
#endif
2826
extension Font.TextStyle: AttributeDecodable {
2927
public init(from attribute: LiveViewNativeCore.Attribute?) throws {
3028
guard let value = attribute?.value

Sources/LiveViewNative/Views/Accessibility/ScaledMetric.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,22 @@ import SwiftUI
3636
///
3737
/// ## Bindings
3838
/// * ``scaledValue``
39-
#if swift(>=5.8)
4039
@_documentation(visibility: public)
41-
#endif
4240
struct ScaledMetric<R: RootRegistry>: View {
4341
@ObservedElement private var element: ElementNode
4442
@LiveContext<R> private var context
4543

4644
/// The event to update with the scaled ``value``.
47-
#if swift(>=5.8)
4845
@_documentation(visibility: public)
49-
#endif
5046
@Event("phx-change", type: "click") private var onChange
5147

5248
/// The initial value to scale.
53-
#if swift(>=5.8)
5449
@_documentation(visibility: public)
55-
#endif
5650
@Attribute("value") private var value: Double
5751

5852
/// The ``LiveViewNative/SwiftUI/Font/TextStyle`` to scale with.
5953
/// Defaults to `body`.
60-
#if swift(>=5.8)
6154
@_documentation(visibility: public)
62-
#endif
6355
@Attribute("relative-to") private var relativeStyle: Font.TextStyle = .body
6456

6557
var body: some View {

Sources/LiveViewNative/Views/Controls and Indicators/Buttons/Button.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ import SwiftUI
2626
///
2727
/// ## Events
2828
/// * ``click``
29-
#if swift(>=5.8)
3029
@_documentation(visibility: public)
31-
#endif
3230
@_spi(LiveForm)
3331
public struct Button<R: RootRegistry>: View {
3432
@ObservedElement private var element: ElementNode
@@ -37,15 +35,11 @@ public struct Button<R: RootRegistry>: View {
3735
private let action: (() -> Void)?
3836

3937
/// Event triggered when tapped.
40-
#if swift(>=5.8)
4138
@_documentation(visibility: public)
42-
#endif
4339
@Event("phx-click", type: "click") private var click
4440

4541
/// Boolean attribute that indicates whether the button is tappable.
46-
#if swift(>=5.8)
4742
@_documentation(visibility: public)
48-
#endif
4943
@Attribute("disabled") private var disabled: Bool
5044

5145
@_spi(LiveForm) public init(action: (() -> Void)? = nil) {

0 commit comments

Comments
 (0)