Skip to content

Commit

Permalink
tweak: paginated & refreshable source list (#453)
Browse files Browse the repository at this point in the history
* handle paginating through lens sources

* round edit button again & set default background color
  • Loading branch information
a5huynh committed May 17, 2023
1 parent b02fcbd commit 742974d
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 50 deletions.
2 changes: 1 addition & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
<script src="https://apis.google.com/js/api.js"></script>
<script src="https://accounts.google.com/gsi/client"></script>
</head>
<body class="text-white bg-transparent"></body>
<body class="text-white bg-neutral-800"></body>
</html>
31 changes: 30 additions & 1 deletion apps/web/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ pub struct Lens {
pub example_questions: Vec<String>,
pub example_docs: Vec<String>,
pub is_public: bool,
pub sources: Vec<LensSource>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -234,8 +233,18 @@ pub struct LensSource {
pub status: String,
}

#[derive(Deserialize)]
pub struct GetLensSourceResponse {
pub page: usize,
pub num_items: usize,
pub num_pages: usize,
pub results: Vec<LensSource>,
}

#[derive(Error, Debug)]
pub enum ApiError {
#[error("You need to sign in.")]
Unauthorized,
#[error("Unable to make request: {0}")]
RequestError(#[from] reqwest::Error),
#[error("Api Error: {0}")]
Expand Down Expand Up @@ -303,6 +312,26 @@ impl ApiClient {
.await?)
}

pub async fn lens_retrieve_sources(
&self,
id: &str,
page: usize,
) -> Result<GetLensSourceResponse, ApiError> {
match &self.token {
Some(token) => Ok(self
.client
.get(format!("{}/user/lenses/{}/sources", self.endpoint, id))
.query(&[("page".to_string(), page.to_string())])
.bearer_auth(token)
.send()
.await?
.error_for_status()?
.json::<GetLensSourceResponse>()
.await?),
None => Err(ApiError::Unauthorized),
}
}

pub async fn lens_add_source(
&self,
lens: &str,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub fn lens_list(props: &LensListProps) -> Html {
html! {}
} else {
html! {
<Btn size={BtnSize::Sm} _type={BtnType::Borderless} classes="ml-auto" onclick={on_edit}>
<Btn size={BtnSize::Sm} _type={BtnType::Borderless} classes="ml-auto rounded" onclick={on_edit}>
<icons::PencilIcon height="h-3" width="w-3" />
</Btn>
}
Expand Down
140 changes: 122 additions & 18 deletions apps/web/src/pages/lens_edit.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use gloo::timers::callback::Timeout;
use ui_components::{btn::Btn, icons};
use ui_components::{
btn::{Btn, BtnSize},
icons,
results::Paginator,
};
use wasm_bindgen::{
prelude::{wasm_bindgen, Closure},
JsValue,
Expand All @@ -10,7 +14,10 @@ use yew::prelude::*;
use yew_router::scope_ext::RouterScopeExt;

use crate::{
client::{ApiError, Lens, LensAddDocType, LensAddDocument, LensDocType, LensSource},
client::{
ApiError, GetLensSourceResponse, Lens, LensAddDocType, LensAddDocument, LensDocType,
LensSource,
},
AuthStatus,
};

Expand All @@ -31,9 +38,21 @@ extern "C" {
fn clear_timeout(handle: JsValue);
}

#[derive(Clone)]
pub struct LensSourcePaginator {
page: usize,
num_items: usize,
num_pages: usize,
}

pub struct CreateLensPage {
pub lens_identifier: String,
pub lens_data: Option<Lens>,

pub lens_sources: Option<Vec<LensSource>>,
pub lens_source_paginator: Option<LensSourcePaginator>,
pub is_loading_lens_sources: bool,

pub auth_status: AuthStatus,
pub add_url_error: Option<String>,
pub _context_listener: ContextHandle<AuthStatus>,
Expand All @@ -54,8 +73,10 @@ pub enum Msg {
AddUrlError(String),
FilePicked { token: String, url: String },
Reload,
ReloadSources(usize),
Save { display_name: String },
SetLensData(Lens),
SetLensSources(GetLensSourceResponse),
OpenCloudFilePicker,
UpdateContext(AuthStatus),
UpdateDisplayName,
Expand All @@ -74,11 +95,15 @@ impl Component for CreateLensPage {
.context(ctx.link().callback(Msg::UpdateContext))
.expect("No Message Context Provided");

ctx.link().send_message(Msg::Reload);
ctx.link()
.send_message_batch(vec![Msg::Reload, Msg::ReloadSources(0)]);

Self {
lens_identifier: ctx.props().lens.clone(),
lens_data: None,
lens_sources: None,
lens_source_paginator: None,
is_loading_lens_sources: false,
auth_status,
add_url_error: None,
_context_listener: context_listener,
Expand All @@ -92,7 +117,15 @@ impl Component for CreateLensPage {
let new_lens = ctx.props().lens.clone();
if self.lens_identifier != new_lens {
self.lens_identifier = new_lens;
ctx.link().send_message(Msg::Reload);

let page = self
.lens_source_paginator
.as_ref()
.map(|x| x.page)
.unwrap_or(0);

ctx.link()
.send_message_batch(vec![Msg::Reload, Msg::ReloadSources(page)]);
true
} else {
false
Expand Down Expand Up @@ -182,6 +215,30 @@ impl Component for CreateLensPage {

false
}
Msg::ReloadSources(page) => {
let auth_status = self.auth_status.clone();
let identifier = self.lens_identifier.clone();
let link = link.clone();
self.is_loading_lens_sources = true;
spawn_local(async move {
let api: crate::client::ApiClient = auth_status.get_client();
match api.lens_retrieve_sources(&identifier, page).await {
Ok(lens) => link.send_message(Msg::SetLensSources(lens)),
Err(ApiError::ClientError(msg)) => {
// Unauthorized
if msg.code == 400 {
let navi = link.navigator().expect("No navigator");
navi.push(&crate::Route::Start);
}

log::error!("error retrieving lens: {msg}");
}
Err(err) => log::error!("error retrieving lens: {err}"),
}
});

true
}
Msg::Save { display_name } => {
let auth_status = self.auth_status.clone();
let identifier = self.lens_identifier.clone();
Expand All @@ -200,6 +257,17 @@ impl Component for CreateLensPage {
self.lens_data = Some(lens_data);
true
}
Msg::SetLensSources(sources) => {
self.is_loading_lens_sources = false;
self.lens_source_paginator = Some(LensSourcePaginator {
page: sources.page,
num_items: sources.num_items,
num_pages: sources.num_pages,
});

self.lens_sources = Some(sources.results);
true
}
Msg::OpenCloudFilePicker => {
let link = link.clone();
spawn_local(async move {
Expand All @@ -221,7 +289,12 @@ impl Component for CreateLensPage {
}
Msg::UpdateContext(auth_status) => {
self.auth_status = auth_status;
link.send_message(Msg::Reload);
let page = self
.lens_source_paginator
.as_ref()
.map(|x| x.page)
.unwrap_or(0);
link.send_message_batch(vec![Msg::Reload, Msg::ReloadSources(page)]);
true
}
Msg::UpdateDisplayName => {
Expand Down Expand Up @@ -251,17 +324,14 @@ impl Component for CreateLensPage {
fn view(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();

let sources = self
.lens_data
.as_ref()
.map(|x| x.sources.clone())
.unwrap_or_default();
let sources = self.lens_sources.as_ref().cloned().unwrap_or_default();

let source_html = sources
.iter()
.map(|x| html! { <LensSourceComponent source={x.clone()} /> })
.collect::<Html>();

let is_loading_sources = self.is_loading_lens_sources;
html! {
<div>
<div class="flex flex-row items-center px-8 pt-6">
Expand Down Expand Up @@ -298,12 +368,41 @@ impl Component for CreateLensPage {
</div>
<div><Btn onclick={link.callback(|_| Msg::OpenCloudFilePicker)}>{"Add data from Google Drive"}</Btn></div>
</div>
<div class="flex flex-col">
<div class="mb-2 text-sm font-semibold uppercase text-cyan-500">
{format!("Sources ({})", sources.len())}
</div>
<div class="flex flex-col">{source_html}</div>
</div>
{if let Some(paginator) = self.lens_source_paginator.clone() {
html! {
<div class="flex flex-col">
<div class="flex flex-row mb-2 text-sm font-semibold uppercase text-cyan-500">
<div>{format!("Sources ({})", paginator.num_items)}</div>
<div class="ml-auto">
<Btn size={BtnSize::Sm} onclick={link.callback(move |_| Msg::ReloadSources(paginator.page))}>
<icons::RefreshIcon
classes="mr-1"
width="w-3"
height="h-3"
animate_spin={is_loading_sources}
/>
{"Refresh"}
</Btn>
</div>
</div>
<div class="flex flex-col">{source_html}</div>
{if paginator.num_pages > 1 {
html! {
<div>
<Paginator
disabled={is_loading_sources}
cur_page={paginator.page}
num_pages={paginator.num_pages}
on_select_page={link.callback(Msg::ReloadSources)}
/>
</div>
}
} else {
html! {}
}}
</div>
}
} else { html! {} }}
</div>
</div>
}
Expand All @@ -321,7 +420,12 @@ fn lens_source_comp(props: &LensSourceComponentProps) -> Html {

let doc_type_icon = match source.doc_type {
LensDocType::GDrive => html! { <icons::GDrive /> },
LensDocType::Web => html! { <icons::GlobeIcon /> },
LensDocType::Web => html! {
<div class="flex flex-col items-center">
<icons::GlobeIcon width="w-4" height="h-4" />
<div class="text-xs">{"Web"}</div>
</div>
},
};

let status_icon = match source.status.as_ref() {
Expand All @@ -330,7 +434,7 @@ fn lens_source_comp(props: &LensSourceComponentProps) -> Html {
};

html! {
<div class="border-b border-neutral-700 py-4 flex flex-row items-center gap-2">
<div class="py-4 flex flex-row items-center gap-2">
<div class="flex-none px-2">
{doc_type_icon}
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct AppPageProps {
#[function_component]
pub fn AppPage(props: &AppPageProps) -> Html {
html! {
<div class="flex-col flex-1 h-screen overflow-y-auto bg-neutral-800">
<div class="flex-col flex-1">
{props.children.clone()}
</div>
}
Expand Down
Loading

0 comments on commit 742974d

Please sign in to comment.