yew: initial todo app skeleton
This commit is contained in:
		
							parent
							
								
									4ed90a4403
								
							
						
					
					
						commit
						e439bddad0
					
				
					 7 changed files with 7874 additions and 0 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					target/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
							
								
								
									
										1519
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1519
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										28
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "todo-yew-hasura"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					graphql_client = { version = "0.11.0" }
 | 
				
			||||||
 | 
					serde_json = "1.0.87"
 | 
				
			||||||
 | 
					wasm-bindgen = "0.2.83"
 | 
				
			||||||
 | 
					wasm-bindgen-futures = "0.4.33"
 | 
				
			||||||
 | 
					web-sys = { version = "0.3.60", features = [
 | 
				
			||||||
 | 
					  "Request",
 | 
				
			||||||
 | 
					  "RequestInit",
 | 
				
			||||||
 | 
					  "RequestMode",
 | 
				
			||||||
 | 
					  "Response",
 | 
				
			||||||
 | 
					] }
 | 
				
			||||||
 | 
					yew = "0.19.3"
 | 
				
			||||||
 | 
					yew-router = "0.16.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gloo = "0.7"
 | 
				
			||||||
 | 
					gloo-utils = "0.1"
 | 
				
			||||||
 | 
					serde = { version = "1.0.147", features = ["derive"] }
 | 
				
			||||||
 | 
					console_error_panic_hook = "0.1.7"
 | 
				
			||||||
 | 
					reqwest = { version = "^0.11", features = ["json"] }
 | 
				
			||||||
 | 
					log = "0.4.3"
 | 
				
			||||||
 | 
					env_logger = "0.5.10"
 | 
				
			||||||
							
								
								
									
										10
									
								
								graphql/all_users_todos.graphql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								graphql/all_users_todos.graphql
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					query AllUsersTodos {
 | 
				
			||||||
 | 
					  users {
 | 
				
			||||||
 | 
					    id
 | 
				
			||||||
 | 
					    name
 | 
				
			||||||
 | 
					    todos(order_by: {created_at: asc}, limit: 5) {
 | 
				
			||||||
 | 
					      title
 | 
				
			||||||
 | 
					      created_at
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6087
									
								
								graphql/schema.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6087
									
								
								graphql/schema.json
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										12
									
								
								index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								index.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
 | 
					    <title>Todo app with hasura and yew frontend</title>
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .menu a {
 | 
				
			||||||
 | 
					            padding: 5px;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										216
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,216 @@
 | 
				
			||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    error::Error,
 | 
				
			||||||
 | 
					    fmt::{self, Debug, Display, Formatter},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use graphql_client::GraphQLQuery;
 | 
				
			||||||
 | 
					use wasm_bindgen::{prelude::*, JsCast};
 | 
				
			||||||
 | 
					use wasm_bindgen_futures::JsFuture;
 | 
				
			||||||
 | 
					use web_sys::{Request, RequestInit, RequestMode, Response};
 | 
				
			||||||
 | 
					use serde_json::{Value, from_str, json, from_value};
 | 
				
			||||||
 | 
					use serde::Deserialize;
 | 
				
			||||||
 | 
					use yew::prelude::*;
 | 
				
			||||||
 | 
					use yew_router::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Timestamptz = String;
 | 
				
			||||||
 | 
					#[derive(GraphQLQuery)]
 | 
				
			||||||
 | 
					#[graphql(
 | 
				
			||||||
 | 
					    schema_path = "graphql/schema.json",
 | 
				
			||||||
 | 
					    query_path = "graphql/all_users_todos.graphql",
 | 
				
			||||||
 | 
					    response_derives = "Debug",
 | 
				
			||||||
 | 
					    normalization = "rust"
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					struct AllUsersTodos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, PartialEq)]
 | 
				
			||||||
 | 
					pub struct FetchError {
 | 
				
			||||||
 | 
					    err: JsValue,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for FetchError {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
 | 
				
			||||||
 | 
					        Debug::fmt(&self.err, f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Error for FetchError {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<JsValue> for FetchError {
 | 
				
			||||||
 | 
					    fn from(value: JsValue) -> Self {
 | 
				
			||||||
 | 
					        Self { err: value }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub enum FetchState<T> {
 | 
				
			||||||
 | 
					    NotFetching,
 | 
				
			||||||
 | 
					    Fetching,
 | 
				
			||||||
 | 
					    Success(T),
 | 
				
			||||||
 | 
					    Failed(FetchError),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub enum Msg {
 | 
				
			||||||
 | 
					    SetState(FetchState<all_users_todos::ResponseData>),
 | 
				
			||||||
 | 
					    GetData,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn fetch_gql_data<T: for<'a> Deserialize<'a>>(query: &str) -> Result<T, FetchError> {
 | 
				
			||||||
 | 
					    let mut req_opts = RequestInit::new();
 | 
				
			||||||
 | 
					    req_opts.method("POST");
 | 
				
			||||||
 | 
					    req_opts.body(Some(&JsValue::from_str(query)));
 | 
				
			||||||
 | 
					    req_opts.mode(RequestMode::Cors);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let request = Request::new_with_str_and_init("http://localhost:8080/v1/graphql", &req_opts)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let window = gloo_utils::window();
 | 
				
			||||||
 | 
					    let resp_value =
 | 
				
			||||||
 | 
					        JsFuture::from(window.fetch_with_request(&request)).await?;
 | 
				
			||||||
 | 
					    let resp: Response = resp_value.dyn_into().unwrap();
 | 
				
			||||||
 | 
					    let resp_text = JsFuture::from(resp.text()?).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let data_str = resp_text.as_string().unwrap();
 | 
				
			||||||
 | 
					    let data_value: Value = from_str(&data_str).unwrap();
 | 
				
			||||||
 | 
					    from_value(data_value["data"].clone())
 | 
				
			||||||
 | 
					        .map_err(|e| FetchError { err: JsValue::from(format!("{}", e)) })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Routable, PartialEq, Clone, Debug)]
 | 
				
			||||||
 | 
					pub enum Routes {
 | 
				
			||||||
 | 
					    #[at("/users")]
 | 
				
			||||||
 | 
					    Users,
 | 
				
			||||||
 | 
					    #[at("/todos")]
 | 
				
			||||||
 | 
					    Todos,
 | 
				
			||||||
 | 
					    #[at("/")]
 | 
				
			||||||
 | 
					    Index,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct App;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[function_component(NavGlobal)]
 | 
				
			||||||
 | 
					fn nav_global() -> Html {
 | 
				
			||||||
 | 
					    html! {
 | 
				
			||||||
 | 
					        <div class={classes!("menu")}>
 | 
				
			||||||
 | 
					            <Link<Routes> to={ Routes::Index }> { "Index" }</Link<Routes>>
 | 
				
			||||||
 | 
					            <Link<Routes> to={ Routes::Users }> { "Users" }</Link<Routes>>
 | 
				
			||||||
 | 
					            <Link<Routes> to={ Routes::Todos }> { "Todos" }</Link<Routes>>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AllUsersTodosComponent {
 | 
				
			||||||
 | 
					    data: FetchState<all_users_todos::ResponseData>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn view_all_users_todos(all_users: &all_users_todos::ResponseData) -> Html {
 | 
				
			||||||
 | 
					        html! {
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					                <h1>{ "All users and their todos" }</h1>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    all_users.users.iter().map(|user| {
 | 
				
			||||||
 | 
					                        html! {
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <h3>{ user.name.to_owned() }</h3>
 | 
				
			||||||
 | 
					                                <ul>
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    user.todos.iter().map(|todo| {
 | 
				
			||||||
 | 
					                                        html! {
 | 
				
			||||||
 | 
					                                            <li>
 | 
				
			||||||
 | 
					                                            <span>{ todo.title.to_owned() }</span>
 | 
				
			||||||
 | 
					                                            <span>{ todo.created_at.to_owned() }</span>
 | 
				
			||||||
 | 
					                                            </li>
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }).collect::<Html>()
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                </ul>
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }).collect::<Html>()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Component for AllUsersTodosComponent {
 | 
				
			||||||
 | 
					    type Message = Msg;
 | 
				
			||||||
 | 
					    type Properties = ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn create(_ctx: &Context<Self>) -> Self {
 | 
				
			||||||
 | 
					        Self { data: FetchState::NotFetching }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn view(&self, _ctx: &Context<Self>) -> Html {
 | 
				
			||||||
 | 
					        match &self.data {
 | 
				
			||||||
 | 
					            FetchState::NotFetching => html! { "NotFetching" },
 | 
				
			||||||
 | 
					            FetchState::Fetching => html! { "Fetching" },
 | 
				
			||||||
 | 
					            FetchState::Success(all_users_todos_data) => view_all_users_todos(all_users_todos_data),
 | 
				
			||||||
 | 
					            FetchState::Failed(err) => html! { err },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
 | 
				
			||||||
 | 
					        if first_render {
 | 
				
			||||||
 | 
					            ctx.link().send_message(Msg::GetData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
 | 
				
			||||||
 | 
					        match msg {
 | 
				
			||||||
 | 
					            Msg::SetState(fetch_state) => {
 | 
				
			||||||
 | 
					                self.data = fetch_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Msg::GetData => {
 | 
				
			||||||
 | 
					                ctx.link().send_future(async {
 | 
				
			||||||
 | 
					                    match fetch_gql_data(&users_todos_query().await).await
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Ok(data) => Msg::SetState(FetchState::Success(data)),
 | 
				
			||||||
 | 
					                        Err(err) => Msg::SetState(FetchState::Failed(err)),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ctx.link().send_message(Msg::SetState(FetchState::Fetching));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn users_todos_query() -> String {
 | 
				
			||||||
 | 
					    let build_query = AllUsersTodos::build_query(all_users_todos::Variables {});
 | 
				
			||||||
 | 
					    json!(build_query).to_string()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn navigate_to_view(target: &Routes) -> Html {
 | 
				
			||||||
 | 
					    match target {
 | 
				
			||||||
 | 
					        Routes::Users => html!{ "TODO: All Users" },
 | 
				
			||||||
 | 
					        // Routes::Todos => html!{ <Todos /> },
 | 
				
			||||||
 | 
					        // Routes::Index => html!{ <Home /> },
 | 
				
			||||||
 | 
					        Routes::Todos => html!{ <div>{ "TODO: All Todos" }</div> },
 | 
				
			||||||
 | 
					        Routes::Index => html!{ <AllUsersTodosComponent /> },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Component for App {
 | 
				
			||||||
 | 
					    type Message = ();
 | 
				
			||||||
 | 
					    type Properties = ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn create(_ctx: &Context<Self>) -> Self {
 | 
				
			||||||
 | 
					        Self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn view(&self, _ctx: &Context<Self>) -> Html {
 | 
				
			||||||
 | 
					        html! {
 | 
				
			||||||
 | 
					            <BrowserRouter>
 | 
				
			||||||
 | 
					                <NavGlobal />
 | 
				
			||||||
 | 
					                <main class="ps-relative t64">
 | 
				
			||||||
 | 
					                    <Switch<Routes> render={ Switch::render(navigate_to_view) } />
 | 
				
			||||||
 | 
					                </main>
 | 
				
			||||||
 | 
					            </BrowserRouter>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    yew::start_app::<App>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue