Skip to content

Commit d4312d2

Browse files
committed
Implement alias for exposures.
1 parent a8ed7fa commit d4312d2

File tree

4 files changed

+151
-62
lines changed

4 files changed

+151
-62
lines changed

pmrapp/src/exposure.rs

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use leptos::logging;
2+
use leptos::context::Provider;
23
use leptos::prelude::*;
34
use leptos_meta::*;
45
use leptos_router::{
@@ -59,6 +60,7 @@ use crate::{
5960
},
6061
exposure::api::{
6162
list,
63+
list_aliased,
6264
get_exposure_info,
6365
resolve_exposure_path,
6466
update_wizard_field,
@@ -72,15 +74,19 @@ use crate::{
7274
EFView,
7375
ExposureFileView,
7476
},
75-
app::portlet::{
76-
ContentActionCtx,
77-
ContentActionItem,
78-
ExposureSourceCtx,
79-
ExposureSourceItem,
80-
NavigationCtx,
81-
NavigationItem,
82-
ViewsAvailableCtx,
83-
ViewsAvailableItem,
77+
app::{
78+
portlet::{
79+
ContentActionCtx,
80+
ContentActionItem,
81+
ExposureSourceCtx,
82+
ExposureSourceItem,
83+
NavigationCtx,
84+
NavigationItem,
85+
ViewsAvailableCtx,
86+
ViewsAvailableItem,
87+
},
88+
EntityRoot,
89+
Root,
8490
},
8591
};
8692

@@ -106,12 +112,27 @@ pub fn ExposureRoutes() -> impl MatchNestedRoutes + Clone {
106112
<ParentRoute path=StaticSegment("/exposure") view=ExposureRoot ssr>
107113
<Route path=StaticSegment("/") view=ExposureListing/>
108114
<Route path=StaticSegment("") view=RedirectTS/>
109-
<ParentRoute path=ParamSegment("id") view=Exposure>
110-
<Route path=StaticSegment("/") view=ExposureMain/>
111-
<Route path=StaticSegment("") view=RedirectTS/>
112-
<Route path=(StaticSegment(":"), StaticSegment("wizard")) view=Wizard/>
113-
<Route path=WildcardSegment("path") view=ExposureFile/>
115+
<ParentRoute path=StaticSegment(":") view=Outlet>
116+
<ParentRoute path=StaticSegment("id") view=ExposureIdRoot>
117+
<Route path=StaticSegment("/") view=ExposureListing/>
118+
<ExposureViewRoutes/>
119+
</ParentRoute>
114120
</ParentRoute>
121+
<ExposureViewRoutes/>
122+
</ParentRoute>
123+
}
124+
.into_inner()
125+
}
126+
127+
#[component]
128+
pub fn ExposureViewRoutes() -> impl MatchNestedRoutes + Clone {
129+
let ssr = SsrMode::Async;
130+
view! {
131+
<ParentRoute path=ParamSegment("id") view=Exposure ssr>
132+
<Route path=StaticSegment("/") view=ExposureMain/>
133+
<Route path=StaticSegment("") view=RedirectTS/>
134+
<Route path=(StaticSegment(":"), StaticSegment("wizard")) view=Wizard/>
135+
<Route path=WildcardSegment("path") view=ExposureFile/>
115136
</ParentRoute>
116137
}
117138
.into_inner()
@@ -127,13 +148,26 @@ pub fn ExposureRoot() -> impl IntoView {
127148

128149
view! {
129150
<Title text="Exposure — Physiome Model Repository"/>
130-
<Outlet/>
151+
<Provider value=Root::Aliased("/exposure/")>
152+
<Outlet/>
153+
</Provider>
154+
}
155+
}
156+
157+
#[component]
158+
pub fn ExposureIdRoot() -> impl IntoView {
159+
view! {
160+
<Title text="Exposure — Physiome Model Repository"/>
161+
<Provider value=Root::Id("/exposure/:/id/")>
162+
<Outlet/>
163+
</Provider>
131164
}
132165
}
133166

134167
#[component]
135168
pub fn ExposureListing() -> impl IntoView {
136169
let account_ctx = expect_context::<AccountCtx>();
170+
let root = expect_context::<Root>();
137171
#[cfg(not(feature = "ssr"))]
138172
on_cleanup({
139173
let account_ctx = account_ctx.clone();
@@ -151,9 +185,12 @@ pub fn ExposureListing() -> impl IntoView {
151185
move |_| {
152186
let set_ps = account_ctx.policy_state.write_only();
153187
async move {
154-
let result = list().await;
155188
// required by notify_into which will take it.
156189
provide_context(set_ps);
190+
let result = match root {
191+
Root::Id(_) => list().await,
192+
Root::Aliased(_) => list_aliased().await,
193+
};
157194
match result {
158195
Ok(ref result) => logging::log!("{}", result.inner.len()),
159196
Err(_) => logging::log!("error loading exposures"),
@@ -171,10 +208,10 @@ pub fn ExposureListing() -> impl IntoView {
171208
.into_iter()
172209
.map(move |exposure| view! {
173210
<div>
174-
<div><a href=format!("/exposure/{}/", exposure.id)>
175-
"Exposure "{exposure.id}
211+
<div><a href=format!("{root}{}/", exposure.alias)>
212+
"Exposure "{exposure.entity.id}
176213
</a></div>
177-
<div>{exposure.description}</div>
214+
<div>{exposure.entity.description}</div>
178215
</div>
179216
})
180217
.collect_view()
@@ -197,7 +234,7 @@ pub fn ExposureListing() -> impl IntoView {
197234

198235
#[derive(Params, PartialEq, Clone, Debug)]
199236
pub struct ExposureParams {
200-
id: Option<i64>,
237+
id: Option<String>,
201238
}
202239

203240
#[component]
@@ -206,6 +243,7 @@ pub fn Exposure() -> impl IntoView {
206243
let navigation_ctx = NavigationCtx::expect();
207244
let content_action_ctx = ContentActionCtx::expect();
208245
let account_ctx = expect_context::<AccountCtx>();
246+
let root = expect_context::<Root>();
209247

210248
#[cfg(not(feature = "ssr"))]
211249
on_cleanup({
@@ -224,17 +262,30 @@ pub fn Exposure() -> impl IntoView {
224262
});
225263

226264
let params = use_params::<ExposureParams>();
265+
266+
let entity_root: Memo<EntityRoot> = Memo::new(move |_| {
267+
root.build_entity_root(params.get()
268+
.expect("conversion to string must be infallible")
269+
.id
270+
.expect("this must be used inside a route with an id parameter")
271+
)
272+
});
273+
provide_context(entity_root);
274+
227275
provide_context(Resource::new_blocking(
228276
move || params.get().map(|p| p.id),
229277
move |p| {
230278
provide_context(account_ctx.policy_state.write_only());
231279
async move {
232280
let result = match p {
233281
Err(_) => Err(AppError::InternalServerError),
234-
Ok(Some(id)) => get_exposure_info(id)
235-
.await
236-
.map(EnforcedOk::notify_into)
237-
.map_err(AppError::from),
282+
Ok(Some(id)) => {
283+
let id = root.build_id(id)?;
284+
get_exposure_info(id)
285+
.await
286+
.map(EnforcedOk::notify_into)
287+
.map_err(AppError::from)
288+
}
238289
_ => Err(AppError::NotFound),
239290
};
240291
let _ = take_context::<SsrWriteSignal<Option<PolicyState>>>();
@@ -268,13 +319,14 @@ pub fn Exposure() -> impl IntoView {
268319
async move {
269320
exposure_info.await.ok().map(|info| {
270321
let exposure_id = info.exposure.id;
322+
let base_href = entity_root.get().to_string();
271323
logging::log!("building NavigationCtx");
272324
// TODO should derive from exposure.files when it contains title/description
273325
info.files
274326
.into_iter()
275327
.filter_map(move |(file, flag)| {
276328
flag.then(|| {
277-
let href = format!("/exposure/{exposure_id}/{file}/");
329+
let href = format!("{base_href}/{file}/");
278330
let text = file.clone();
279331
let title = None;
280332
NavigationItem { href, text, title }
@@ -291,7 +343,8 @@ pub fn Exposure() -> impl IntoView {
291343
exposure_info.track();
292344
async move {
293345
exposure_info.await.ok().map(|info| {
294-
let resource = format!("/exposure/{}/", info.exposure.id);
346+
let base_href = entity_root.get().to_string();
347+
let resource = format!("{base_href}/");
295348
vec![
296349
ContentActionItem {
297350
href: resource.clone(),
@@ -322,16 +375,16 @@ pub fn Exposure() -> impl IntoView {
322375
}
323376

324377
#[component]
325-
pub fn ExposureFileListing(id: i64, files: Vec<(String, bool)>) -> impl IntoView {
378+
pub fn ExposureFileListing(base_href: Arc<str>, files: Vec<(String, bool)>) -> impl IntoView {
326379
view! {
327380
<ul>{files.into_iter()
328381
.map(|(file, flag)| view! {
329382
<li>
330-
<a href=format!("/exposure/{id}/{file}")>
383+
<a href=format!("{base_href}/{file}")>
331384
{file.clone()}
332385
</a>
333386
" - "{flag.then(|| view! {
334-
<a href=format!("/exposure/{id}/{file}/")>
387+
<a href=format!("{base_href}/{file}/")>
335388
{flag}
336389
</a>
337390
}.into_any()).unwrap_or("false".into_any())}
@@ -345,10 +398,12 @@ pub fn ExposureFileListing(id: i64, files: Vec<(String, bool)>) -> impl IntoView
345398
#[component]
346399
pub fn ExposureMain() -> impl IntoView {
347400
let exposure_info = expect_context::<Resource<Result<ExposureInfo, AppError>>>();
401+
let entity_root = expect_context::<Memo<EntityRoot>>();
348402
let file_listing = move || Suspend::new(async move {
403+
let base_href: Arc<str> = entity_root.get().to_string().into();
349404
exposure_info.await.map(|info| view! {
350405
<h1>"Viewing exposure "{info.exposure.id}</h1>
351-
<ExposureFileListing id=info.exposure.id files=info.files/>
406+
<ExposureFileListing base_href=base_href files=info.files/>
352407
})
353408
});
354409

@@ -374,6 +429,7 @@ pub struct ViewPath(pub Option<String>);
374429
#[component]
375430
pub fn ExposureFile() -> impl IntoView {
376431
let views_available_ctx = ViewsAvailableCtx::expect();
432+
let entity_root = expect_context::<Memo<EntityRoot>>();
377433

378434
#[cfg(not(feature = "ssr"))]
379435
on_cleanup({
@@ -398,13 +454,16 @@ pub fn ExposureFile() -> impl IntoView {
398454

399455
let view_key_entry = move |(ef, view_key): (&exposure::ExposureFile, String)| view! {
400456
<li>
401-
<a href=format!("/exposure/{}/{}/{}", ef.exposure_id, ef.workspace_file_path, view_key)>
457+
<a href=format!("{}/{}/{}", entity_root.get().to_string(), ef.workspace_file_path, view_key)>
402458
{view_key.clone()}
403459
</a>
404460
</li>
405461
};
406462

407463
let ep_view = move || Suspend::new(async move {
464+
// XXX not sure why the track need to be here explicitly, when `file` should
465+
// already track the params.
466+
params.track();
408467
match file.await
409468
.map_err(|_| AppError::NotFound)
410469
{
@@ -453,6 +512,7 @@ pub fn ExposureFile() -> impl IntoView {
453512
{views_available_ctx.set_with(move || {
454513
#[cfg(not(feature = "ssr"))]
455514
file.track();
515+
let base_href = entity_root.get().to_string();
456516
async move {
457517
match file.await {
458518
Ok(ResolvedExposurePath::Target(ef, _)) => {
@@ -464,7 +524,7 @@ pub fn ExposureFile() -> impl IntoView {
464524
.into_iter()
465525
.filter_map(|view| {
466526
view.view_key.map(|view_key| ViewsAvailableItem {
467-
href: format!("/exposure/{exposure_id}/{file}/{view_key}"),
527+
href: format!("{base_href}/{file}/{view_key}"),
468528
// TODO should derive from exposure.files when it contains
469529
// title/description
470530
text: view_key,
@@ -662,17 +722,21 @@ pub fn WizardField(
662722
pub fn Wizard() -> impl IntoView {
663723
let wizard_add_file = ServerAction::<WizardAddFile>::new();
664724
let wizard_build = ServerAction::<WizardBuild>::new();
725+
let root = expect_context::<Root>();
665726

666727
let params = use_params::<ExposureParams>();
667728
let wizard_res = Resource::new_blocking(
668729
move || params.get().map(|p| p.id),
669-
|p| async move {
730+
move |p| async move {
670731
match p {
671732
Err(_) => Err(AppError::InternalServerError),
672-
Ok(Some(id)) => wizard(id)
673-
.await
674-
.map(EnforcedOk::notify_into)
675-
.map_err(AppError::from),
733+
Ok(Some(id)) => {
734+
let id = root.build_id(id)?;
735+
wizard(id)
736+
.await
737+
.map(EnforcedOk::notify_into)
738+
.map_err(AppError::from)
739+
}
676740
_ => Err(AppError::NotFound),
677741
}
678742
}

0 commit comments

Comments
 (0)