Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Render transclude blocks in MemoDetailViewer #574

Merged
merged 23 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1958,6 +1958,7 @@ struct AppEnvironment {
var database: DatabaseService
var data: DataService
var feed: FeedService
var transclude: TranscludeService

var recoveryPhrase: RecoveryPhraseEnvironment = RecoveryPhraseEnvironment()

Expand Down Expand Up @@ -2045,6 +2046,7 @@ struct AppEnvironment {

self.feed = FeedService()
self.gatewayProvisioningService = GatewayProvisioningService()
self.transclude = TranscludeService(database: database, noosphere: noosphere)
}
}

36 changes: 34 additions & 2 deletions xcode/Subconscious/Shared/Components/Common/SubtextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,37 @@ import SwiftUI
struct SubtextView: View {
private static var renderer = SubtextAttributedStringRenderer()
var subtext: Subtext
var transcludePreviews: [Slashlink: EntryStub]
var onViewTransclude: (Slashlink) -> Void

private func entries(for block: Subtext.Block) -> [EntryStub] {
block.slashlinks
.compactMap { link in
guard let slashlink = link.toSlashlink() else {
return nil
}

return transcludePreviews[slashlink]
}
.filter { entry in
// Avoid empty transclude blocks
entry.excerpt.count > 0
}
}

var body: some View {
VStack(alignment: .leading) {
ForEach(subtext.blocks, id: \.self) { block in
Text(Self.renderer.render(block.description))
ForEach(self.entries(for: block), id: \.self) { entry in
Transclude2View(
address: entry.address,
excerpt: entry.excerpt,
action: {
onViewTransclude(entry.address)
}
)
}
}
}
.expandAlignedLeading()
Expand Down Expand Up @@ -45,13 +71,19 @@ struct SubtextView_Previews: PreviewProvider {

Brief were my days among you, and briefer still the words I have spoken.

But should my voice fade in your ears, and my love vanish in your memory, then I will come again,
But should my /voice fade in your ears, and my love vanish in your /memory, then I will come again,

And with a richer heart and lips more yielding to the spirit will I speak.

Yea, I shall return with the tide,
"""
)
),
transcludePreviews: [
Slashlink("/wanderer-your-footsteps-are-the-road")!: EntryStub(address: Slashlink("/wanderer-your-footsteps-are-the-road")!, excerpt: "hello mother", modified: Date.now),
Slashlink("/voice")!: EntryStub(address: Slashlink("/voice")!, excerpt: "hello father", modified: Date.now),
Slashlink("/memory")!: EntryStub(address: Slashlink("/memory")!, excerpt: "hello world", modified: Date.now)
],
onViewTransclude: { _ in }
)
}
}
Expand Down
139 changes: 135 additions & 4 deletions xcode/Subconscious/Shared/Components/Detail/MemoViewerDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct MemoViewerDetailView: View {
MemoViewerDetailLoadedView(
title: store.state.title,
dom: store.state.dom,
transcludePreviews: store.state.transcludePreviews,
address: description.address,
backlinks: store.state.backlinks,
send: store.send,
Expand Down Expand Up @@ -127,6 +128,7 @@ struct MemoViewerDetailLoadingView: View {
struct MemoViewerDetailLoadedView: View {
var title: String
var dom: Subtext
var transcludePreviews: [Slashlink: EntryStub]
var address: Slashlink
var backlinks: [EntryStub]
var send: (MemoViewerDetailAction) -> Void
Expand Down Expand Up @@ -158,14 +160,27 @@ struct MemoViewerDetailLoadedView: View {
)
return .handled
}

private func onViewTransclude(
address: Slashlink
) {
if address.isOurs {
notify(.requestDetail(.editor(MemoEditorDetailDescription(address: address))))
} else {
notify(.requestDetail(.viewer(MemoViewerDetailDescription(address: address))))
}
bfollington marked this conversation as resolved.
Show resolved Hide resolved
}


var body: some View {
GeometryReader { geometry in
ScrollView {
VStack {
VStack {
SubtextView(
subtext: dom
subtext: dom,
transcludePreviews: transcludePreviews,
onViewTransclude: self.onViewTransclude
).textSelection(
.enabled
).environment(\.openURL, OpenURLAction { url in
Expand Down Expand Up @@ -216,6 +231,14 @@ enum MemoViewerDetailAction: Hashable {
case failLoadDetail(_ message: String)
case presentMetaSheet(_ isPresented: Bool)

case fetchTranscludePreviews
case succeedFetchTranscludePreviews([Slashlink: EntryStub])
case failFetchTranscludePreviews(_ error: String)

case fetchOwnerProfile
case succeedFetchOwnerProfile(UserProfile)
case failFetchOwnerProfile(_ error: String)

/// Synonym for `.metaSheet(.setAddress(_))`
static func setMetaSheetAddress(_ address: Slashlink) -> Self {
.metaSheet(.setAddress(address))
Expand Down Expand Up @@ -245,6 +268,7 @@ struct MemoViewerDetailModel: ModelProtocol {

var loadingState = LoadingState.loading

var owner: UserProfile?
var address: Slashlink?
var defaultAudience = Audience.local
var title = ""
Expand All @@ -255,6 +279,10 @@ struct MemoViewerDetailModel: ModelProtocol {
var isMetaSheetPresented = false
var metaSheet = MemoViewerDetailMetaSheetModel()

var transcludePreviews: [Slashlink: EntryStub] = [:]



static func update(
state: Self,
action: Action,
Expand Down Expand Up @@ -294,6 +322,36 @@ struct MemoViewerDetailModel: ModelProtocol {
environment: environment,
isPresented: isPresented
)
case .fetchTranscludePreviews:
return fetchTranscludePreviews(
state: state,
environment: environment
)
case .succeedFetchTranscludePreviews(let transcludes):
var model = state
model.transcludePreviews = transcludes
return Update(state: model)

case .failFetchTranscludePreviews(let error):
logger.error("Failed to fetch transcludes: \(error)")
return Update(state: state)

case .fetchOwnerProfile:
return fetchOwnerProfile(
state: state,
environment: environment
)
case .succeedFetchOwnerProfile(let profile):
var model = state
model.owner = profile
return update(
state: model,
action: .fetchTranscludePreviews,
environment: environment
)
case .failFetchOwnerProfile(let error):
logger.error("Failed to fetch owner: \(error)")
return Update(state: state)
}
}

Expand All @@ -314,7 +372,10 @@ struct MemoViewerDetailModel: ModelProtocol {
return update(
state: model,
// Set meta sheet address as well
action: .setMetaSheetAddress(description.address),
actions: [
.setMetaSheetAddress(description.address),
.fetchOwnerProfile
],
environment: environment
).mergeFx(fx)
}
Expand Down Expand Up @@ -354,7 +415,68 @@ struct MemoViewerDetailModel: ModelProtocol {
) -> Update<Self> {
var model = state
model.dom = dom
return Update(state: model)
return update(
state: model,
action: .fetchTranscludePreviews,
environment: environment
)
}

static func fetchTranscludePreviews(
state: MemoViewerDetailModel,
environment: MemoViewerDetailModel.Environment
) -> Update<MemoViewerDetailModel> {

guard let owner = state.owner else {
return Update(state: state)
bfollington marked this conversation as resolved.
Show resolved Hide resolved
}

let links = state.dom.slashlinks
.map { value in value.toSlashlink() }
.compactMap { value in value }
bfollington marked this conversation as resolved.
Show resolved Hide resolved

let fx: Fx<MemoViewerDetailAction> =
environment.transclude
bfollington marked this conversation as resolved.
Show resolved Hide resolved
.fetchTranscludePreviewsPublisher(slashlinks: links, owner: owner)
.map { entries in
MemoViewerDetailAction.succeedFetchTranscludePreviews(entries)
}
.recover { error in
MemoViewerDetailAction.failFetchTranscludePreviews(error.localizedDescription)
}
.eraseToAnyPublisher()

return Update(state: state, fx: fx)
}

static func fetchOwnerProfile(
state: MemoViewerDetailModel,
environment: MemoViewerDetailModel.Environment
) -> Update<MemoViewerDetailModel> {
let fx: Fx<MemoViewerDetailAction> =
Future.detached {
guard let petname = state.address?.toPetname() else {
return try await environment
.userProfile
.requestOurProfile()
.profile

}

return try await environment
.userProfile
.requestUserProfile(petname: petname)
.profile
}
.map { profile in
.succeedFetchOwnerProfile(profile)
}
.recover { error in
.failFetchOwnerProfile(error.localizedDescription)
}
.eraseToAnyPublisher()

return Update(state: state, fx: fx)
}

static func presentMetaSheet(
Expand Down Expand Up @@ -403,13 +525,22 @@ struct MemoViewerDetailView_Previews: PreviewProvider {

Say not, "I have found the path of the soul." Say rather, "I have met the soul walking upon my path."

For the soul walks upon all paths. /infinity-paths
For the soul walks upon all paths.

/infinity-paths

The soul walks not upon a line, neither does it grow like a reed.

The soul unfolds itself, like a [[lotus]] of countless petals.
"""
),
transcludePreviews: [
Slashlink("/infinity-paths")!: EntryStub(
address: Slashlink("/infinity-paths")!,
excerpt: "Say not, \"I have discovered the soul's destination,\" but rather, \"I have glimpsed the soul's journey, ever unfolding along the way.\"",
bfollington marked this conversation as resolved.
Show resolved Hide resolved
modified: Date.now
)
],
address: Slashlink(slug: Slug("truth-the-prophet")!),
backlinks: [],
send: { action in },
Expand Down
4 changes: 2 additions & 2 deletions xcode/Subconscious/Shared/Library/Func.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ struct Func {
powf(2.0, Float(attempts))
)
)
sleep(seconds)
try await Task.sleep(for: .seconds(seconds))

Comment on lines 79 to +81
Copy link
Collaborator Author

@bfollington bfollington Jun 26, 2023

Choose a reason for hiding this comment

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

Not related to this PR but I realised this was causing blocking issues while waiting for the gateway to provision.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Namely, it was causing this issue #710

Copy link
Collaborator

Choose a reason for hiding this comment

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

😅

return try await self.retryWithBackoff(
maxAttempts: maxAttempts,
maxWaitSeconds: maxWaitSeconds,
Expand Down
9 changes: 9 additions & 0 deletions xcode/Subconscious/Shared/Models/Slashlink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ extension Slashlink {
return self
}
}

func relativizeIfNeeded(petname base: Petname?) -> Slashlink {
switch self.peer {
case .petname(let name) where name == base:
return Slashlink(slug: self.slug)
default:
return self
}
}
Comment on lines +228 to +235
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similar to the DID version, drop the peer from the address if it matches the base.


/// Get petname from slashlink (if any)
func toPetname() -> Petname? {
Expand Down
47 changes: 44 additions & 3 deletions xcode/Subconscious/Shared/Services/DatabaseService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,49 @@ final class DatabaseService {
return results.col(0)?.toInt()
}

func listEntries(for slashlinks: [Slashlink], owner: Petname?) throws -> [EntryStub] {
return try slashlinks.compactMap({ slashlink in
return try readEntry(for: slashlink, owner: owner)
})
}

func readEntry(for slashlink: Slashlink, owner: Petname?) throws -> EntryStub? {
bfollington marked this conversation as resolved.
Show resolved Hide resolved
guard self.state == .ready else {
throw DatabaseServiceError.notReady
}

let results = try database.execute(
sql: """
SELECT slashlink, modified, excerpt
bfollington marked this conversation as resolved.
Show resolved Hide resolved
FROM memo
WHERE slashlink = ?
ORDER BY modified DESC
LIMIT 1000
""",
parameters: [
.text(slashlink.markup),
]
)

return results.compactMap({ row in
guard
let address = row.col(0)?
.toString()?
.toSlashlink(),
let modified = row.col(1)?.toDate(),
let excerpt = row.col(2)?.toString()
else {
return nil
}
return EntryStub(
address: address,
excerpt: excerpt,
modified: modified
)
})
.first
}

/// List recent entries
func listRecentMemos(owner: Did?) throws -> [EntryStub] {
guard self.state == .ready else {
Expand Down Expand Up @@ -462,9 +505,7 @@ final class DatabaseService {
guard
let address = row.col(0)?
.toString()?
.toLink()?
.toSlashlink()?
.relativizeIfNeeded(did: owner),
.toSlashlink(),
let modified = row.col(1)?.toDate(),
let excerpt = row.col(2)?.toString()
else {
Expand Down
Loading
Loading