Skip to content

Commit

Permalink
Render and populate the portlets
Browse files Browse the repository at this point in the history
- Provide conversion trait for ExposureFile for the views available
  links.
- Just trying out the bare signal for the views available, though it may
  be a unique situation given the routing is also provided by that
  component.
  • Loading branch information
metatoaster committed Aug 30, 2024
1 parent d376a07 commit 6e5ec88
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 44 deletions.
2 changes: 1 addition & 1 deletion pmrapp/src/app/portlet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pub use self::navigation::{
NavigationCtx,
};
pub use self::views_available::{
ViewAvailableItem,
ViewsAvailableItem,
ViewsAvailableCtx,
};
19 changes: 15 additions & 4 deletions pmrapp/src/app/portlet/navigation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use leptos::prelude::*;
use leptos_router::components::A;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -30,10 +31,20 @@ pub(in crate::app) fn Navigation() -> impl IntoView {
// can be calculated to avoid the sidebar grid space being reserved?
// Unless of course there is a CSS-based solution.
match resource {
Some(resource) => resource.await.0.map(|navigation| view! {
<section>
<h4>"Navigation: "{navigation.len()}</h4>
</section>
Some(resource) => resource.await.0.map(|navigation| {
let view = navigation.into_iter()
.map(|NavigationItem { href, text, .. }| view! {
<li><A href>{text}</A></li>
})
.collect_view();
view! {
<section>
<h4>"Navigation"</h4>
<ul>
{view}
</ul>
</section>
}
}),
_ => None,
}
Expand Down
47 changes: 39 additions & 8 deletions pmrapp/src/app/portlet/views_available.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use leptos::prelude::*;
use leptos_router::components::A;
use pmrcore::exposure::ExposureFile;
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug)]
pub struct ViewAvailableItem {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ViewsAvailableItem {
pub href: String,
pub text: String,
pub title: Option<String>,
}

#[derive(Clone, Debug)]
pub struct ViewsAvailableCtx(pub Option<Vec<ViewAvailableItem>>);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ViewsAvailableCtx(pub Option<Vec<ViewsAvailableItem>>);

#[component]
pub(in crate::app) fn ViewsAvailable() -> impl IntoView {
Expand All @@ -22,10 +25,20 @@ pub(in crate::app) fn ViewsAvailable() -> impl IntoView {
// can be calculated to avoid the sidebar grid space being reserved?
// Unless of course there is a CSS-based solution.
match resource {
Some(resource) => resource.await.0.map(|views_available| view! {
<section>
<h4>"Views Available: "{views_available.len()}</h4>
</section>
Some(resource) => resource.await.0.map(|views_available| {
let view = views_available.into_iter()
.map(|ViewsAvailableItem { href, text, .. }| view! {
<li><A href>{text}</A></li>
})
.collect_view();
view! {
<section>
<h4>"Views Available"</h4>
<ul>
{view}
</ul>
</section>
}
}),
_ => None,
}
Expand All @@ -34,3 +47,21 @@ pub(in crate::app) fn ViewsAvailable() -> impl IntoView {
}
}
}

impl From<&ExposureFile> for ViewsAvailableCtx {
fn from(item: &ExposureFile) -> Self {
let exposure_id = item.exposure_id;
let file = item.workspace_file_path.clone();
Self(item.views.as_ref().map(|views| views.iter()
.filter_map(|view| {
view.view_key.as_ref().map(|view_key| ViewsAvailableItem {
href: format!("/exposure/{exposure_id}/{file}/{view_key}"),
// TODO should derive from exposure.files when it contains title/description
text: view_key.clone(),
title: None,
})
})
.collect::<Vec<_>>()
).into())
}
}
39 changes: 26 additions & 13 deletions pmrapp/src/exposure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ pub fn Exposure() -> impl IntoView {
.into_iter()
.filter_map(move |(file, flag)| {
flag.then(|| {
let href = format!("/exposure/{exposure_id}/{file}");
let href = format!("/exposure/{exposure_id}/{file}/");
let text = file.clone();
let title = None;
NavigationItem { href, text, title }
Expand Down Expand Up @@ -240,10 +240,20 @@ pub fn ExposureFile() -> impl IntoView {
</li>
};

// TODO could this be encapsulated in a function provided by the portlet?
let (views_available, set_views_available) = signal(None::<ViewsAvailableCtx>);
expect_context::<ArcWriteSignal<Option<Resource<ViewsAvailableCtx>>>>().set(Some(
Resource::new(
move || views_available.get(),
|views_available| async move { views_available.unwrap_or(ViewsAvailableCtx(None)) },
)
));

let ep_view = move || Suspend::new(async move {
match file.await {
// TODO figure out how to redirect to the workspace.
Ok(Ok((ef, Ok((efv, view_path))))) => {
set_views_available.set(Some((&ef).into()));
let view_key = efv.view_key.clone();
let view_key = EFView::from_str(&view_key
.expect("API failed to produce a fully formed ExposureFileView")
Expand All @@ -255,18 +265,21 @@ pub fn ExposureFile() -> impl IntoView {
<ExposureFileView view_key/>
}.into_any())
}
Ok(Ok((ef, Err(view_keys)))) => Ok(view! {
<h1>
"Exposure "{ef.exposure_id}
" - ExposureFile "{ef.workspace_file_path.clone()}
" - Listing of all views"
</h1>
<ul>{
view_keys.into_iter()
.map(|k| view_key_entry((&ef, k)))
.collect_view()
}</ul>
}.into_any()),
Ok(Ok((ef, Err(view_keys)))) => {
set_views_available.set(Some((&ef).into()));
Ok(view! {
<h1>
"Exposure "{ef.exposure_id}
" - ExposureFile "{ef.workspace_file_path.clone()}
" - Listing of all views"
</h1>
<ul>{
view_keys.into_iter()
.map(|k| view_key_entry((&ef, k)))
.collect_view()
}</ul>
}.into_any())
},
Ok(Err(e)) => match e {
AppError::Redirect(path) => Ok(view! { <Redirect path show_link=true/> }.into_any()),
_ => Err(AppError::NotFound),
Expand Down
42 changes: 25 additions & 17 deletions pmrapp/src/exposure/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,17 @@ pub async fn resolve_exposure_path(
let ec = platform.get_exposure(id).await?;

match ec.resolve_file_view(path.as_ref()).await {
(Ok(efc), Ok(efvc)) => Ok(Ok((
efc.exposure_file().clone_inner(),
Ok((
efvc.exposure_file_view().clone_inner(),
efvc.view_path().map(str::to_string),
)),
))),
(Ok(efc), Ok(efvc)) => {
// to ensure the views is populated
efc.exposure_file().views().await?;
Ok(Ok((
efc.exposure_file().clone_inner(),
Ok((
efvc.exposure_file_view().clone_inner(),
efvc.view_path().map(str::to_string),
)),
)))
},
(_, Err(CtrlError::None)) => {
// since the request path has a direct hit on file, doesn't
// matter if ExposureFileCtrl found or not.
Expand All @@ -106,16 +110,20 @@ pub async fn resolve_exposure_path(
// to facilitate this custom redirect handling.
Ok(Err(AppError::Redirect(path).into()))
},
(Ok(efc), Err(CtrlError::EFVCNotFound(viewstr))) if viewstr == "" => Ok(Ok((
efc.exposure_file().clone_inner(),
Err(efc.exposure_file()
.views()
.await?
.iter()
.filter_map(|v| v.view_key().map(str::to_string))
.collect::<Vec<_>>()
),
))),
(Ok(efc), Err(CtrlError::EFVCNotFound(viewstr))) if viewstr == "" => {
// to ensure the views is populated
efc.exposure_file().views().await?;
Ok(Ok((
efc.exposure_file().clone_inner(),
Err(efc.exposure_file()
.views()
.await?
.iter()
.filter_map(|v| v.view_key().map(str::to_string))
.collect::<Vec<_>>()
),
)))
},
// CtrlError::UnknownPath(_) | CtrlError::EFVCNotFound(_)
_ => Err(AppError::NotFound.into()),
}
Expand Down
9 changes: 8 additions & 1 deletion pmrcore/src/exposure/refs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ impl ExposureFileRef<'_> {
self.inner
}
pub fn clone_inner(&self) -> ExposureFile {
self.inner.clone()
let mut inner = self.inner.clone();
inner.views = self.views.get().map(|v| v
.iter()
.map(|v| v.clone_inner())
.collect::<Vec<_>>()
.into()
);
inner
}
}

Expand Down

0 comments on commit 6e5ec88

Please sign in to comment.