Initial commit
This commit is contained in:
13
board-frontend/Cargo.toml
Normal file
13
board-frontend/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "board-frontend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "A web app built with Yew"
|
||||
keywords = ["yew", "trunk"]
|
||||
categories = ["gui", "wasm", "web-programming"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
yew = { version="0.20", features=["csr"] }
|
||||
board-shared = { path = "../board-shared" }
|
||||
gloo-dialogs = "0.1.1"
|
9
board-frontend/index.html
Normal file
9
board-frontend/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Scrabble des chiffres</title>
|
||||
<link data-trunk rel="sass" href="index.scss" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
71
board-frontend/index.scss
Normal file
71
board-frontend/index.scss
Normal file
@@ -0,0 +1,71 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.board {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.board-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cell, .tile {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cell {
|
||||
background-color: #888888;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.button, .tile {
|
||||
background-color: #535bf2;
|
||||
}
|
||||
.button:hover, .tile:hover {
|
||||
background-color: #646cff;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.hand {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
123
board-frontend/src/app.rs
Normal file
123
board-frontend/src/app.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use crate::hand_view::HandView;
|
||||
use crate::tile_view::PlacedTileView;
|
||||
use board_shared::board::Board;
|
||||
use board_shared::expr::is_valid_guess;
|
||||
use board_shared::game::Game;
|
||||
use board_shared::tile::Tile;
|
||||
use gloo_dialogs::alert;
|
||||
use yew::prelude::*;
|
||||
|
||||
enum SelectedTile {
|
||||
InHand(usize),
|
||||
Equals,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct BoardViewProps {
|
||||
board: Board,
|
||||
on_click: Callback<(usize, usize)>,
|
||||
}
|
||||
|
||||
#[function_component(BoardView)]
|
||||
fn board_view(BoardViewProps { board, on_click }: &BoardViewProps) -> Html {
|
||||
html! {
|
||||
<table class="board">
|
||||
{ (0..25).map(|x| html! {
|
||||
<tr class="board-row">
|
||||
{ (0..25).map(|y| html! {
|
||||
<PlacedTileView x={x} y={y} key={x} tile={board.get(x, y)} on_click={on_click.clone()} />
|
||||
}).collect::<Html>() }
|
||||
</tr>
|
||||
}).collect::<Html>() }
|
||||
</table>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component(App)]
|
||||
pub fn app() -> Html {
|
||||
let game = use_state(Game::default);
|
||||
let current_game = use_state(Game::default);
|
||||
|
||||
let selected_tile = use_state(|| SelectedTile::None);
|
||||
let on_tile_click = {
|
||||
let game = current_game.clone();
|
||||
let selected_tile = selected_tile.clone();
|
||||
Callback::from(move |(x, y)| {
|
||||
if let SelectedTile::InHand(idx) = *selected_tile {
|
||||
let mut in_hand = game.in_hand.clone();
|
||||
let tile = in_hand.tiles.remove(idx);
|
||||
let mut board = game.board.clone();
|
||||
board.set(x, y, tile);
|
||||
game.set(Game { board, in_hand });
|
||||
selected_tile.set(SelectedTile::None);
|
||||
} else if let SelectedTile::Equals = *selected_tile {
|
||||
let mut board = game.board.clone();
|
||||
board.set(x, y, Tile::Equals);
|
||||
game.set(Game {
|
||||
board,
|
||||
in_hand: game.in_hand.clone(),
|
||||
});
|
||||
selected_tile.set(SelectedTile::None);
|
||||
}
|
||||
})
|
||||
};
|
||||
let on_tile_select = {
|
||||
let selected_tile = selected_tile.clone();
|
||||
Callback::from(move |idx| {
|
||||
selected_tile.set(SelectedTile::InHand(idx));
|
||||
})
|
||||
};
|
||||
let on_equals_select = {
|
||||
Callback::from(move |_| {
|
||||
selected_tile.set(SelectedTile::Equals);
|
||||
})
|
||||
};
|
||||
let on_continue_click = {
|
||||
let current_game = current_game.clone();
|
||||
Callback::from(move |_| {
|
||||
let diff = game.board.difference(¤t_game.board);
|
||||
if let Some(true) = Board::is_contiguous(&diff) {
|
||||
if let Ok(true) = is_valid_guess(¤t_game.board, &diff) {
|
||||
alert("Valid move!");
|
||||
let mut in_hand = current_game.in_hand.clone();
|
||||
in_hand.complete();
|
||||
game.set(Game {
|
||||
board: current_game.board.clone(),
|
||||
in_hand: in_hand.clone()
|
||||
});
|
||||
current_game.set(Game {
|
||||
board: current_game.board.clone(),
|
||||
in_hand,
|
||||
});
|
||||
} else {
|
||||
alert("Invalid move! (invalid expressions)");
|
||||
current_game.set(Game {
|
||||
board: game.board.clone(),
|
||||
in_hand: game.in_hand.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if !diff.is_empty() {
|
||||
alert("Invalid move! (not contiguous)");
|
||||
}
|
||||
let mut in_hand = game.in_hand.clone();
|
||||
in_hand.complete();
|
||||
current_game.set(Game {
|
||||
board: game.board.clone(),
|
||||
in_hand,
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
html! {
|
||||
<main>
|
||||
<BoardView board={current_game.board.clone()} on_click={on_tile_click} />
|
||||
<HandView hand={current_game.in_hand.clone()} on_select={on_tile_select} />
|
||||
<div class="row">
|
||||
<button onclick={on_equals_select} class="button">{"="}</button>
|
||||
<button onclick={on_continue_click} class="button">{if current_game.in_hand.tiles.is_empty() { "Start" } else { "Continue" }}</button>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
22
board-frontend/src/hand_view.rs
Normal file
22
board-frontend/src/hand_view.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::tile_view::TileView;
|
||||
use board_shared::game::Hand;
|
||||
use yew::prelude::*;
|
||||
use yew::{html, Callback, Html};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct HandViewProps {
|
||||
pub hand: Hand,
|
||||
pub on_select: Callback<usize>,
|
||||
}
|
||||
|
||||
#[function_component(HandView)]
|
||||
pub fn hand_view(HandViewProps { hand, on_select }: &HandViewProps) -> Html {
|
||||
let on_select = on_select.clone();
|
||||
html! {
|
||||
<div class="hand">
|
||||
{ hand.tiles.iter().enumerate().map(|(i, tile)| html! {
|
||||
<TileView tile={*tile} key={i} idx={i} on_select={on_select.clone()} />
|
||||
}).collect::<Html>() }
|
||||
</div>
|
||||
}
|
||||
}
|
9
board-frontend/src/main.rs
Normal file
9
board-frontend/src/main.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod app;
|
||||
mod hand_view;
|
||||
mod tile_view;
|
||||
|
||||
use app::App;
|
||||
|
||||
fn main() {
|
||||
yew::Renderer::<App>::new().render();
|
||||
}
|
59
board-frontend/src/tile_view.rs
Normal file
59
board-frontend/src/tile_view.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use board_shared::tile::Tile;
|
||||
use yew::prelude::*;
|
||||
use yew::{html, Callback, Html};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct PlacedTileViewProps {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub tile: Option<Tile>,
|
||||
pub on_click: Callback<(usize, usize)>,
|
||||
}
|
||||
|
||||
#[function_component(PlacedTileView)]
|
||||
pub fn placed_tile_view(
|
||||
PlacedTileViewProps {
|
||||
x,
|
||||
y,
|
||||
tile,
|
||||
on_click,
|
||||
}: &PlacedTileViewProps,
|
||||
) -> Html {
|
||||
let x = *x;
|
||||
let y = *y;
|
||||
let on_select = {
|
||||
let on_click = on_click.clone();
|
||||
Callback::from(move |_| on_click.emit((x, y)))
|
||||
};
|
||||
html! {
|
||||
<td class="cell" key={y} onclick={on_select}>{ tile.map(|tile| {
|
||||
html! { tile }
|
||||
}).unwrap_or_else(|| {
|
||||
html! { "" }
|
||||
})}</td>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct TileViewProps {
|
||||
pub tile: Tile,
|
||||
pub on_select: Callback<usize>,
|
||||
pub idx: usize,
|
||||
}
|
||||
|
||||
#[function_component(TileView)]
|
||||
pub fn tile_view(
|
||||
TileViewProps {
|
||||
tile,
|
||||
on_select,
|
||||
idx,
|
||||
}: &TileViewProps,
|
||||
) -> Html {
|
||||
let on_select = on_select.clone();
|
||||
let idx = *idx;
|
||||
html! {
|
||||
<div class="tile" onclick={Callback::from(move |_| {
|
||||
on_select.emit(idx)
|
||||
})}>{ tile }</div>
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user