Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions WordPress/Classes/Services/BlockEditorCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ final actor BlockEditorCache {
try settings.write(to: fileURL)
}

func getBlockSettings(for blogID: String) throws -> Data? {
func getBlockSettings(for blogID: String) -> Data? {
let fileURL = makeBlockSettingsURL(for: blogID)

guard FileManager.default.fileExists(atPath: fileURL.path) else {
return nil
}

return try Data(contentsOf: fileURL)
do {
return try Data(contentsOf: fileURL)
} catch {
wpAssertionFailure("BlockEditorCache failed to read cached data", userInfo: ["error": "\(error)"])
return nil
}
}

func deleteBlockSettings(for blogID: String) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class RawBlockEditorSettingsService {
/// the network.
func getSettings(allowingCachedResponse: Bool = true) async throws -> Data {
// Return cached settings if available
if allowingCachedResponse, let cachedSettings = try await BlockEditorCache.shared.getBlockSettings(for: blogID) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If local cache read using try Data(contentsOf: fileURL) were ever to fail, it would prevent fetchSettingsFromAPI from running. The cache lookup should not stop the request.

if allowingCachedResponse, let cachedSettings = await BlockEditorCache.shared.getBlockSettings(for: blogID) {
return cachedSettings
}
return try await fetchSettingsFromAPI()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,6 @@ import WebKit
import CocoaLumberjackSwift

class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor {

enum EditorLoadingState {
/// We haven't done anything with the editor yet
///
/// Valid states to transition to:
/// - .loadingDependencies
case uninitialized

/// We're loading the editor's dependencies
///
/// Valid states to transition to:
/// - .loadingCancelled
/// - .dependencyError
/// - .dependenciesReady
case loadingDependencies(_ task: Task<Void, Error>)

/// We cancelled loading the editor's dependencies
///
/// Valid states to transition to:
/// - .loadingDependencies
case loadingCancelled

/// An error occured while fetching dependencies
///
/// Valid states to transition to:
/// - .loadingDependencies
case dependencyError(Error)

/// All dependencies have been loaded, so we're ready to start the editor
///
/// Valid states to transition to:
/// - .ready
case dependenciesReady(EditorDependencies)

/// The editor is fully loaded and we've passed all required configuration and data to it
///
/// There are no valid transition states from `.started`
case started
}

struct EditorDependencies {
let settings: String?
let didLoadCookies: Bool
Expand Down Expand Up @@ -125,9 +85,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
private var suggestionViewBottomConstraint: NSLayoutConstraint?
private var currentSuggestionsController: GutenbergSuggestionsViewController?

private var editorState: EditorLoadingState = .uninitialized
private var dependencyLoadingError: Error?
private var editorLoadingTask: Task<Void, Error>?
private var editorLoadingTask: Task<Void, Never>?

// TODO: remove (none of these APIs are needed for the new editor)
func prepopulateMediaItems(_ media: [Media]) {}
Expand Down Expand Up @@ -215,8 +173,6 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
configureNavigationBar()
refreshInterface()

startLoadingDependencies()

SiteSuggestionService.shared.prefetchSuggestionsIfNeeded(for: post.blog) {
// Do nothing
}
Expand All @@ -228,56 +184,16 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
// DDLogError("Error syncing JETPACK: \(String(describing: error))")
// })

loadEditor()
onViewDidLoad()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

if case .loadingDependencies = self.editorState {
self.showActivityIndicator()
}

if case .loadingCancelled = self.editorState {
startLoadingDependencies()
}
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

if case .loadingCancelled = self.editorState {
preconditionFailure("Dependency loading should not be cancelled")
}

self.editorLoadingTask = Task { [weak self] in
guard let self else { return }
do {
while case .loadingDependencies = self.editorState {
try await Task.sleep(nanoseconds: 1000)
}

switch self.editorState {
case .uninitialized: preconditionFailure("Dependencies must be initialized")
case .loadingDependencies: preconditionFailure("Dependencies should not still be loading")
case .loadingCancelled: preconditionFailure("Dependency loading should not be cancelled")
case .dependencyError(let error): self.showEditorError(error)
case .dependenciesReady(let dependencies): try await self.startEditor(settings: dependencies.settings)
case .started: preconditionFailure("The editor should not already be started")
}
} catch {
self.showEditorError(error)
}
}
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if case .loadingDependencies(let task) = self.editorState {
task.cancel()
}

self.editorLoadingTask?.cancel()
if isBeingDismissedDirectlyOrByAncestor() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The easiest way to verify it works is by setting a breakpoint.

editorLoadingTask?.cancel()
}
}

private func setupEditorView() {
Expand All @@ -296,7 +212,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
setContentScrollView(editorViewController.webView.scrollView)
}

// MARK: - Functions
// MARK: - Helpers

private func configureNavigationBar() {
navigationController?.navigationBar.accessibilityIdentifier = "Gutenberg Editor Navigation Bar"
Expand Down Expand Up @@ -361,51 +277,6 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
}
}

func startLoadingDependencies() {
switch self.editorState {
case .uninitialized:
break // This is fine – we're loading for the first time
case .loadingDependencies:
preconditionFailure("`startLoadingDependencies` should not be called while in the `.loadingDependencies` state")
case .loadingCancelled:
break // This is fine – we're loading after quickly switching posts
case .dependencyError:
break // We're retrying after an error
case .dependenciesReady:
preconditionFailure("`startLoadingDependencies` should not be called while in the `.dependenciesReady` state")
case .started:
preconditionFailure("`startLoadingDependencies` should not be called while in the `.started` state")
}

self.editorState = .loadingDependencies(Task {
do {
let dependencies = try await fetchEditorDependencies()
self.editorState = .dependenciesReady(dependencies)
} catch {
self.editorState = .dependencyError(error)
}
})
}

@MainActor
func startEditor(settings: String?) async throws {
guard case .dependenciesReady = self.editorState else {
preconditionFailure("`startEditor` should only be called when the editor is in the `.dependenciesReady` state.")
}

let updatedConfiguration = self.editorViewController.configuration.toBuilder()
.apply(settings) { $0.setEditorSettings($1) }
.setTitle(post.postTitle ?? "")
.setContent(post.content ?? "")
.build()

self.editorViewController.updateConfiguration(updatedConfiguration)
self.editorViewController.startEditorSetup()

// Handles refreshing controls with state context after options screen is dismissed
editorContentWasUpdated()
}

// MARK: - Keyboard Observers

private func setupKeyboardObservers() {
Expand Down Expand Up @@ -473,7 +344,36 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
}

// MARK: - Editor Setup
private func fetchEditorDependencies() async throws -> EditorDependencies {

private func loadEditor() {
editorLoadingTask = Task { @MainActor in
await actuallyLoadEditor()
}
}

@MainActor
private func actuallyLoadEditor() async {
showActivityIndicator()

let dependencies = await fetchEditorDependencies()
startEditor(dependencies: dependencies)
}

private func startEditor(dependencies: EditorDependencies) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this with no changes.

let configuration = editorViewController.configuration.toBuilder()
.apply(dependencies.settings) { $0.setEditorSettings($1) }
.setTitle(post.postTitle ?? "")
.setContent(post.content ?? "")
.build()

editorViewController.updateConfiguration(configuration)
editorViewController.startEditorSetup()

// Handles refreshing controls with state context after options screen is dismissed
editorContentWasUpdated()
}

private func fetchEditorDependencies() async -> EditorDependencies {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not actually throw any errors – removing throws.

let settings: String?
do {
settings = try await blockEditorSettingsService.getSettingsString(allowingCachedResponse: true)
Expand Down Expand Up @@ -527,7 +427,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate
// is still reflecting the actual startup time of the editor
editorSession.start()
}
self.hideActivityIndicator()
hideActivityIndicator()
}

func editor(_ viewContoller: GutenbergKit.EditorViewController, didDisplayInitialContent content: String) {
Expand Down