tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
ssr mode works properly now
Orual
2 months ago
17cf62c2
ec7d916e
+590
-424
10 changed files
expand all
collapse all
unified
split
crates
weaver-app
src
blobcache.rs
components
entry.rs
identity.rs
mod.rs
data.rs
fetch.rs
main.rs
views
home.rs
navbar.rs
notebook.rs
+22
-31
crates/weaver-app/src/blobcache.rs
···
42
42
}
43
43
AtIdentifier::Handle(handle) => self.client.pds_for_handle(&handle).await?,
44
44
};
45
45
-
// let blob = if let Ok(blob_stream) = self
46
46
-
// .client
47
47
-
// .xrpc(pds_url)
48
48
-
// .send(
49
49
-
// &GetBlob::new()
50
50
-
// .cid(cid.clone())
51
51
-
// .did(repo_did.clone())
52
52
-
// .build(),
53
53
-
// )
54
54
-
// .await
55
55
-
// {
56
56
-
// blob_stream.buffer().clone()
57
57
-
// } else {
58
58
-
// reqwest::get(format!(
59
59
-
// "https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg",
60
60
-
// repo_did, cid
61
61
-
// ))
62
62
-
// .await?
63
63
-
// .bytes()
64
64
-
// .await?
65
65
-
// .clone()
66
66
-
// };
67
67
-
//
68
68
-
let blob = reqwest::get(format!(
69
69
-
"https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg",
70
70
-
repo_did, cid
71
71
-
))
72
72
-
.await?
73
73
-
.bytes()
74
74
-
.await?
75
75
-
.clone();
45
45
+
let blob = if let Ok(blob_stream) = self
46
46
+
.client
47
47
+
.xrpc(pds_url)
48
48
+
.send(
49
49
+
&GetBlob::new()
50
50
+
.cid(cid.clone())
51
51
+
.did(repo_did.clone())
52
52
+
.build(),
53
53
+
)
54
54
+
.await
55
55
+
{
56
56
+
blob_stream.buffer().clone()
57
57
+
} else {
58
58
+
reqwest::get(format!(
59
59
+
"https://cdn.bsky.app/img/feed_fullsize/plain/{}/{}@jpeg",
60
60
+
repo_did, cid
61
61
+
))
62
62
+
.await?
63
63
+
.bytes()
64
64
+
.await?
65
65
+
.clone()
66
66
+
};
76
67
77
68
self.cache.insert(cid.clone(), blob);
78
69
if let Some(name) = name {
+32
-25
crates/weaver-app/src/components/entry.rs
···
24
24
use std::sync::Arc;
25
25
use weaver_api::sh_weaver::notebook::{BookEntryView, EntryView, entry};
26
26
27
27
+
// #[component]
28
28
+
// pub fn EntryPage(
29
29
+
// ident: ReadSignal<AtIdentifier<'static>>,
30
30
+
// book_title: ReadSignal<SmolStr>,
31
31
+
// title: ReadSignal<SmolStr>,
32
32
+
// ) -> Element {
33
33
+
// rsx! {
34
34
+
// {std::iter::once(rsx! {Entry {ident, book_title, title}})}
35
35
+
// }
36
36
+
// }
37
37
+
27
38
#[component]
28
39
pub fn EntryPage(
29
40
ident: ReadSignal<AtIdentifier<'static>>,
30
41
book_title: ReadSignal<SmolStr>,
31
42
title: ReadSignal<SmolStr>,
32
43
) -> Element {
33
33
-
tracing::debug!(
34
34
-
"EntryPage component rendering for ident: {:?}, book: {}, title: {}",
35
35
-
ident(),
36
36
-
book_title(),
37
37
-
title()
38
38
-
);
39
39
-
rsx! {
40
40
-
{std::iter::once(rsx! {Entry {ident, book_title, title}})}
41
41
-
}
42
42
-
}
44
44
+
// Use feature-gated hook for SSR support
45
45
+
let (entry_res, entry) = crate::data::use_entry_data(ident, book_title, title);
46
46
+
let route = use_route::<Route>();
47
47
+
let mut last_route = use_signal(|| route.clone());
43
48
44
44
-
#[component]
45
45
-
pub fn Entry(
46
46
-
ident: ReadSignal<AtIdentifier<'static>>,
47
47
-
book_title: ReadSignal<SmolStr>,
48
48
-
title: ReadSignal<SmolStr>,
49
49
-
) -> Element {
50
50
-
tracing::debug!(
51
51
-
"Entry component rendering for ident: {:?}, book: {}, title: {}",
52
52
-
ident(),
53
53
-
book_title(),
54
54
-
title()
55
55
-
);
56
56
-
// Use feature-gated hook for SSR support
57
57
-
let entry = crate::data::use_entry_data(ident, book_title, title);
49
49
+
#[cfg(all(
50
50
+
target_family = "wasm",
51
51
+
target_os = "unknown",
52
52
+
not(feature = "fullstack-server")
53
53
+
))]
58
54
let fetcher = use_context::<crate::fetch::Fetcher>();
59
59
-
tracing::debug!("Entry component got entry data");
55
55
+
56
56
+
// Suspend SSR until entry loads
57
57
+
#[cfg(feature = "fullstack-server")]
58
58
+
let mut entry_res = entry_res?;
59
59
+
60
60
+
#[cfg(feature = "fullstack-server")]
61
61
+
use_effect(use_reactive!(|route| {
62
62
+
if route != last_route() {
63
63
+
entry_res.restart();
64
64
+
last_route.set(route.clone());
65
65
+
}
66
66
+
}));
60
67
61
68
// Handle blob caching when entry data is available
62
69
match &*entry.read() {
+9
-2
crates/weaver-app/src/components/identity.rs
···
26
26
ident()
27
27
);
28
28
use crate::components::ProfileDisplay;
29
29
-
let notebooks = data::use_notebooks_for_did(ident);
30
30
-
let profile = crate::data::use_profile_data(ident);
29
29
+
let (notebooks_result, notebooks) = data::use_notebooks_for_did(ident);
30
30
+
let (profile_result, profile) = crate::data::use_profile_data(ident);
31
31
tracing::debug!("RepositoryIndex got profile and notebooks");
32
32
+
33
33
+
#[cfg(feature = "fullstack-server")]
34
34
+
notebooks_result?;
35
35
+
36
36
+
#[cfg(feature = "fullstack-server")]
37
37
+
profile_result?;
38
38
+
32
39
rsx! {
33
40
document::Stylesheet { href: NOTEBOOK_CARD_CSS }
34
41
+1
-1
crates/weaver-app/src/components/mod.rs
···
7
7
8
8
mod entry;
9
9
#[allow(unused_imports)]
10
10
-
pub use entry::{Entry, EntryCard, EntryMarkdown, EntryPage};
10
10
+
pub use entry::{EntryCard, EntryMarkdown, EntryPage};
11
11
12
12
pub mod identity;
13
13
#[allow(unused_imports)]
+215
-86
crates/weaver-app/src/data.rs
···
36
36
ident: ReadSignal<AtIdentifier<'static>>,
37
37
book_title: ReadSignal<SmolStr>,
38
38
title: ReadSignal<SmolStr>,
39
39
-
) -> Memo<Option<(BookEntryView<'static>, Entry<'static>)>> {
39
39
+
) -> (
40
40
+
Result<Resource<Option<(serde_json::Value, serde_json::Value)>>, RenderError>,
41
41
+
Memo<Option<(BookEntryView<'static>, Entry<'static>)>>,
42
42
+
) {
40
43
let fetcher = use_context::<crate::fetch::Fetcher>();
41
44
let fetcher = fetcher.clone();
42
42
-
let res = use_server_future(move || {
45
45
+
let res = use_server_future(use_reactive!(|(ident, book_title, title)| {
43
46
let fetcher = fetcher.clone();
44
47
async move {
45
48
if let Some(entry) = fetcher
···
51
54
let (_book_entry_view, entry_record) = (&entry.0, &entry.1);
52
55
if let Some(embeds) = &entry_record.embeds {
53
56
if let Some(images) = &embeds.images {
54
54
-
let ident_val = ident();
57
57
+
let ident_val = ident.clone();
55
58
let images = images.clone();
56
59
for image in &images.images {
57
60
use jacquard::smol_str::ToSmolStr;
···
75
78
None
76
79
}
77
80
}
78
78
-
});
79
79
-
use_memo(use_reactive!(|res| {
80
80
-
let res = res.ok()?;
81
81
+
}));
82
82
+
let memo = use_memo(use_reactive!(|res| {
83
83
+
let res = res.as_ref().ok()?;
81
84
if let Some(Some((ev, e))) = &*res.read() {
82
85
use jacquard::from_json_value;
83
86
···
88
91
} else {
89
92
None
90
93
}
91
91
-
}))
94
94
+
}));
95
95
+
(res, memo)
92
96
}
93
97
/// Fetches entry data client-side only (no SSR).
94
98
#[cfg(not(feature = "fullstack-server"))]
···
96
100
ident: ReadSignal<AtIdentifier<'static>>,
97
101
book_title: ReadSignal<SmolStr>,
98
102
title: ReadSignal<SmolStr>,
99
99
-
) -> Memo<Option<(BookEntryView<'static>, Entry<'static>)>> {
103
103
+
) -> (
104
104
+
Resource<Option<(serde_json::Value, serde_json::Value)>>,
105
105
+
Memo<Option<(BookEntryView<'static>, Entry<'static>)>>,
106
106
+
) {
100
107
let fetcher = use_context::<crate::fetch::Fetcher>();
101
108
let fetcher = fetcher.clone();
102
109
let res = use_resource(move || {
···
136
143
}
137
144
}
138
145
});
139
139
-
use_memo(move || {
146
146
+
let memo = use_memo(move || {
140
147
if let Some(Some((ev, e))) = &*res.read() {
141
148
use jacquard::from_json_value;
142
149
···
147
154
} else {
148
155
None
149
156
}
157
157
+
});
158
158
+
(res, memo)
159
159
+
}
160
160
+
161
161
+
#[cfg(feature = "fullstack-server")]
162
162
+
pub fn use_get_handle(did: Did<'static>) -> Memo<AtIdentifier<'static>> {
163
163
+
let ident = use_signal(use_reactive!(|did| AtIdentifier::Did(did.clone())));
164
164
+
let old_ident = ident.read().clone();
165
165
+
let fetcher = use_context::<crate::fetch::Fetcher>();
166
166
+
let fetcher = fetcher.clone();
167
167
+
let res = use_resource(move || {
168
168
+
let client = fetcher.get_client();
169
169
+
let old_ident = old_ident.clone();
170
170
+
async move {
171
171
+
client
172
172
+
.resolve_ident_owned(&*ident.read())
173
173
+
.await
174
174
+
.map(|doc| {
175
175
+
doc.handles()
176
176
+
.first()
177
177
+
.map(|h| AtIdentifier::Handle(h.clone()).into_static())
178
178
+
})
179
179
+
.ok()
180
180
+
.flatten()
181
181
+
.unwrap_or(old_ident)
182
182
+
}
183
183
+
});
184
184
+
use_memo(move || {
185
185
+
if let Some(value) = &*res.read() {
186
186
+
value.clone()
187
187
+
} else {
188
188
+
ident.read().clone()
189
189
+
}
150
190
})
151
191
}
152
192
153
153
-
pub fn use_get_handle(did: Did<'static>) -> AtIdentifier<'static> {
193
193
+
#[cfg(not(feature = "fullstack-server"))]
194
194
+
pub fn use_get_handle(did: Did<'static>) -> Memo<AtIdentifier<'static>> {
154
195
let ident = use_signal(use_reactive!(|did| AtIdentifier::Did(did.clone())));
155
155
-
use_handle(ident.into()).read().clone()
196
196
+
let old_ident = ident.read().clone();
197
197
+
let fetcher = use_context::<crate::fetch::Fetcher>();
198
198
+
let fetcher = fetcher.clone();
199
199
+
let res = use_resource(move || {
200
200
+
let client = fetcher.get_client();
201
201
+
let old_ident = old_ident.clone();
202
202
+
async move {
203
203
+
client
204
204
+
.resolve_ident_owned(&*ident.read())
205
205
+
.await
206
206
+
.map(|doc| {
207
207
+
doc.handles()
208
208
+
.first()
209
209
+
.map(|h| AtIdentifier::Handle(h.clone()).into_static())
210
210
+
})
211
211
+
.ok()
212
212
+
.flatten()
213
213
+
.unwrap_or(old_ident)
214
214
+
}
215
215
+
});
216
216
+
use_memo(move || {
217
217
+
if let Some(value) = &*res.read() {
218
218
+
value.clone()
219
219
+
} else {
220
220
+
ident.read().clone()
221
221
+
}
222
222
+
})
156
223
}
157
224
158
225
#[cfg(feature = "fullstack-server")]
159
226
pub fn use_load_handle(
160
227
ident: Option<AtIdentifier<'static>>,
161
161
-
) -> ReadSignal<Option<AtIdentifier<'static>>> {
228
228
+
) -> (
229
229
+
Result<Resource<Option<SmolStr>>, RenderError>,
230
230
+
Memo<Option<AtIdentifier<'static>>>,
231
231
+
) {
162
232
let ident = use_signal(use_reactive!(|ident| ident.clone()));
163
233
let fetcher = use_context::<crate::fetch::Fetcher>();
164
234
let fetcher = fetcher.clone();
165
165
-
let res = use_server_future(move || {
235
235
+
let res = use_server_future(use_reactive!(|ident| {
166
236
let client = fetcher.get_client();
167
237
async move {
168
238
if let Some(ident) = &*ident.read() {
···
177
247
None
178
248
}
179
249
}
180
180
-
});
250
250
+
}));
181
251
182
182
-
use_memo(use_reactive!(|res| {
252
252
+
let memo = use_memo(use_reactive!(|res| {
183
253
if let Ok(res) = res {
184
254
if let Some(value) = &*res.read() {
185
255
if let Some(handle) = value {
···
193
263
} else {
194
264
ident.read().clone()
195
265
}
196
196
-
}))
197
197
-
.into()
266
266
+
}));
267
267
+
268
268
+
(res, memo)
198
269
}
199
270
200
271
#[cfg(not(feature = "fullstack-server"))]
201
272
pub fn use_load_handle(
202
273
ident: Option<AtIdentifier<'static>>,
203
203
-
) -> ReadSignal<Option<AtIdentifier<'static>>> {
274
274
+
) -> (
275
275
+
Resource<Option<AtIdentifier<'static>>>,
276
276
+
Memo<Option<AtIdentifier<'static>>>,
277
277
+
) {
204
278
let ident = use_signal(use_reactive!(|ident| ident.clone()));
205
279
let fetcher = use_context::<crate::fetch::Fetcher>();
206
280
let fetcher = fetcher.clone();
···
223
297
}
224
298
});
225
299
226
226
-
if let Ok(ident) = res.suspend() {
227
227
-
ident.into()
228
228
-
} else {
229
229
-
ident.into()
230
230
-
}
300
300
+
let memo = use_memo(move || {
301
301
+
if let Some(value) = &*res.read() {
302
302
+
value.clone()
303
303
+
} else {
304
304
+
ident.read().clone()
305
305
+
}
306
306
+
});
307
307
+
308
308
+
(res, memo)
231
309
}
232
310
#[cfg(not(feature = "fullstack-server"))]
233
233
-
pub fn use_handle(ident: ReadSignal<AtIdentifier<'static>>) -> ReadSignal<AtIdentifier<'static>> {
311
311
+
pub fn use_handle(
312
312
+
ident: ReadSignal<AtIdentifier<'static>>,
313
313
+
) -> (Resource<AtIdentifier<'static>>, Memo<AtIdentifier<'static>>) {
234
314
let old_ident = ident.read().clone();
235
315
let fetcher = use_context::<crate::fetch::Fetcher>();
236
316
let fetcher = fetcher.clone();
···
251
331
.unwrap_or(old_ident)
252
332
}
253
333
});
254
254
-
if let Ok(ident) = res.suspend() {
255
255
-
ident.into()
256
256
-
} else {
257
257
-
ident
258
258
-
}
334
334
+
335
335
+
let memo = use_memo(move || {
336
336
+
if let Some(value) = &*res.read() {
337
337
+
value.clone()
338
338
+
} else {
339
339
+
ident.read().clone()
340
340
+
}
341
341
+
});
342
342
+
343
343
+
(res, memo)
259
344
}
260
345
#[cfg(feature = "fullstack-server")]
261
261
-
pub fn use_handle(ident: ReadSignal<AtIdentifier<'static>>) -> ReadSignal<AtIdentifier<'static>> {
346
346
+
pub fn use_handle(
347
347
+
ident: ReadSignal<AtIdentifier<'static>>,
348
348
+
) -> (
349
349
+
Result<Resource<SmolStr>, RenderError>,
350
350
+
Memo<AtIdentifier<'static>>,
351
351
+
) {
262
352
let old_ident = ident.read().clone();
263
353
let fetcher = use_context::<crate::fetch::Fetcher>();
264
354
let fetcher = fetcher.clone();
265
265
-
let res = use_server_future(move || {
355
355
+
let res = use_server_future(use_reactive!(|ident| {
266
356
let client = fetcher.get_client();
267
357
let old_ident = old_ident.clone();
268
358
async move {
269
359
use jacquard::smol_str::ToSmolStr;
270
360
271
361
client
272
272
-
.resolve_ident_owned(&*ident.read())
362
362
+
.resolve_ident_owned(&ident())
273
363
.await
274
364
.map(|doc| {
275
365
use jacquard::smol_str::ToSmolStr;
···
280
370
.flatten()
281
371
.unwrap_or(old_ident.to_smolstr())
282
372
}
283
283
-
});
373
373
+
}));
284
374
285
285
-
use_memo(use_reactive!(|res| {
375
375
+
let memo = use_memo(use_reactive!(|res| {
286
376
if let Ok(res) = res {
287
377
if let Some(value) = &*res.read() {
288
378
AtIdentifier::new_owned(value).unwrap()
···
292
382
} else {
293
383
ident.read().clone()
294
384
}
295
295
-
}))
296
296
-
.into()
385
385
+
}));
386
386
+
387
387
+
(res, memo)
297
388
}
298
389
299
390
/// Hook to render markdown with SSR support.
···
303
394
ident: ReadSignal<AtIdentifier<'static>>,
304
395
) -> Memo<Option<String>> {
305
396
let fetcher = use_context::<crate::fetch::Fetcher>();
306
306
-
let res = use_server_future(move || {
397
397
+
let res = use_server_future(use_reactive!(|(content, ident)| {
307
398
let client = fetcher.get_client();
308
399
async move {
309
309
-
let did = match ident() {
400
400
+
let did = match ident.read().clone() {
310
401
AtIdentifier::Did(d) => d,
311
402
AtIdentifier::Handle(h) => client.resolve_handle(&h).await.ok()?,
312
403
};
313
404
Some(render_markdown_impl(content(), did).await)
314
405
}
315
315
-
});
406
406
+
}));
316
407
use_memo(use_reactive!(|res| {
317
408
let res = res.ok()?;
318
409
if let Some(Some(value)) = &*res.read() {
···
374
465
#[cfg(feature = "fullstack-server")]
375
466
pub fn use_profile_data(
376
467
ident: ReadSignal<AtIdentifier<'static>>,
377
377
-
) -> Memo<Option<ProfileDataView<'static>>> {
468
468
+
) -> (
469
469
+
Result<Resource<Option<serde_json::Value>>, RenderError>,
470
470
+
Memo<Option<ProfileDataView<'static>>>,
471
471
+
) {
378
472
let fetcher = use_context::<crate::fetch::Fetcher>();
379
379
-
let res = use_server_future(move || {
473
473
+
let res = use_server_future(use_reactive!(|ident| {
380
474
let fetcher = fetcher.clone();
381
475
async move {
382
382
-
tracing::debug!("use_profile_data server future STARTED for {:?}", ident());
476
476
+
tracing::debug!("use_profile_data server future STARTED for {:?}", ident);
383
477
let result = fetcher
384
478
.fetch_profile(&ident())
385
479
.await
386
480
.ok()
387
481
.map(|arc| serde_json::to_value(&*arc).ok())
388
482
.flatten();
389
389
-
tracing::debug!("use_profile_data server future COMPLETED for {:?}", ident());
483
483
+
tracing::debug!("use_profile_data server future COMPLETED for {:?}", ident);
390
484
result
391
485
}
392
392
-
});
393
393
-
use_memo(use_reactive!(|res| {
394
394
-
let res = res.ok()?;
486
486
+
}));
487
487
+
let memo = use_memo(use_reactive!(|res| {
488
488
+
let res = res.as_ref().ok()?;
395
489
if let Some(Some(value)) = &*res.read() {
396
490
jacquard::from_json_value::<ProfileDataView>(value.clone()).ok()
397
491
} else {
398
492
None
399
493
}
400
400
-
}))
494
494
+
}));
495
495
+
(res, memo)
401
496
}
402
497
403
498
/// Fetches profile data client-side only (no SSR)
404
499
#[cfg(not(feature = "fullstack-server"))]
405
500
pub fn use_profile_data(
406
501
ident: ReadSignal<AtIdentifier<'static>>,
407
407
-
) -> Memo<Option<ProfileDataView<'static>>> {
502
502
+
) -> (
503
503
+
Resource<Option<serde_json::Value>>,
504
504
+
Memo<Option<ProfileDataView<'static>>>,
505
505
+
) {
408
506
let fetcher = use_context::<crate::fetch::Fetcher>();
409
507
let res = use_resource(move || {
410
508
let fetcher = fetcher.clone();
···
417
515
.flatten()
418
516
}
419
517
});
420
420
-
use_memo(move || {
518
518
+
let memo = use_memo(move || {
421
519
if let Some(Some(value)) = &*res.read() {
422
520
jacquard::from_json_value::<ProfileDataView>(value.clone()).ok()
423
521
} else {
424
522
None
425
523
}
426
426
-
})
524
524
+
});
525
525
+
(res, memo);
427
526
}
428
527
429
528
/// Fetches notebooks for a specific DID
430
529
#[cfg(feature = "fullstack-server")]
431
530
pub fn use_notebooks_for_did(
432
531
ident: ReadSignal<AtIdentifier<'static>>,
433
433
-
) -> Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
532
532
+
) -> (
533
533
+
Result<Resource<Option<Vec<serde_json::Value>>>, RenderError>,
534
534
+
Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>,
535
535
+
) {
434
536
let fetcher = use_context::<crate::fetch::Fetcher>();
435
435
-
let res = use_server_future(move || {
537
537
+
let res = use_server_future(use_reactive!(|ident| {
436
538
let fetcher = fetcher.clone();
437
539
async move {
438
540
fetcher
···
447
549
})
448
550
.flatten()
449
551
}
450
450
-
});
451
451
-
use_memo(use_reactive!(|res| {
452
452
-
let res = res.ok()?;
552
552
+
}));
553
553
+
let memo = use_memo(use_reactive!(|res| {
554
554
+
let res = res.as_ref().ok()?;
453
555
if let Some(Some(values)) = &*res.read() {
454
556
values
455
557
.iter()
···
460
562
} else {
461
563
None
462
564
}
463
463
-
}))
565
565
+
}));
566
566
+
(res, memo)
464
567
}
465
568
466
569
/// Fetches notebooks client-side only (no SSR)
467
570
#[cfg(not(feature = "fullstack-server"))]
468
571
pub fn use_notebooks_for_did(
469
572
ident: ReadSignal<AtIdentifier<'static>>,
470
470
-
) -> Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
573
573
+
) -> (
574
574
+
Resource<Option<Vec<serde_json::Value>>>,
575
575
+
Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>,
576
576
+
) {
471
577
let fetcher = use_context::<crate::fetch::Fetcher>();
472
578
let res = use_resource(move || {
473
579
let fetcher = fetcher.clone();
···
485
591
.flatten()
486
592
}
487
593
});
488
488
-
use_memo(move || {
594
594
+
let memo = use_memo(move || {
489
595
if let Some(Some(values)) = &*res.read() {
490
596
values
491
597
.iter()
···
496
602
} else {
497
603
None
498
604
}
499
499
-
})
605
605
+
});
606
606
+
(res, memo)
500
607
}
501
608
502
609
/// Fetches notebooks from UFOS with SSR support in fullstack mode
503
610
#[cfg(feature = "fullstack-server")]
504
504
-
pub fn use_notebooks_from_ufos()
505
505
-
-> Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
611
611
+
pub fn use_notebooks_from_ufos() -> (
612
612
+
Result<Resource<Option<Vec<serde_json::Value>>>, RenderError>,
613
613
+
Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>,
614
614
+
) {
506
615
let fetcher = use_context::<crate::fetch::Fetcher>();
507
616
let res = use_server_future(move || {
508
617
let fetcher = fetcher.clone();
···
520
629
.flatten()
521
630
}
522
631
});
523
523
-
use_memo(use_reactive!(|res| {
524
524
-
let res = res.ok()?;
632
632
+
let memo = use_memo(use_reactive!(|res| {
633
633
+
let res = res.as_ref().ok()?;
525
634
if let Some(Some(values)) = &*res.read() {
526
635
values
527
636
.iter()
···
532
641
} else {
533
642
None
534
643
}
535
535
-
}))
644
644
+
}));
645
645
+
(res, memo)
536
646
}
537
647
538
648
/// Fetches notebooks from UFOS client-side only (no SSR)
539
649
#[cfg(not(feature = "fullstack-server"))]
540
540
-
pub fn use_notebooks_from_ufos()
541
541
-
-> Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
650
650
+
pub fn use_notebooks_from_ufos() -> (
651
651
+
Resource<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>,
652
652
+
Memo<Option<Vec<(NotebookView<'static>, Vec<StrongRef<'static>>)>>>,
653
653
+
) {
542
654
let fetcher = use_context::<crate::fetch::Fetcher>();
543
655
let res = use_resource(move || {
544
656
let fetcher = fetcher.clone();
···
556
668
.flatten()
557
669
}
558
670
});
559
559
-
use_memo(move || {
671
671
+
let memo = use_memo(move || {
560
672
if let Some(Some(values)) = &*res.read() {
561
673
values
562
674
.iter()
···
567
679
} else {
568
680
None
569
681
}
570
570
-
})
682
682
+
});
683
683
+
(res, memo)
571
684
}
572
685
573
686
/// Fetches notebook metadata with SSR support in fullstack mode
···
575
688
pub fn use_notebook(
576
689
ident: ReadSignal<AtIdentifier<'static>>,
577
690
book_title: ReadSignal<SmolStr>,
578
578
-
) -> Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>> {
691
691
+
) -> (
692
692
+
Result<Resource<Option<serde_json::Value>>, RenderError>,
693
693
+
Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>>,
694
694
+
) {
579
695
let fetcher = use_context::<crate::fetch::Fetcher>();
580
580
-
let res = use_server_future(move || {
696
696
+
let res = use_server_future(use_reactive!(|(ident, book_title)| {
581
697
let fetcher = fetcher.clone();
582
698
async move {
583
699
fetcher
···
588
704
.map(|arc| serde_json::to_value(arc.as_ref()).ok())
589
705
.flatten()
590
706
}
591
591
-
});
592
592
-
use_memo(use_reactive!(|res| {
593
593
-
let res = res.ok()?;
707
707
+
}));
708
708
+
let memo = use_memo(use_reactive!(|res| {
709
709
+
let res = res.as_ref().ok()?;
594
710
if let Some(Some(value)) = &*res.read() {
595
711
jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(value.clone()).ok()
596
712
} else {
597
713
None
598
714
}
599
599
-
}))
715
715
+
}));
716
716
+
(res, memo)
600
717
}
601
718
602
719
/// Fetches notebook metadata client-side only (no SSR)
···
604
721
pub fn use_notebook(
605
722
ident: ReadSignal<AtIdentifier<'static>>,
606
723
book_title: ReadSignal<SmolStr>,
607
607
-
) -> Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>> {
724
724
+
) -> (
725
725
+
Resource<Option<serde_json::Value>>,
726
726
+
Memo<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>>,
727
727
+
) {
608
728
let fetcher = use_context::<crate::fetch::Fetcher>();
609
729
let res = use_resource(move || {
610
730
let fetcher = fetcher.clone();
···
618
738
.flatten()
619
739
}
620
740
});
621
621
-
use_memo(use_reactive!(|res| {
622
622
-
let res = res.ok()?;
741
741
+
let memo = use_memo(use_reactive!(|res| {
623
742
if let Some(Some(value)) = &*res.read() {
624
743
jacquard::from_json_value::<(NotebookView, Vec<StrongRef>)>(value.clone()).ok()
625
744
} else {
626
745
None
627
746
}
628
628
-
}))
747
747
+
}));
748
748
+
(res, memo)
629
749
}
630
750
631
751
/// Fetches notebook entries with SSR support in fullstack mode
···
633
753
pub fn use_notebook_entries(
634
754
ident: ReadSignal<AtIdentifier<'static>>,
635
755
book_title: ReadSignal<SmolStr>,
636
636
-
) -> Memo<Option<Vec<BookEntryView<'static>>>> {
756
756
+
) -> (
757
757
+
Result<Resource<Option<Vec<serde_json::Value>>>, RenderError>,
758
758
+
Memo<Option<Vec<BookEntryView<'static>>>>,
759
759
+
) {
637
760
let fetcher = use_context::<crate::fetch::Fetcher>();
638
638
-
let res = use_server_future(move || {
761
761
+
let res = use_server_future(use_reactive!(|(ident, book_title)| {
639
762
let fetcher = fetcher.clone();
640
763
async move {
641
764
fetcher
···
651
774
})
652
775
.flatten()
653
776
}
654
654
-
});
655
655
-
use_memo(use_reactive!(|res| {
656
656
-
let res = res.ok()?;
777
777
+
}));
778
778
+
let memo = use_memo(use_reactive!(|res| {
779
779
+
let res = res.as_ref().ok()?;
657
780
if let Some(Some(values)) = &*res.read() {
658
781
values
659
782
.iter()
···
662
785
} else {
663
786
None
664
787
}
665
665
-
}))
788
788
+
}));
789
789
+
790
790
+
(res, memo)
666
791
}
667
792
668
793
/// Fetches notebook entries client-side only (no SSR)
···
670
795
pub fn use_notebook_entries(
671
796
ident: ReadSignal<AtIdentifier<'static>>,
672
797
book_title: ReadSignal<SmolStr>,
673
673
-
) -> Memo<Option<Vec<BookEntryView<'static>>>> {
798
798
+
) -> (
799
799
+
Resource<Option<Vec<BookEntryView<'static>>>>,
800
800
+
Memo<Option<Vec<BookEntryView<'static>>>>,
801
801
+
) {
674
802
let fetcher = use_context::<crate::fetch::Fetcher>();
675
803
let r = use_resource(move || {
676
804
let fetcher = fetcher.clone();
···
682
810
.flatten()
683
811
}
684
812
});
685
685
-
use_memo(move || r.read().as_ref().and_then(|v| v.clone()))
813
813
+
let memo = use_memo(move || r.read().as_ref().and_then(|v| v.clone()));
814
814
+
(r, memo)
686
815
}
687
816
688
817
#[cfg(feature = "fullstack-server")]
+258
-258
crates/weaver-app/src/fetch.rs
···
326
326
}
327
327
}
328
328
329
329
-
//#[cfg(not(feature = "server"))]
329
329
+
#[cfg(not(feature = "server"))]
330
330
#[derive(Clone)]
331
331
pub struct Fetcher {
332
332
pub client: Arc<Client>,
333
333
}
334
334
335
335
-
//#[cfg(not(feature = "server"))]
335
335
+
#[cfg(not(feature = "server"))]
336
336
impl Fetcher {
337
337
pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self {
338
338
Self {
···
574
574
}
575
575
}
576
576
577
577
-
// //#[cfg(feature = "server")]
578
578
-
// #[derive(Clone)]
579
579
-
// pub struct Fetcher {
580
580
-
// pub client: Arc<Client>,
581
581
-
// book_cache: cache_impl::Cache<
582
582
-
// (AtIdentifier<'static>, SmolStr),
583
583
-
// Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>,
584
584
-
// >,
585
585
-
// entry_cache: cache_impl::Cache<
586
586
-
// (AtIdentifier<'static>, SmolStr),
587
587
-
// Arc<(BookEntryView<'static>, Entry<'static>)>,
588
588
-
// >,
589
589
-
// profile_cache: cache_impl::Cache<AtIdentifier<'static>, Arc<ProfileDataView<'static>>>,
590
590
-
// }
577
577
+
#[cfg(feature = "server")]
578
578
+
#[derive(Clone)]
579
579
+
pub struct Fetcher {
580
580
+
pub client: Arc<Client>,
581
581
+
book_cache: cache_impl::Cache<
582
582
+
(AtIdentifier<'static>, SmolStr),
583
583
+
Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>,
584
584
+
>,
585
585
+
entry_cache: cache_impl::Cache<
586
586
+
(AtIdentifier<'static>, SmolStr),
587
587
+
Arc<(BookEntryView<'static>, Entry<'static>)>,
588
588
+
>,
589
589
+
profile_cache: cache_impl::Cache<AtIdentifier<'static>, Arc<ProfileDataView<'static>>>,
590
590
+
}
591
591
592
592
-
// // /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM
593
593
-
// //#[cfg(feature = "server")]
594
594
-
// unsafe impl Sync for Fetcher {}
592
592
+
// /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM
593
593
+
//#[cfg(feature = "server")]
594
594
+
unsafe impl Sync for Fetcher {}
595
595
596
596
-
// // /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM
597
597
-
// //#[cfg(feature = "server")]
598
598
-
// unsafe impl Send for Fetcher {}
596
596
+
// /// SAFETY: This isn't thread-safe on WASM, but we aren't multithreaded on WASM
597
597
+
//#[cfg(feature = "server")]
598
598
+
unsafe impl Send for Fetcher {}
599
599
600
600
-
// //#[cfg(feature = "server")]
601
601
-
// impl Fetcher {
602
602
-
// pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self {
603
603
-
// Self {
604
604
-
// client: Arc::new(Client::new(client)),
605
605
-
// book_cache: cache_impl::new_cache(100, Duration::from_secs(30)),
606
606
-
// entry_cache: cache_impl::new_cache(100, Duration::from_secs(30)),
607
607
-
// profile_cache: cache_impl::new_cache(100, Duration::from_secs(1800)),
608
608
-
// }
609
609
-
// }
600
600
+
#[cfg(feature = "server")]
601
601
+
impl Fetcher {
602
602
+
pub fn new(client: OAuthClient<JacquardResolver, AuthStore>) -> Self {
603
603
+
Self {
604
604
+
client: Arc::new(Client::new(client)),
605
605
+
book_cache: cache_impl::new_cache(100, Duration::from_secs(30)),
606
606
+
entry_cache: cache_impl::new_cache(100, Duration::from_secs(30)),
607
607
+
profile_cache: cache_impl::new_cache(100, Duration::from_secs(1800)),
608
608
+
}
609
609
+
}
610
610
611
611
-
// pub async fn upgrade_to_authenticated(
612
612
-
// &self,
613
613
-
// session: OAuthSession<JacquardResolver, crate::auth::AuthStore>,
614
614
-
// ) {
615
615
-
// let mut session_slot = self.client.session.write().await;
616
616
-
// *session_slot = Some(Arc::new(Agent::new(session)));
617
617
-
// }
611
611
+
pub async fn upgrade_to_authenticated(
612
612
+
&self,
613
613
+
session: OAuthSession<JacquardResolver, crate::auth::AuthStore>,
614
614
+
) {
615
615
+
let mut session_slot = self.client.session.write().await;
616
616
+
*session_slot = Some(Arc::new(Agent::new(session)));
617
617
+
}
618
618
619
619
-
// pub async fn downgrade_to_unauthenticated(&self) {
620
620
-
// let mut session_slot = self.client.session.write().await;
621
621
-
// if let Some(session) = session_slot.take() {
622
622
-
// session.inner().logout().await.ok();
623
623
-
// }
624
624
-
// }
619
619
+
pub async fn downgrade_to_unauthenticated(&self) {
620
620
+
let mut session_slot = self.client.session.write().await;
621
621
+
if let Some(session) = session_slot.take() {
622
622
+
session.inner().logout().await.ok();
623
623
+
}
624
624
+
}
625
625
626
626
-
// #[allow(dead_code)]
627
627
-
// pub async fn current_did(&self) -> Option<Did<'static>> {
628
628
-
// let session_slot = self.client.session.read().await;
629
629
-
// if let Some(session) = session_slot.as_ref() {
630
630
-
// session.info().await.map(|(d, _)| d)
631
631
-
// } else {
632
632
-
// None
633
633
-
// }
634
634
-
// }
626
626
+
#[allow(dead_code)]
627
627
+
pub async fn current_did(&self) -> Option<Did<'static>> {
628
628
+
let session_slot = self.client.session.read().await;
629
629
+
if let Some(session) = session_slot.as_ref() {
630
630
+
session.info().await.map(|(d, _)| d)
631
631
+
} else {
632
632
+
None
633
633
+
}
634
634
+
}
635
635
636
636
-
// pub fn get_client(&self) -> Arc<Client> {
637
637
-
// self.client.clone()
638
638
-
// }
636
636
+
pub fn get_client(&self) -> Arc<Client> {
637
637
+
self.client.clone()
638
638
+
}
639
639
640
640
-
// pub async fn get_notebook(
641
641
-
// &self,
642
642
-
// ident: AtIdentifier<'static>,
643
643
-
// title: SmolStr,
644
644
-
// ) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
645
645
-
// if let Some(entry) = cache_impl::get(&self.book_cache, &(ident.clone(), title.clone())) {
646
646
-
// Ok(Some(entry))
647
647
-
// } else {
648
648
-
// let client = self.get_client();
649
649
-
// if let Some((notebook, entries)) = client
650
650
-
// .notebook_by_title(&ident, &title)
651
651
-
// .await
652
652
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?
653
653
-
// {
654
654
-
// let stored = Arc::new((notebook, entries));
655
655
-
// cache_impl::insert(&self.book_cache, (ident, title), stored.clone());
656
656
-
// Ok(Some(stored))
657
657
-
// } else {
658
658
-
// Ok(None)
659
659
-
// }
660
660
-
// }
661
661
-
// }
640
640
+
pub async fn get_notebook(
641
641
+
&self,
642
642
+
ident: AtIdentifier<'static>,
643
643
+
title: SmolStr,
644
644
+
) -> Result<Option<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
645
645
+
if let Some(entry) = cache_impl::get(&self.book_cache, &(ident.clone(), title.clone())) {
646
646
+
Ok(Some(entry))
647
647
+
} else {
648
648
+
let client = self.get_client();
649
649
+
if let Some((notebook, entries)) = client
650
650
+
.notebook_by_title(&ident, &title)
651
651
+
.await
652
652
+
.map_err(|e| dioxus::CapturedError::from_display(e))?
653
653
+
{
654
654
+
let stored = Arc::new((notebook, entries));
655
655
+
cache_impl::insert(&self.book_cache, (ident, title), stored.clone());
656
656
+
Ok(Some(stored))
657
657
+
} else {
658
658
+
Ok(None)
659
659
+
}
660
660
+
}
661
661
+
}
662
662
663
663
-
// pub async fn get_entry(
664
664
-
// &self,
665
665
-
// ident: AtIdentifier<'static>,
666
666
-
// book_title: SmolStr,
667
667
-
// entry_title: SmolStr,
668
668
-
// ) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> {
669
669
-
// if let Some(result) = self.get_notebook(ident.clone(), book_title).await? {
670
670
-
// let (notebook, entries) = result.as_ref();
671
671
-
// if let Some(entry) =
672
672
-
// cache_impl::get(&self.entry_cache, &(ident.clone(), entry_title.clone()))
673
673
-
// {
674
674
-
// Ok(Some(entry))
675
675
-
// } else {
676
676
-
// let client = self.get_client();
677
677
-
// if let Some(entry) = client
678
678
-
// .entry_by_title(notebook, entries.as_ref(), &entry_title)
679
679
-
// .await
680
680
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?
681
681
-
// {
682
682
-
// let stored = Arc::new(entry);
683
683
-
// cache_impl::insert(&self.entry_cache, (ident, entry_title), stored.clone());
684
684
-
// Ok(Some(stored))
685
685
-
// } else {
686
686
-
// Ok(None)
687
687
-
// }
688
688
-
// }
689
689
-
// } else {
690
690
-
// Ok(None)
691
691
-
// }
692
692
-
// }
663
663
+
pub async fn get_entry(
664
664
+
&self,
665
665
+
ident: AtIdentifier<'static>,
666
666
+
book_title: SmolStr,
667
667
+
entry_title: SmolStr,
668
668
+
) -> Result<Option<Arc<(BookEntryView<'static>, Entry<'static>)>>> {
669
669
+
if let Some(result) = self.get_notebook(ident.clone(), book_title).await? {
670
670
+
let (notebook, entries) = result.as_ref();
671
671
+
if let Some(entry) =
672
672
+
cache_impl::get(&self.entry_cache, &(ident.clone(), entry_title.clone()))
673
673
+
{
674
674
+
Ok(Some(entry))
675
675
+
} else {
676
676
+
let client = self.get_client();
677
677
+
if let Some(entry) = client
678
678
+
.entry_by_title(notebook, entries.as_ref(), &entry_title)
679
679
+
.await
680
680
+
.map_err(|e| dioxus::CapturedError::from_display(e))?
681
681
+
{
682
682
+
let stored = Arc::new(entry);
683
683
+
cache_impl::insert(&self.entry_cache, (ident, entry_title), stored.clone());
684
684
+
Ok(Some(stored))
685
685
+
} else {
686
686
+
Ok(None)
687
687
+
}
688
688
+
}
689
689
+
} else {
690
690
+
Ok(None)
691
691
+
}
692
692
+
}
693
693
694
694
-
// pub async fn fetch_notebooks_from_ufos(
695
695
-
// &self,
696
696
-
// ) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
697
697
-
// use jacquard::{IntoStatic, types::aturi::AtUri};
694
694
+
pub async fn fetch_notebooks_from_ufos(
695
695
+
&self,
696
696
+
) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
697
697
+
use jacquard::{IntoStatic, types::aturi::AtUri};
698
698
699
699
-
// let url = "https://ufos-api.microcosm.blue/records?collection=sh.weaver.notebook.book";
700
700
-
// let response = reqwest::get(url)
701
701
-
// .await
702
702
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?;
699
699
+
let url = "https://ufos-api.microcosm.blue/records?collection=sh.weaver.notebook.book";
700
700
+
let response = reqwest::get(url)
701
701
+
.await
702
702
+
.map_err(|e| dioxus::CapturedError::from_display(e))?;
703
703
704
704
-
// let records: Vec<UfosRecord> = response
705
705
-
// .json()
706
706
-
// .await
707
707
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?;
704
704
+
let records: Vec<UfosRecord> = response
705
705
+
.json()
706
706
+
.await
707
707
+
.map_err(|e| dioxus::CapturedError::from_display(e))?;
708
708
709
709
-
// let mut notebooks = Vec::new();
710
710
-
// let client = self.get_client();
709
709
+
let mut notebooks = Vec::new();
710
710
+
let client = self.get_client();
711
711
712
712
-
// for ufos_record in records {
713
713
-
// // Construct URI
714
714
-
// let uri_str = format!(
715
715
-
// "at://{}/{}/{}",
716
716
-
// ufos_record.did, ufos_record.collection, ufos_record.rkey
717
717
-
// );
718
718
-
// let uri = AtUri::new_owned(uri_str)
719
719
-
// .map_err(|e| dioxus::CapturedError::from_display(format!("Invalid URI: {}", e)))?;
712
712
+
for ufos_record in records {
713
713
+
// Construct URI
714
714
+
let uri_str = format!(
715
715
+
"at://{}/{}/{}",
716
716
+
ufos_record.did, ufos_record.collection, ufos_record.rkey
717
717
+
);
718
718
+
let uri = AtUri::new_owned(uri_str)
719
719
+
.map_err(|e| dioxus::CapturedError::from_display(format!("Invalid URI: {}", e)))?;
720
720
721
721
-
// // Fetch the full notebook view (which hydrates authors)
722
722
-
// match client.view_notebook(&uri).await {
723
723
-
// Ok((notebook, entries)) => {
724
724
-
// let ident = uri.authority().clone().into_static();
725
725
-
// let title = notebook
726
726
-
// .title
727
727
-
// .as_ref()
728
728
-
// .map(|t| SmolStr::new(t.as_ref()))
729
729
-
// .unwrap_or_else(|| SmolStr::new("Untitled"));
721
721
+
// Fetch the full notebook view (which hydrates authors)
722
722
+
match client.view_notebook(&uri).await {
723
723
+
Ok((notebook, entries)) => {
724
724
+
let ident = uri.authority().clone().into_static();
725
725
+
let title = notebook
726
726
+
.title
727
727
+
.as_ref()
728
728
+
.map(|t| SmolStr::new(t.as_ref()))
729
729
+
.unwrap_or_else(|| SmolStr::new("Untitled"));
730
730
731
731
-
// let result = Arc::new((notebook, entries));
732
732
-
// // Cache it
733
733
-
// cache_impl::insert(&self.book_cache, (ident, title), result.clone());
734
734
-
// notebooks.push(result);
735
735
-
// }
736
736
-
// Err(_) => continue, // Skip notebooks that fail to load
737
737
-
// }
738
738
-
// }
731
731
+
let result = Arc::new((notebook, entries));
732
732
+
// Cache it
733
733
+
cache_impl::insert(&self.book_cache, (ident, title), result.clone());
734
734
+
notebooks.push(result);
735
735
+
}
736
736
+
Err(_) => continue, // Skip notebooks that fail to load
737
737
+
}
738
738
+
}
739
739
740
740
-
// Ok(notebooks)
741
741
-
// }
740
740
+
Ok(notebooks)
741
741
+
}
742
742
743
743
-
// pub async fn fetch_notebooks_for_did(
744
744
-
// &self,
745
745
-
// ident: &AtIdentifier<'_>,
746
746
-
// ) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
747
747
-
// use jacquard::{
748
748
-
// IntoStatic,
749
749
-
// types::{collection::Collection, nsid::Nsid},
750
750
-
// xrpc::XrpcExt,
751
751
-
// };
752
752
-
// use weaver_api::{
753
753
-
// com_atproto::repo::list_records::ListRecords, sh_weaver::notebook::book::Book,
754
754
-
// };
743
743
+
pub async fn fetch_notebooks_for_did(
744
744
+
&self,
745
745
+
ident: &AtIdentifier<'_>,
746
746
+
) -> Result<Vec<Arc<(NotebookView<'static>, Vec<StrongRef<'static>>)>>> {
747
747
+
use jacquard::{
748
748
+
IntoStatic,
749
749
+
types::{collection::Collection, nsid::Nsid},
750
750
+
xrpc::XrpcExt,
751
751
+
};
752
752
+
use weaver_api::{
753
753
+
com_atproto::repo::list_records::ListRecords, sh_weaver::notebook::book::Book,
754
754
+
};
755
755
756
756
-
// let client = self.get_client();
756
756
+
let client = self.get_client();
757
757
758
758
-
// // Resolve DID and PDS
759
759
-
// let (repo_did, pds_url) = match ident {
760
760
-
// AtIdentifier::Did(did) => {
761
761
-
// let pds = client
762
762
-
// .pds_for_did(did)
763
763
-
// .await
764
764
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?;
765
765
-
// (did.clone(), pds)
766
766
-
// }
767
767
-
// AtIdentifier::Handle(handle) => client
768
768
-
// .pds_for_handle(handle)
769
769
-
// .await
770
770
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?,
771
771
-
// };
758
758
+
// Resolve DID and PDS
759
759
+
let (repo_did, pds_url) = match ident {
760
760
+
AtIdentifier::Did(did) => {
761
761
+
let pds = client
762
762
+
.pds_for_did(did)
763
763
+
.await
764
764
+
.map_err(|e| dioxus::CapturedError::from_display(e))?;
765
765
+
(did.clone(), pds)
766
766
+
}
767
767
+
AtIdentifier::Handle(handle) => client
768
768
+
.pds_for_handle(handle)
769
769
+
.await
770
770
+
.map_err(|e| dioxus::CapturedError::from_display(e))?,
771
771
+
};
772
772
773
773
-
// // Fetch all notebook records for this repo
774
774
-
// let resp = client
775
775
-
// .xrpc(pds_url)
776
776
-
// .send(
777
777
-
// &ListRecords::new()
778
778
-
// .repo(repo_did)
779
779
-
// .collection(Nsid::raw(Book::NSID))
780
780
-
// .limit(100)
781
781
-
// .build(),
782
782
-
// )
783
783
-
// .await
784
784
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?;
773
773
+
// Fetch all notebook records for this repo
774
774
+
let resp = client
775
775
+
.xrpc(pds_url)
776
776
+
.send(
777
777
+
&ListRecords::new()
778
778
+
.repo(repo_did)
779
779
+
.collection(Nsid::raw(Book::NSID))
780
780
+
.limit(100)
781
781
+
.build(),
782
782
+
)
783
783
+
.await
784
784
+
.map_err(|e| dioxus::CapturedError::from_display(e))?;
785
785
786
786
-
// let mut notebooks = Vec::new();
786
786
+
let mut notebooks = Vec::new();
787
787
788
788
-
// if let Ok(list) = resp.parse() {
789
789
-
// for record in list.records {
790
790
-
// // View the notebook (which hydrates authors)
791
791
-
// match client.view_notebook(&record.uri).await {
792
792
-
// Ok((notebook, entries)) => {
793
793
-
// let ident = record.uri.authority().clone().into_static();
794
794
-
// let title = notebook
795
795
-
// .title
796
796
-
// .as_ref()
797
797
-
// .map(|t| SmolStr::new(t.as_ref()))
798
798
-
// .unwrap_or_else(|| SmolStr::new("Untitled"));
788
788
+
if let Ok(list) = resp.parse() {
789
789
+
for record in list.records {
790
790
+
// View the notebook (which hydrates authors)
791
791
+
match client.view_notebook(&record.uri).await {
792
792
+
Ok((notebook, entries)) => {
793
793
+
let ident = record.uri.authority().clone().into_static();
794
794
+
let title = notebook
795
795
+
.title
796
796
+
.as_ref()
797
797
+
.map(|t| SmolStr::new(t.as_ref()))
798
798
+
.unwrap_or_else(|| SmolStr::new("Untitled"));
799
799
800
800
-
// let result = Arc::new((notebook, entries));
801
801
-
// // Cache it
802
802
-
// cache_impl::insert(&self.book_cache, (ident, title), result.clone());
803
803
-
// notebooks.push(result);
804
804
-
// }
805
805
-
// Err(_) => continue, // Skip notebooks that fail to load
806
806
-
// }
807
807
-
// }
808
808
-
// }
800
800
+
let result = Arc::new((notebook, entries));
801
801
+
// Cache it
802
802
+
cache_impl::insert(&self.book_cache, (ident, title), result.clone());
803
803
+
notebooks.push(result);
804
804
+
}
805
805
+
Err(_) => continue, // Skip notebooks that fail to load
806
806
+
}
807
807
+
}
808
808
+
}
809
809
810
810
-
// Ok(notebooks)
811
811
-
// }
810
810
+
Ok(notebooks)
811
811
+
}
812
812
813
813
-
// pub async fn list_notebook_entries(
814
814
-
// &self,
815
815
-
// ident: AtIdentifier<'static>,
816
816
-
// book_title: SmolStr,
817
817
-
// ) -> Result<Option<Vec<BookEntryView<'static>>>> {
818
818
-
// if let Some(result) = self.get_notebook(ident.clone(), book_title).await? {
819
819
-
// let (notebook, entries) = result.as_ref();
820
820
-
// let mut book_entries = Vec::new();
821
821
-
// let client = self.get_client();
813
813
+
pub async fn list_notebook_entries(
814
814
+
&self,
815
815
+
ident: AtIdentifier<'static>,
816
816
+
book_title: SmolStr,
817
817
+
) -> Result<Option<Vec<BookEntryView<'static>>>> {
818
818
+
if let Some(result) = self.get_notebook(ident.clone(), book_title).await? {
819
819
+
let (notebook, entries) = result.as_ref();
820
820
+
let mut book_entries = Vec::new();
821
821
+
let client = self.get_client();
822
822
823
823
-
// for index in 0..entries.len() {
824
824
-
// match client.view_entry(notebook, entries, index).await {
825
825
-
// Ok(book_entry) => book_entries.push(book_entry),
826
826
-
// Err(_) => continue, // Skip entries that fail to load
827
827
-
// }
828
828
-
// }
823
823
+
for index in 0..entries.len() {
824
824
+
match client.view_entry(notebook, entries, index).await {
825
825
+
Ok(book_entry) => book_entries.push(book_entry),
826
826
+
Err(_) => continue, // Skip entries that fail to load
827
827
+
}
828
828
+
}
829
829
830
830
-
// Ok(Some(book_entries))
831
831
-
// } else {
832
832
-
// Ok(None)
833
833
-
// }
834
834
-
// }
830
830
+
Ok(Some(book_entries))
831
831
+
} else {
832
832
+
Ok(None)
833
833
+
}
834
834
+
}
835
835
836
836
-
// pub async fn fetch_profile(
837
837
-
// &self,
838
838
-
// ident: &AtIdentifier<'_>,
839
839
-
// ) -> Result<Arc<ProfileDataView<'static>>> {
840
840
-
// use jacquard::IntoStatic;
836
836
+
pub async fn fetch_profile(
837
837
+
&self,
838
838
+
ident: &AtIdentifier<'_>,
839
839
+
) -> Result<Arc<ProfileDataView<'static>>> {
840
840
+
use jacquard::IntoStatic;
841
841
842
842
-
// let ident_static = ident.clone().into_static();
842
842
+
let ident_static = ident.clone().into_static();
843
843
844
844
-
// if let Some(cached) = cache_impl::get(&self.profile_cache, &ident_static) {
845
845
-
// return Ok(cached);
846
846
-
// }
844
844
+
if let Some(cached) = cache_impl::get(&self.profile_cache, &ident_static) {
845
845
+
return Ok(cached);
846
846
+
}
847
847
848
848
-
// let client = self.get_client();
848
848
+
let client = self.get_client();
849
849
850
850
-
// let did = match ident {
851
851
-
// AtIdentifier::Did(d) => d.clone(),
852
852
-
// AtIdentifier::Handle(h) => client
853
853
-
// .resolve_handle(h)
854
854
-
// .await
855
855
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?,
856
856
-
// };
850
850
+
let did = match ident {
851
851
+
AtIdentifier::Did(d) => d.clone(),
852
852
+
AtIdentifier::Handle(h) => client
853
853
+
.resolve_handle(h)
854
854
+
.await
855
855
+
.map_err(|e| dioxus::CapturedError::from_display(e))?,
856
856
+
};
857
857
858
858
-
// let (_uri, profile_view) = client
859
859
-
// .hydrate_profile_view(&did)
860
860
-
// .await
861
861
-
// .map_err(|e| dioxus::CapturedError::from_display(e))?;
858
858
+
let (_uri, profile_view) = client
859
859
+
.hydrate_profile_view(&did)
860
860
+
.await
861
861
+
.map_err(|e| dioxus::CapturedError::from_display(e))?;
862
862
863
863
-
// let result = Arc::new(profile_view);
864
864
-
// cache_impl::insert(&self.profile_cache, ident_static, result.clone());
863
863
+
let result = Arc::new(profile_view);
864
864
+
cache_impl::insert(&self.profile_cache, ident_static, result.clone());
865
865
866
866
-
// Ok(result)
867
867
-
// }
868
868
-
// }
866
866
+
Ok(result)
867
867
+
}
868
868
+
}
869
869
870
870
impl HttpClient for Fetcher {
871
871
#[doc = " Error type returned by the HTTP client"]
+8
-5
crates/weaver-app/src/main.rs
···
178
178
))]
179
179
{
180
180
let fetcher = fetcher.clone();
181
181
-
use_future(move || {
181
181
+
use_effect(move || {
182
182
let fetcher = fetcher.clone();
183
183
-
async move {
184
184
-
if let Err(e) = auth::restore_session(fetcher, auth_state).await {
185
185
-
tracing::debug!("Session restoration failed: {}", e);
183
183
+
use_future(move || {
184
184
+
let fetcher = fetcher.clone();
185
185
+
async move {
186
186
+
if let Err(e) = auth::restore_session(fetcher, auth_state).await {
187
187
+
tracing::debug!("Session restoration failed: {}", e);
188
188
+
}
186
189
}
187
187
-
}
190
190
+
});
188
191
});
189
192
}
190
193
+7
-1
crates/weaver-app/src/views/home.rs
···
8
8
#[component]
9
9
pub fn Home() -> Element {
10
10
// Fetch notebooks from UFOS with SSR support
11
11
-
let notebooks = data::use_notebooks_from_ufos();
11
11
+
let (notebooks_result, notebooks) = data::use_notebooks_from_ufos();
12
12
let navigator = use_navigator();
13
13
let mut uri_input = use_signal(|| String::new());
14
14
···
21
21
}
22
22
}
23
23
};
24
24
+
#[cfg(feature = "fullstack-server")]
25
25
+
notebooks_result
26
26
+
.as_ref()
27
27
+
.ok()
28
28
+
.map(|r| r.suspend())
29
29
+
.transpose()?;
24
30
25
31
rsx! {
26
32
document::Link { rel: "stylesheet", href: NOTEBOOK_CARD_CSS }
+30
-13
crates/weaver-app/src/views/navbar.rs
···
5
5
use crate::data::{use_get_handle, use_load_handle};
6
6
use crate::fetch::Fetcher;
7
7
use dioxus::prelude::*;
8
8
+
use jacquard::types::string::Did;
8
9
9
10
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
10
11
···
20
21
tracing::debug!("Route: {:?}", route);
21
22
22
23
let mut auth_state = use_context::<Signal<crate::auth::AuthState>>();
23
23
-
let route_handle = use_load_handle(match &route {
24
24
+
let (route_handle_res, route_handle) = use_load_handle(match &route {
24
25
Route::EntryPage { ident, .. } => Some(ident.clone()),
25
26
Route::RepositoryIndex { ident } => Some(ident.clone()),
26
27
Route::NotebookIndex { ident, .. } => Some(ident.clone()),
27
28
_ => None,
28
29
});
30
30
+
31
31
+
#[cfg(feature = "fullstack-server")]
32
32
+
route_handle_res?;
33
33
+
29
34
let fetcher = use_context::<Fetcher>();
30
35
let mut show_login_modal = use_signal(|| false);
31
36
32
32
-
tracing::debug!("Navbar got route_handle: {:?}", route_handle);
37
37
+
tracing::debug!("Navbar got route_handle: {:?}", route_handle.read());
33
38
34
39
rsx! {
35
40
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
···
90
95
}
91
96
if auth_state.read().is_authenticated() {
92
97
if let Some(did) = &auth_state.read().did {
93
93
-
Button {
94
94
-
variant: ButtonVariant::Ghost,
95
95
-
onclick: move |_| {
96
96
-
let fetcher = fetcher.clone();
97
97
-
auth_state.write().clear();
98
98
-
async move {
99
99
-
fetcher.downgrade_to_unauthenticated().await;
100
100
-
}
101
101
-
},
102
102
-
span { class: "auth-handle", "@{use_get_handle(did.clone())}" }
103
103
-
}
98
98
+
AuthButton { did: did.clone() }
104
99
}
105
100
} else {
106
101
div {
···
121
116
Outlet::<Route> {}
122
117
}
123
118
}
119
119
+
120
120
+
#[component]
121
121
+
fn AuthButton(did: Did<'static>) -> Element {
122
122
+
let auth_handle = use_get_handle(did);
123
123
+
124
124
+
let fetcher = use_context::<Fetcher>();
125
125
+
let mut auth_state = use_context::<Signal<AuthState>>();
126
126
+
127
127
+
rsx! {
128
128
+
Button {
129
129
+
variant: ButtonVariant::Ghost,
130
130
+
onclick: move |_| {
131
131
+
let fetcher = fetcher.clone();
132
132
+
auth_state.write().clear();
133
133
+
async move {
134
134
+
fetcher.downgrade_to_unauthenticated().await;
135
135
+
}
136
136
+
},
137
137
+
span { class: "auth-handle", "@{auth_handle()}" }
138
138
+
}
139
139
+
}
140
140
+
}
+8
-2
crates/weaver-app/src/views/notebook.rs
···
40
40
);
41
41
// Fetch full notebook metadata with SSR support
42
42
// IMPORTANT: Call ALL hooks before any ? early returns to maintain hook order
43
43
-
let notebook_data = data::use_notebook(ident, book_title);
44
44
-
let entries_resource = data::use_notebook_entries(ident, book_title);
43
43
+
let (notebook_result, notebook_data) = data::use_notebook(ident, book_title);
44
44
+
let (entries_result, entries_resource) = data::use_notebook_entries(ident, book_title);
45
45
tracing::debug!("NotebookIndex got notebook data and entries");
46
46
+
47
47
+
#[cfg(feature = "fullstack-server")]
48
48
+
notebook_result?;
49
49
+
50
50
+
#[cfg(feature = "fullstack-server")]
51
51
+
entries_result?;
46
52
47
53
rsx! {
48
54
document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS }