11use leptos:: logging;
2+ use leptos:: context:: Provider ;
23use leptos:: prelude:: * ;
34use leptos_meta:: * ;
45use 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]
135168pub 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 ) ]
199236pub 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]
346399pub 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]
375430pub 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(
662722pub 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