edc-web-components 0.8.1

A Rust Web Components with patternfly-yew client for EDC
Documentation
use crate::components::DatasetCard;
use crate::models::{AssetItem, DataspaceDataset};
use patternfly_yew::prelude::*;
use std::rc::Rc;
use yew::prelude::*;

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct ListAssetsProps {
  pub asset_items: Vec<AssetItem>,
  pub offset: usize,
  pub limit: usize,
  pub onoffset: Callback<usize>,
  pub onlimit: Callback<usize>,
  pub onshow: Callback<String>,
  #[prop_or(AttrValue::Static("Select"))]
  pub button_label: AttrValue,
}

#[component]
pub fn ListAssets(props: &ListAssetsProps) -> Html {
  let display_cards = use_state(|| true);
  let total_entries: Option<usize> = None;

  let nav_callback = use_callback(
    (
      props.offset,
      props.limit,
      total_entries,
      props.onoffset.clone(),
    ),
    |page: Navigation, (offset, limit, total_entries, onoffset)| {
      let 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,
      };

      onoffset.emit(offset);
    },
  );

  let set_display_cards = use_callback(display_cards.setter(), |value, display_cards_setter| {
    display_cards_setter.set(value);
  });

  let items_render = if *display_cards {
    html!(
      <ListAssetsGallery
        asset_items={props.asset_items.clone()}
        onshow={props.onshow.clone()}
        button_label={props.button_label.clone()}
      />
    )
  } else {
    html!(
      <ListAssetsTable
        asset_items={props.asset_items.clone()}
        onshow={props.onshow.clone()}
        button_label={props.button_label.clone()}
      />
    )
  };

  html!(
    <>
      <Toolbar>
        <ToolbarContent>
          <ToolbarItem>
            <Button
              variant={ButtonVariant::Tertiary}
              icon={Icon::ThLarge}
              disabled={*display_cards}
              onclick={set_display_cards.reform(|_| true)}
            />
            <Button
              variant={ButtonVariant::Tertiary}
              icon={Icon::List}
              disabled={!*display_cards}
              onclick={set_display_cards.reform(|_| false)}
            />
          </ToolbarItem>
          <ToolbarItem r#type={ToolbarItemType::Pagination}>
            <Pagination
              offset={props.offset}
              entries_per_page_choices={vec![5, 10, 25, 50, 100]}
              selected_choice={props.limit}
              onlimit={&props.onlimit}
              onnavigation={&nav_callback}
            />
          </ToolbarItem>
        </ToolbarContent>
      </Toolbar>
      { items_render }
    </>
  )
}

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct ListAssetsTableProps {
  pub asset_items: Vec<AssetItem>,
  pub onshow: Callback<String>,
  #[prop_or(AttrValue::Static("Select"))]
  pub button_label: AttrValue,
}

#[component]
pub fn ListAssetsGallery(props: &ListAssetsTableProps) -> Html {
  let rows = props
    .asset_items
    .clone()
    .into_iter()
    .map(DataspaceDataset::from)
    .map(|dataset| {
      let asset_id = dataset.id.clone();

      html!(
        <DatasetCard
          {dataset}
          on_offer_click={props.onshow.reform(move |_| asset_id.clone())}
          button_label={props.button_label.clone()}
        />
      )
    });

  html!(<Gallery gutter=true>{ for rows }</Gallery>)
}

#[component]
pub fn ListAssetsTable(props: &ListAssetsTableProps) -> Html {
  let header = html_nested! {
    <TableHeader<Columns>>
      <TableColumn<Columns> label="Name" index={Columns::Name} />
      <TableColumn<Columns> label="Base URL" index={Columns::BaseUrl} />
      <TableColumn<Columns> label="Proxy Path" index={Columns::ProxyPath} />
      <TableColumn<Columns> label="Proxy Query Parameters" index={Columns::ProxyQueryParameters} />
      <TableColumn<Columns> label="Proxy Method" index={Columns::ProxyMethod} />
      <TableColumn<Columns> label="Proxy Body" index={Columns::ProxyBody} />
      <TableColumn<Columns> label="" index={Columns::Actions} />
    </TableHeader<Columns>>
  };

  let rows = props
    .asset_items
    .iter()
    .map(|asset_item| AssetRenderer {
      asset_item: asset_item.clone(),
      onshow: props.onshow.clone(),
    })
    .collect();

  let (entries, _) = use_table_data(MemoizedTableModel::new(Rc::new(rows)));

  html!(
    <Table<Columns, UseTableData<Columns, MemoizedTableModel<AssetRenderer>>>
      mode={TableMode::Compact}
      {header}
      {entries}
    />
  )
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum Columns {
  Name,
  BaseUrl,
  ProxyPath,
  ProxyQueryParameters,
  ProxyMethod,
  ProxyBody,
  Actions,
}

#[derive(Clone, Debug)]
struct AssetRenderer {
  asset_item: AssetItem,
  onshow: Callback<String>,
}

impl TableEntryRenderer<Columns> for AssetRenderer {
  fn render_cell(&self, context: CellContext<'_, Columns>) -> Cell {
    match context.column {
      Columns::Name => html!(self.asset_item.name.to_owned()),
      Columns::BaseUrl => html!(self.asset_item.base_url.to_owned()),
      Columns::ProxyPath => html!(self.asset_item.proxy_path),
      Columns::ProxyQueryParameters => html!(self.asset_item.proxy_query_params),
      Columns::ProxyMethod => html!(self.asset_item.proxy_method),
      Columns::ProxyBody => html!(self.asset_item.proxy_body),
      Columns::Actions => {
        let asset_id = self.asset_item.id.to_string();

        html!(<ShowAsset {asset_id} onshow={self.onshow.clone()} />)
      }
    }
    .into()
  }
}

#[derive(Clone, PartialEq, Properties)]
pub struct ShowAssetProps {
  pub asset_id: String,
  pub onshow: Callback<String>,
}

#[function_component]
pub fn ShowAsset(props: &ShowAssetProps) -> Html {
  let onclick = use_callback(
    (props.asset_id.clone(), props.onshow.clone()),
    |_, (asset_id, onshow)| {
      onshow.emit(asset_id.to_string());
    },
  );

  html!(<Button variant={ButtonVariant::Primary} icon={Icon::Eye} {onclick}>{ "Show" }</Button>)
}