edc-web-components 0.8.1

A Rust Web Components with patternfly-yew client for EDC
Documentation
use crate::components::DatasetCard;
use crate::contexts::use_edc_connector_context;
use crate::models::{AssetItem, DataspaceDataset};
use edc_connector_client::EdcConnectorApiVersion;
use edc_connector_client::types::query::Query;
use patternfly_yew::prelude::*;
use yew::prelude::*;
use yew::suspense::use_future_with;

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct AssetSelectorProps {
  pub selected_assets: Vec<AssetItem>,
  pub onselect: Callback<Vec<AssetItem>>,
  #[prop_or("selectable-asset".to_string())]
  pub select_id: String,
}

#[component]
pub fn AssetSelector(props: &AssetSelectorProps) -> Html {
  html!(
    <Suspense>
      <AssetSelectorInner
        onselect={props.onselect.clone()}
        selected_assets={props.selected_assets.clone()}
        select_id={props.select_id.clone()}
      />
    </Suspense>
  )
}

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct AssetSelectorInnerProps {
  pub selected_assets: Vec<AssetItem>,
  pub onselect: Callback<Vec<AssetItem>>,
  pub select_id: String,
}

#[component]
fn AssetSelectorInner(props: &AssetSelectorInnerProps) -> HtmlResult {
  let limit = use_state(|| 10usize);
  let offset = use_state(|| 0usize);
  let force_refresh = use_state(|| 0usize);
  let edc_connector_context = use_edc_connector_context();

  let assets = use_future_with(
    (edc_connector_context, *limit, *offset, *force_refresh),
    |parameters| async move {
      let (edc_connector_context, limit, offset, _) = (*parameters).clone();

      let query = Query::builder()
        .limit(limit as u32)
        .offset(offset as u32)
        .build();

      if let Some(client) = edc_connector_context.get_client() {
        client
          .assets(EdcConnectorApiVersion::V4)
          .query(query)
          .await
          .unwrap_or_default()
          .into_iter()
          .map(AssetItem::from)
          .collect::<Vec<_>>()
      } else {
        vec![]
      }
    },
  )?;

  let assets = (*assets).clone();

  let on_selected = use_callback(
    (props.selected_assets.clone(), props.onselect.clone()),
    |(asset_id, selected), (selected_assets, onselect)| {
      let mut selected_assets = selected_assets.clone();

      if selected {
        selected_assets.push(asset_id);
      } else {
        selected_assets.retain(|id| id != &asset_id);
      }

      onselect.emit(selected_assets);
    },
  );

  let items = assets.iter().map(|asset_item| {
    let selected = props.selected_assets.contains(asset_item);
    let dataset = DataspaceDataset::from(asset_item.clone());
    let asset_item = asset_item.clone();

    html!(
      <DatasetCard
        {dataset}
        {selected}
        on_selected={on_selected.reform(move |selected| (asset_item.clone(), selected) )}
      />
    )
  });

  let onlimit = use_callback(limit.setter(), |limit, limit_setter| {
    limit_setter.set(limit)
  });

  let total_entries = Option::<usize>::None;

  let nav_callback = use_callback(
    (offset.clone(), *limit, total_entries),
    |page: Navigation, (offset, limit, total_entries)| {
      let new_offset = match page {
        Navigation::First => 0,
        Navigation::Last => (total_entries.unwrap_or_default().saturating_sub(1) / limit) * limit,
        Navigation::Previous => **offset - limit,
        Navigation::Next => **offset + limit,
        Navigation::Page(n) => n * limit,
      };
      offset.set(new_offset);
    },
  );

  Ok(html!(
    <Stack gutter=true>
      <StackItem>
        <Pagination
          offset={*offset}
          entries_per_page_choices={vec![5, 10, 25, 50, 100]}
          selected_choice={*limit}
          onlimit={&onlimit}
          onnavigation={&nav_callback}
        />
      </StackItem>
      <StackItem>
        <Gallery gutter=true>{ for items }</Gallery>
      </StackItem>
    </Stack>
  ))
}