Add UI for tile exchange
This commit is contained in:
parent
4ba4c15e23
commit
b790e48bc4
5 changed files with 154 additions and 6 deletions
3
ui/package-lock.json
generated
3
ui/package-lock.json
generated
|
@ -19,7 +19,8 @@
|
||||||
},
|
},
|
||||||
"../pkg": {
|
"../pkg": {
|
||||||
"name": "word_grid",
|
"name": "word_grid",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0",
|
||||||
|
"license": "AGPL-3"
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.5",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {useEffect, useRef} from "react";
|
||||||
|
|
||||||
export function Modal(props: {
|
export function Modal(props: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setClosed: () => void;
|
setOpen: (isOpen: boolean) => void;
|
||||||
children: any;
|
children: any;
|
||||||
}){
|
}){
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export function Modal(props: {
|
||||||
// we manually trigger a close anyway because the children can get updated before the dialog gets closed otherwise
|
// we manually trigger a close anyway because the children can get updated before the dialog gets closed otherwise
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
ref.current.close();
|
ref.current.close();
|
||||||
props.setClosed();
|
props.setOpen(false);
|
||||||
}}>✕</button>
|
}}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
|
import {GameWasm, Letter as LetterData, MyResult, PlayedTile, PlayerAndScore, Tray, WordResult} from "word_grid";
|
||||||
import {ChangeEvent, JSX, useEffect, useMemo, useReducer, useRef, useState} from "react";
|
import {ChangeEvent, JSX, useEffect, useMemo, useReducer, useRef, useState} from "react";
|
||||||
|
import {Modal} from "./Modal";
|
||||||
|
import {addNTimes} from "./utils";
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
trayLength: number;
|
trayLength: number;
|
||||||
|
@ -181,8 +183,6 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []);
|
const [playerLetters, trayDispatch] = useReducer(movePlayableLetters, []);
|
||||||
|
|
||||||
|
|
||||||
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
|
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
|
||||||
|
|
||||||
const [turnCount, setTurnCount] = useState<number>(1);
|
const [turnCount, setTurnCount] = useState<number>(1);
|
||||||
|
@ -202,6 +202,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
|
||||||
|
|
||||||
const logDivRef = useRef(null);
|
const logDivRef = useRef(null);
|
||||||
|
|
||||||
|
const [isTileExchangeOpen, setIsTileExchangeOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Effect is to keep the log window scrolled down
|
// Effect is to keep the log window scrolled down
|
||||||
|
@ -214,6 +215,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
<TileExchangeModal playerLetters={playerLetters} isOpen={isTileExchangeOpen} setOpen={setIsTileExchangeOpen} />
|
||||||
<div className="board-log">
|
<div className="board-log">
|
||||||
<Grid cellTypes={cellTypes} playerLetters={playerLetters} boardLetters={boardLetters} dispatch={trayDispatch}/>
|
<Grid cellTypes={cellTypes} playerLetters={playerLetters} boardLetters={boardLetters} dispatch={trayDispatch}/>
|
||||||
<div className="message-log">
|
<div className="message-log">
|
||||||
|
@ -273,6 +275,7 @@ export function Game(props: {wasm: GameWasm, settings: Settings}) {
|
||||||
|
|
||||||
|
|
||||||
}}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
|
}}>{confirmedScorePoints > -1 ? `Score ${confirmedScorePoints} points ✅` : "Check"}</button>
|
||||||
|
<button onClick={(e) => {setIsTileExchangeOpen(true);}}>Open Tile Exchange</button>
|
||||||
</>;
|
</>;
|
||||||
|
|
||||||
|
|
||||||
|
@ -465,4 +468,115 @@ function Scores(props: {playerScores: Array<PlayerAndScore>}){
|
||||||
return <div className="scoring">
|
return <div className="scoring">
|
||||||
{elements}
|
{elements}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TileExchangeModal(props: {playerLetters: PlayableLetterData[], isOpen: boolean, setOpen: (isOpen: boolean) => void}) {
|
||||||
|
|
||||||
|
function clearExchangeTiles() {
|
||||||
|
const array: boolean[] = [];
|
||||||
|
addNTimes(array, false, props.playerLetters.length);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tilesToExchange, setTilesToExchange] = useState<boolean[]>(clearExchangeTiles);
|
||||||
|
|
||||||
|
let tilesExchangedSelected = 0;
|
||||||
|
for (let i of tilesToExchange) {
|
||||||
|
if(i) {
|
||||||
|
tilesExchangedSelected++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Modal isOpen={props.isOpen} setOpen={(isOpen) => {
|
||||||
|
setTilesToExchange(clearExchangeTiles());
|
||||||
|
props.setOpen(isOpen);
|
||||||
|
}}>
|
||||||
|
<div className="tile-exchange-dialog">
|
||||||
|
<div className="instructions">
|
||||||
|
Click on each tile you'd like to exchange. You currently have {tilesExchangedSelected} tiles selected.
|
||||||
|
</div>
|
||||||
|
<div className="selection-buttons">
|
||||||
|
<button onClick={(e) => {
|
||||||
|
const array: boolean[] = [];
|
||||||
|
addNTimes(array, true, props.playerLetters.length);
|
||||||
|
setTilesToExchange(array);
|
||||||
|
}}>Select All</button>
|
||||||
|
<button onClick={(e) => {
|
||||||
|
setTilesToExchange(clearExchangeTiles());
|
||||||
|
}}>Select None</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TilesExchangedTray
|
||||||
|
tray={props.playerLetters}
|
||||||
|
selectedArray={tilesToExchange}
|
||||||
|
setSelectedArray={setTilesToExchange}
|
||||||
|
trayLength={props.playerLetters.length}
|
||||||
|
/>
|
||||||
|
<div className="finish-buttons">
|
||||||
|
<button onClick={() => {
|
||||||
|
setTilesToExchange(clearExchangeTiles());
|
||||||
|
props.setOpen(false);
|
||||||
|
}}>Cancel</button>
|
||||||
|
<button disabled = {tilesExchangedSelected == 0} onClick={(e) => {
|
||||||
|
// TODO exchange code
|
||||||
|
|
||||||
|
}}>Exchange</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function TilesExchangedTray(props: {
|
||||||
|
tray: Array<PlayableLetterData>,
|
||||||
|
trayLength: number,
|
||||||
|
selectedArray: Array<boolean>,
|
||||||
|
setSelectedArray: (x: Array<boolean>) => void,
|
||||||
|
}){
|
||||||
|
|
||||||
|
const divContent = [];
|
||||||
|
for(let i = 0; i<props.trayLength; i++){
|
||||||
|
const tileData = props.tray[i];
|
||||||
|
|
||||||
|
const toggleFunction = () => {
|
||||||
|
props.selectedArray[i] = !props.selectedArray[i];
|
||||||
|
props.setSelectedArray(props.selectedArray.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tileData != null){
|
||||||
|
divContent.push(
|
||||||
|
<DummyExchangeTile key={i} letter={tileData} isSelected={props.selectedArray[i]} onClick={toggleFunction}/>
|
||||||
|
);
|
||||||
|
} else{
|
||||||
|
divContent.push(<span key={i} />);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="tray">
|
||||||
|
{divContent}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function DummyExchangeTile(props: {
|
||||||
|
letter: LetterData,
|
||||||
|
isSelected: boolean,
|
||||||
|
onClick: () => void,
|
||||||
|
}){
|
||||||
|
|
||||||
|
let textClassName = "text";
|
||||||
|
if(props.letter.is_blank) {
|
||||||
|
textClassName += " prev-blank";
|
||||||
|
}
|
||||||
|
|
||||||
|
let letterClassName = "letter";
|
||||||
|
if(props.isSelected){
|
||||||
|
letterClassName += ' selected-tile';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <div className={letterClassName} onClick={props.onClick}>
|
||||||
|
<div className={textClassName}>{props.letter.text}</div>
|
||||||
|
<div className="letter-points">{props.letter.points}</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
|
@ -188,4 +188,31 @@ dialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tile-exchange-dialog {
|
||||||
|
|
||||||
|
.selection-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-buttons {
|
||||||
|
.selection-buttons();
|
||||||
|
}
|
||||||
|
|
||||||
|
.tray-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tile {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
6
ui/src/utils.ts
Normal file
6
ui/src/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
export function addNTimes<T>(array: T[], toAdd: T, times: number) {
|
||||||
|
for (let i=0; i<times; i++) {
|
||||||
|
array.push(toAdd);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue