Added output-text-box, connected front-end items

More work was needed to have separate components of the front-end to
communicate with each other. Also added an output-text-box for
informative messages.
This commit is contained in:
Stephen Seo 2022-03-01 16:02:59 +09:00
parent a756f0b4b2
commit a8e516c535
5 changed files with 232 additions and 68 deletions

14
front_end/Cargo.lock generated
View File

@ -40,6 +40,9 @@ dependencies = [
name = "four_line_dropper_frontend"
version = "0.1.0"
dependencies = [
"log",
"wasm-logger",
"web-sys",
"yew",
]
@ -397,6 +400,17 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "wasm-logger"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718"
dependencies = [
"log",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.56"

View File

@ -7,3 +7,7 @@ edition = "2021"
[dependencies]
yew = "0.19"
log = "0.4.6"
wasm-logger = "0.2.0"
web-sys = { version = "0.3.56", features = ["HtmlParagraphElement", "HtmlBrElement"] }

View File

@ -11,6 +11,18 @@
grid-auto-rows: 50px;
border: 0px;
}
div.info_text_wrapper {
grid-row: 9;
grid-column: 1 / 8;
}
div.info_text {
background-color: #DDD;
width: fill;
height: 400px;
overflow-x: hidden;
overflow-y: auto;
word-wrap: break-word;
}
button.slot {
width: 50px;
height: 50px;

View File

@ -1,10 +1,14 @@
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::Rc;
use yew::prelude::*;
//const ROWS: u8 = 8;
const COLS: u8 = 7;
const INFO_TEXT_MAX_ITEMS: u32 = 100;
const INFO_TEXT_HEIGHT: i32 = 400;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum BoardState {
Empty,
@ -19,8 +23,41 @@ impl Default for BoardState {
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MessageBus {
queued: VecDeque<String>,
}
impl Default for MessageBus {
fn default() -> Self {
Self {
queued: VecDeque::new(),
}
}
}
impl MessageBus {
pub fn get_next_msg(&mut self) -> Option<String> {
self.queued.pop_front()
}
pub fn push_msg(&mut self, msg: String) -> Result<(), String> {
self.queued.push_back(msg);
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Turn {
CyanPlayer,
MagentaPlayer,
}
#[derive(Clone, Debug, PartialEq)]
struct SharedState {
board: [Rc<Cell<BoardState>>; 56],
bus: Rc<RefCell<MessageBus>>,
turn: Turn,
info_text_ref: NodeRef,
}
impl Default for SharedState {
@ -85,6 +122,9 @@ impl Default for SharedState {
Rc::new(Cell::new(BoardState::default())),
Rc::new(Cell::new(BoardState::default())),
],
bus: Rc::new(RefCell::new(MessageBus::default())),
turn: Turn::CyanPlayer,
info_text_ref: NodeRef::default(),
}
}
}
@ -99,6 +139,7 @@ enum SlotMessage {
struct SlotProperties {
idx: u8,
state: Rc<Cell<BoardState>>,
bus: Rc<RefCell<MessageBus>>,
}
impl Component for Slot {
@ -133,20 +174,21 @@ impl Component for Slot {
.link()
.context::<SharedState>(Callback::noop())
.expect("shared to be set");
let value_at_idx = shared.board[idx as usize].get();
match value_at_idx {
BoardState::Empty => {
shared.board[idx as usize].replace(BoardState::Cyan);
}
BoardState::Cyan => {
shared.board[idx as usize].replace(BoardState::Magenta);
}
BoardState::Magenta => {
shared.board[idx as usize].replace(BoardState::Empty);
let result = shared.bus.borrow_mut().push_msg(format!("pressed {idx}"));
if let Err(e) = result {
log::error!("Error pushing msg to bus: {}", e);
} else {
log::info!("Pushed \"pressed {idx}\" msg to bus");
}
}
}
// notify Wrapper with message
if let Some(p) = ctx.link().get_parent() {
p.clone().downcast::<Wrapper>().send_message(());
}
true
}
}
@ -168,65 +210,152 @@ impl Component for Wrapper {
.expect("state to be set");
html! {
<div class="wrapper">
<Slot idx=0 state={shared.board[0].clone()} />
<Slot idx=1 state={shared.board[1].clone()} />
<Slot idx=2 state={shared.board[2].clone()} />
<Slot idx=3 state={shared.board[3].clone()} />
<Slot idx=4 state={shared.board[4].clone()} />
<Slot idx=5 state={shared.board[5].clone()} />
<Slot idx=6 state={shared.board[6].clone()} />
<Slot idx=7 state={shared.board[7].clone()} />
<Slot idx=8 state={shared.board[8].clone()} />
<Slot idx=9 state={shared.board[9].clone()} />
<Slot idx=10 state={shared.board[10].clone()} />
<Slot idx=11 state={shared.board[11].clone()} />
<Slot idx=12 state={shared.board[12].clone()} />
<Slot idx=13 state={shared.board[13].clone()} />
<Slot idx=14 state={shared.board[14].clone()} />
<Slot idx=15 state={shared.board[15].clone()} />
<Slot idx=16 state={shared.board[16].clone()} />
<Slot idx=17 state={shared.board[17].clone()} />
<Slot idx=18 state={shared.board[18].clone()} />
<Slot idx=19 state={shared.board[19].clone()} />
<Slot idx=20 state={shared.board[20].clone()} />
<Slot idx=21 state={shared.board[21].clone()} />
<Slot idx=22 state={shared.board[22].clone()} />
<Slot idx=23 state={shared.board[23].clone()} />
<Slot idx=24 state={shared.board[24].clone()} />
<Slot idx=25 state={shared.board[25].clone()} />
<Slot idx=26 state={shared.board[26].clone()} />
<Slot idx=27 state={shared.board[27].clone()} />
<Slot idx=28 state={shared.board[28].clone()} />
<Slot idx=29 state={shared.board[29].clone()} />
<Slot idx=30 state={shared.board[30].clone()} />
<Slot idx=31 state={shared.board[31].clone()} />
<Slot idx=32 state={shared.board[32].clone()} />
<Slot idx=33 state={shared.board[33].clone()} />
<Slot idx=34 state={shared.board[34].clone()} />
<Slot idx=35 state={shared.board[35].clone()} />
<Slot idx=36 state={shared.board[36].clone()} />
<Slot idx=37 state={shared.board[37].clone()} />
<Slot idx=38 state={shared.board[38].clone()} />
<Slot idx=39 state={shared.board[39].clone()} />
<Slot idx=40 state={shared.board[40].clone()} />
<Slot idx=41 state={shared.board[41].clone()} />
<Slot idx=42 state={shared.board[42].clone()} />
<Slot idx=43 state={shared.board[43].clone()} />
<Slot idx=44 state={shared.board[44].clone()} />
<Slot idx=45 state={shared.board[45].clone()} />
<Slot idx=46 state={shared.board[46].clone()} />
<Slot idx=47 state={shared.board[47].clone()} />
<Slot idx=48 state={shared.board[48].clone()} />
<Slot idx=49 state={shared.board[49].clone()} />
<Slot idx=50 state={shared.board[50].clone()} />
<Slot idx=51 state={shared.board[51].clone()} />
<Slot idx=52 state={shared.board[52].clone()} />
<Slot idx=53 state={shared.board[53].clone()} />
<Slot idx=54 state={shared.board[54].clone()} />
<Slot idx=55 state={shared.board[55].clone()} />
<Slot idx=0 state={shared.board[0].clone()} bus={shared.bus.clone()} />
<Slot idx=1 state={shared.board[1].clone()} bus={shared.bus.clone()} />
<Slot idx=2 state={shared.board[2].clone()} bus={shared.bus.clone()} />
<Slot idx=3 state={shared.board[3].clone()} bus={shared.bus.clone()} />
<Slot idx=4 state={shared.board[4].clone()} bus={shared.bus.clone()} />
<Slot idx=5 state={shared.board[5].clone()} bus={shared.bus.clone()} />
<Slot idx=6 state={shared.board[6].clone()} bus={shared.bus.clone()} />
<Slot idx=7 state={shared.board[7].clone()} bus={shared.bus.clone()} />
<Slot idx=8 state={shared.board[8].clone()} bus={shared.bus.clone()} />
<Slot idx=9 state={shared.board[9].clone()} bus={shared.bus.clone()} />
<Slot idx=10 state={shared.board[10].clone()} bus={shared.bus.clone()} />
<Slot idx=11 state={shared.board[11].clone()} bus={shared.bus.clone()} />
<Slot idx=12 state={shared.board[12].clone()} bus={shared.bus.clone()} />
<Slot idx=13 state={shared.board[13].clone()} bus={shared.bus.clone()} />
<Slot idx=14 state={shared.board[14].clone()} bus={shared.bus.clone()} />
<Slot idx=15 state={shared.board[15].clone()} bus={shared.bus.clone()} />
<Slot idx=16 state={shared.board[16].clone()} bus={shared.bus.clone()} />
<Slot idx=17 state={shared.board[17].clone()} bus={shared.bus.clone()} />
<Slot idx=18 state={shared.board[18].clone()} bus={shared.bus.clone()} />
<Slot idx=19 state={shared.board[19].clone()} bus={shared.bus.clone()} />
<Slot idx=20 state={shared.board[20].clone()} bus={shared.bus.clone()} />
<Slot idx=21 state={shared.board[21].clone()} bus={shared.bus.clone()} />
<Slot idx=22 state={shared.board[22].clone()} bus={shared.bus.clone()} />
<Slot idx=23 state={shared.board[23].clone()} bus={shared.bus.clone()} />
<Slot idx=24 state={shared.board[24].clone()} bus={shared.bus.clone()} />
<Slot idx=25 state={shared.board[25].clone()} bus={shared.bus.clone()} />
<Slot idx=26 state={shared.board[26].clone()} bus={shared.bus.clone()} />
<Slot idx=27 state={shared.board[27].clone()} bus={shared.bus.clone()} />
<Slot idx=28 state={shared.board[28].clone()} bus={shared.bus.clone()} />
<Slot idx=29 state={shared.board[29].clone()} bus={shared.bus.clone()} />
<Slot idx=30 state={shared.board[30].clone()} bus={shared.bus.clone()} />
<Slot idx=31 state={shared.board[31].clone()} bus={shared.bus.clone()} />
<Slot idx=32 state={shared.board[32].clone()} bus={shared.bus.clone()} />
<Slot idx=33 state={shared.board[33].clone()} bus={shared.bus.clone()} />
<Slot idx=34 state={shared.board[34].clone()} bus={shared.bus.clone()} />
<Slot idx=35 state={shared.board[35].clone()} bus={shared.bus.clone()} />
<Slot idx=36 state={shared.board[36].clone()} bus={shared.bus.clone()} />
<Slot idx=37 state={shared.board[37].clone()} bus={shared.bus.clone()} />
<Slot idx=38 state={shared.board[38].clone()} bus={shared.bus.clone()} />
<Slot idx=39 state={shared.board[39].clone()} bus={shared.bus.clone()} />
<Slot idx=40 state={shared.board[40].clone()} bus={shared.bus.clone()} />
<Slot idx=41 state={shared.board[41].clone()} bus={shared.bus.clone()} />
<Slot idx=42 state={shared.board[42].clone()} bus={shared.bus.clone()} />
<Slot idx=43 state={shared.board[43].clone()} bus={shared.bus.clone()} />
<Slot idx=44 state={shared.board[44].clone()} bus={shared.bus.clone()} />
<Slot idx=45 state={shared.board[45].clone()} bus={shared.bus.clone()} />
<Slot idx=46 state={shared.board[46].clone()} bus={shared.bus.clone()} />
<Slot idx=47 state={shared.board[47].clone()} bus={shared.bus.clone()} />
<Slot idx=48 state={shared.board[48].clone()} bus={shared.bus.clone()} />
<Slot idx=49 state={shared.board[49].clone()} bus={shared.bus.clone()} />
<Slot idx=50 state={shared.board[50].clone()} bus={shared.bus.clone()} />
<Slot idx=51 state={shared.board[51].clone()} bus={shared.bus.clone()} />
<Slot idx=52 state={shared.board[52].clone()} bus={shared.bus.clone()} />
<Slot idx=53 state={shared.board[53].clone()} bus={shared.bus.clone()} />
<Slot idx=54 state={shared.board[54].clone()} bus={shared.bus.clone()} />
<Slot idx=55 state={shared.board[55].clone()} bus={shared.bus.clone()} />
<div class="info_text_wrapper">
<InfoText />
</div>
</div> // wrapper
}
}
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
let (shared, _) = ctx
.link()
.context::<SharedState>(Callback::noop())
.expect("state to be set");
loop {
if let Some(msg) = shared.bus.borrow_mut().get_next_msg() {
let split_str: Vec<&str> = msg.split_whitespace().collect();
if split_str.len() == 2 {
if split_str[0] == "pressed" {
if let Ok(idx) = split_str[1].parse::<u8>() {
let output_str: String = format!("Got {idx} pressed.");
log::info!("{}", &output_str);
if let Some(info_text) =
shared.info_text_ref.cast::<web_sys::HtmlParagraphElement>()
{
// create the new text to be appended in the output
let window = web_sys::window().expect("no window exists");
let document =
window.document().expect("window should have a document");
let p = document
.create_element("p")
.expect("document should be able to create <p>");
p.set_text_content(Some(&output_str));
// check if scrolled to bottom
let at_bottom: bool = info_text.scroll_top() + INFO_TEXT_HEIGHT
>= info_text.scroll_height();
// append text to output
info_text
.append_with_node_1(&p)
.expect("should be able to append to info_text");
while info_text.child_element_count() > INFO_TEXT_MAX_ITEMS {
info_text
.remove_child(&info_text.first_child().unwrap())
.expect("should be able to limit items in info_text");
}
// scroll to bottom only if at bottom
if at_bottom {
info_text.set_scroll_top(info_text.scroll_height());
}
} else {
log::warn!("Failed to get \"info_text\"");
}
}
}
}
} else {
break;
}
}
}
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct InfoText {}
impl Component for InfoText {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let (shared, _) = ctx
.link()
.context::<SharedState>(Callback::noop())
.expect("state to be set");
html! {
<div ref={shared.info_text_ref} class="info_text">
{"Hello"}
</div>
}
}
}
#[function_component(App)]
@ -240,5 +369,9 @@ fn app() -> Html {
}
fn main() {
// setup logging to browser console
wasm_logger::init(wasm_logger::Config::default());
// start webapp
yew::start_app::<App>();
}

1
spreadsheets/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.~lock*