Web UI with a functional async-await boundary!

This commit is contained in:
projectmoon 2021-06-05 21:42:13 +00:00
parent 6154bf2c0b
commit 6eca450346
6 changed files with 167 additions and 139 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ bigboy
.tmp* .tmp*
node_modules node_modules
dist/ dist/
yarn-error.log

3
Cargo.lock generated
View File

@ -3341,7 +3341,10 @@ name = "tenebrous-web-ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"grpc-web-client", "grpc-web-client",
"js-sys",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"yew", "yew",
"yew-router", "yew-router",
"yewdux", "yewdux",

View File

@ -15,7 +15,10 @@ yewtil = {version = "0.3" }
yew-router = {version = "0.14" } yew-router = {version = "0.14" }
yewdux = {version = "^0.6" } yewdux = {version = "^0.6" }
wasm-bindgen = { version = "0.2" } wasm-bindgen = { version = "0.2" }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
grpc-web-client = "0.1" grpc-web-client = "0.1"
web-sys = "0.3"
# hopefully we can add grpc-web later instead of graphql. # hopefully we can add grpc-web later instead of graphql.
# prost = { version = "0.7.0", default-features = false } # prost = { version = "0.7.0", default-features = false }

View File

@ -1,98 +1,84 @@
use oaths::OathsList; use rooms::RoomList;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use yew::prelude::*; use yew::prelude::*;
use yew_router::{components::RouterAnchor, prelude::*}; use yew_router::{components::RouterAnchor, prelude::*};
pub mod grpc; pub mod grpc;
pub mod oaths; pub mod rooms;
#[derive(Switch, Clone, Debug)] #[derive(Switch, Clone, Debug)]
pub enum AppRoute { pub enum AppRoute {
#[to = "/oaths"] #[to = "/rooms"]
Oaths, Rooms,
#[to = "/commitments"] #[to = "/rooms/{room_id}"]
Commitments, Room(String),
#[to = "/studies"]
Studies,
#[to = "/divination"]
RunicDivination,
#[to = "/"] #[to = "/"]
Index, Index,
} }
type AppRouter = Router<AppRoute>; type AppRouter = Router<AppRoute>;
type AppAnchor = RouterAnchor<AppRoute>; type AppAnchor = RouterAnchor<AppRoute>; //For rendering clickable links.
fn render_route(switch: AppRoute) -> Html { fn render_route(switch: AppRoute) -> Html {
match switch { match switch {
AppRoute::Oaths => { AppRoute::Rooms => {
html! { html! {
<OathsList /> <RoomList />
} }
} }
AppRoute::Commitments => { AppRoute::Room(room_id) => {
html! { html! {
<div>{"This is the commitments page."}</div> <div>{"This is the specifi roompage."}</div>
}
}
AppRoute::Studies => {
html! {
<div>{"This is the studies page."}</div>
}
}
AppRoute::RunicDivination => {
html! {
<div>{"This is the runic divination page."}</div>
} }
} }
AppRoute::Index => { AppRoute::Index => {
html! { html! {
<div>{"This is the index."}</div> <RoomList />
} }
} }
} }
} }
struct AppMenu; // struct AppMenu;
impl Component for AppMenu { // impl Component for AppMenu {
type Message = (); // type Message = ();
type Properties = (); // type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self { // fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self // Self
} // }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { // fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false // false
} // }
fn change(&mut self, _: Self::Properties) -> ShouldRender { // fn change(&mut self, _: Self::Properties) -> ShouldRender {
false // false
} // }
fn view(&self) -> Html { // fn view(&self) -> Html {
html! { // html! {
<ul> // <ul>
<li> // <li>
<AppAnchor route=AppRoute::Index>{"Home"}</AppAnchor> // <AppAnchor route=AppRoute::Index>{"Home"}</AppAnchor>
</li> // </li>
<li> // <li>
<AppAnchor route=AppRoute::Oaths>{"Oaths"}</AppAnchor> // <AppAnchor route=AppRoute::Oaths>{"Oaths"}</AppAnchor>
</li> // </li>
<li> // <li>
<AppAnchor route=AppRoute::Commitments>{"Commitments"}</AppAnchor> // <AppAnchor route=AppRoute::Commitments>{"Commitments"}</AppAnchor>
</li> // </li>
<li> // <li>
<AppAnchor route=AppRoute::Studies>{"Studies"}</AppAnchor> // <AppAnchor route=AppRoute::Studies>{"Studies"}</AppAnchor>
</li> // </li>
<li> // <li>
<AppAnchor route=AppRoute::RunicDivination>{"Runic Divination"}</AppAnchor> // <AppAnchor route=AppRoute::RunicDivination>{"Runic Divination"}</AppAnchor>
</li> // </li>
</ul> // </ul>
} // }
} // }
} // }
struct App; struct App;
@ -116,7 +102,6 @@ impl Component for App {
html! { html! {
<div> <div>
{"Hello World"} {"Hello World"}
<AppMenu />
<AppRouter render=AppRouter::render(render_route) /> <AppRouter render=AppRouter::render(render_route) />
</div> </div>
} }

View File

@ -1,77 +0,0 @@
use yew::prelude::*;
use yewdux::prelude::*;
use yewtil::NeqAssign;
struct Oaths;
#[derive(Clone)]
struct Oath {
title: String,
content: String,
}
#[derive(Default, Clone)]
pub(crate) struct OathState {
oaths: Vec<Oath>,
}
type OathDispatch = DispatchProps<BasicStore<OathState>>;
//Oaths list
pub(crate) struct StatefulOathsList {
dispatch: OathDispatch,
}
pub(crate) type OathsList = WithDispatch<StatefulOathsList>;
fn view_oath(oath: &Oath) -> Html {
html! {
<div>
<div>{oath.title.clone()}</div>
<div>{oath.content.clone()}</div>
</div>
}
}
impl Component for StatefulOathsList {
type Message = ();
type Properties = OathDispatch;
fn create(dispatch: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { dispatch }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, dispatch: Self::Properties) -> ShouldRender {
self.dispatch.neq_assign(dispatch)
}
fn view(&self) -> Html {
let add_oath = self.dispatch.reduce_callback(|s| {
s.oaths.push(Oath {
title: "yolo".to_string(),
content: "nolo".to_string(),
})
});
html! {
<div>
<button onclick=add_oath>{ "Add Oath" }</button>
<ul>
{
for self.dispatch.state().oaths.iter().map(|oath| {
view_oath(oath)
})
}
</ul>
</div>
}
}
}
//New oath form
//Edit oath

113
web-ui/crate/src/rooms.rs Normal file
View File

@ -0,0 +1,113 @@
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::{future_to_promise, spawn_local};
use web_sys::console;
use yew::prelude::*;
use yewdux::prelude::*;
use yewtil::NeqAssign;
#[derive(Clone)]
pub(crate) struct Room {
room_id: String,
display_name: String,
}
#[derive(Default, Clone)]
pub(crate) struct RoomListState {
rooms: Vec<Room>,
}
pub(crate) enum Action {
AddRoom(Room),
}
impl Reducer for RoomListState {
type Action = Action;
fn new() -> Self {
Self { rooms: vec![] }
}
fn reduce(&mut self, action: Self::Action) -> bool {
match action {
Action::AddRoom(room) => {
self.rooms.push(room.clone());
true
}
}
}
}
type RoomListDispatch = DispatchProps<ReducerStore<RoomListState>>;
//Oaths list
#[doc(hidden)]
pub(crate) struct YewduxRoomList {
dispatch: RoomListDispatch,
link: ComponentLink<YewduxRoomList>,
}
pub(crate) type RoomList = WithDispatch<YewduxRoomList>;
fn view_room(room: &Room) -> Html {
html! {
<div>
<div>{room.room_id.clone()}</div>
<div>{room.display_name.clone()}</div>
</div>
}
}
async fn do_things(dispatch: &RoomListDispatch) {
dispatch.send(Action::AddRoom(Room {
room_id: "asdf".into(),
display_name: "adslkjg".into(),
}));
}
impl Component for YewduxRoomList {
type Message = ();
type Properties = RoomListDispatch;
fn create(dispatch: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { dispatch, link }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}
fn change(&mut self, dispatch: Self::Properties) -> ShouldRender {
self.dispatch.neq_assign(dispatch)
}
fn view(&self) -> Html {
let dispatch = Arc::new(self.dispatch.clone());
let the_future = self.link.callback(move |_| {
let dispatch = dispatch.clone();
spawn_local(async move {
do_things(&*dispatch).await;
});
});
html! {
<div>
<button onclick=the_future>{ "Add Room" }</button>
<ul>
{
for self.dispatch.state().rooms.iter().map(|oath| {
view_room(oath)
})
}
</ul>
</div>
}
}
}
//New oath form
//Edit oath