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
Reference in a new issue