refactor: add HeaderBar
This commit is contained in:
parent
a07df5b62d
commit
9fcb3e8e71
188
src/Home.tsx
188
src/Home.tsx
@ -11,18 +11,17 @@ import {
|
|||||||
channel_on_game_update,
|
channel_on_game_update,
|
||||||
channel_on_game_user_leave,
|
channel_on_game_user_leave,
|
||||||
} from "./channel_names";
|
} from "./channel_names";
|
||||||
import Button from "./components/Button";
|
|
||||||
import InfoPanel from "./components/InfoPanel";
|
import InfoPanel from "./components/InfoPanel";
|
||||||
import { CommonMancalaGame, MancalaGame, Pit } from "mancala.js";
|
import { CommonMancalaGame, MancalaGame, Pit } from "mancala.js";
|
||||||
import { GameMove } from "./models/GameMove";
|
import { GameMove } from "./models/GameMove";
|
||||||
import PitAnimator from "./animation/PitAnimator";
|
import PitAnimator from "./animation/PitAnimator";
|
||||||
import BoardViewModel from "./viewmodel/BoardViewModel";
|
import BoardViewModel from "./viewmodel/BoardViewModel";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { Menu, MenuButton, MenuItem } from "@szhsin/react-menu";
|
|
||||||
import "@szhsin/react-menu/dist/index.css";
|
|
||||||
import "@szhsin/react-menu/dist/transitions/slide.css";
|
|
||||||
import { getColorByBrightness } from "./util/ColorUtil";
|
import { getColorByBrightness } from "./util/ColorUtil";
|
||||||
import { Theme } from "./theme/Theme";
|
import { Theme } from "./theme/Theme";
|
||||||
|
import HeaderBar from "./components/HeaderBar";
|
||||||
|
import FloatingPanel from "./components/FloatingPanel";
|
||||||
|
import PageContainer from "./components/PageContainer";
|
||||||
|
|
||||||
type ConnectionState = "connecting" | "error" | "connected" | "reconnecting";
|
type ConnectionState = "connecting" | "error" | "connected" | "reconnecting";
|
||||||
|
|
||||||
@ -113,6 +112,17 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
|||||||
setBoardViewModel(boardViewModel);
|
setBoardViewModel(boardViewModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetGameState = () => {
|
||||||
|
setGame(undefined);
|
||||||
|
setCrashMessage(undefined);
|
||||||
|
setUserKeyWhoLeave(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBoardIndex = (index: number) => {
|
||||||
|
if (userKey === game.player2Id) return index + game.board.pits.length / 2;
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setTheme(context.themeManager.theme);
|
setTheme(context.themeManager.theme);
|
||||||
const pitAnimator = new PitAnimator(context, updateBoardViewModel);
|
const pitAnimator = new PitAnimator(context, updateBoardViewModel);
|
||||||
@ -131,27 +141,16 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
|||||||
};
|
};
|
||||||
}, [boardViewModel]);
|
}, [boardViewModel]);
|
||||||
|
|
||||||
const resetGameState = () => {
|
const onNewGameClick = () => {
|
||||||
setGame(undefined);
|
|
||||||
setCrashMessage(undefined);
|
|
||||||
setUserKeyWhoLeave(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const newGameClick = () => {
|
|
||||||
resetGameState();
|
resetGameState();
|
||||||
setSearchingOpponent(true);
|
setSearchingOpponent(true);
|
||||||
context.rtmt.sendMessage("new_game", {});
|
context.rtmt.sendMessage("new_game", {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const leaveGame = () => {
|
const onLeaveGameClick = () => {
|
||||||
context.rtmt.sendMessage(channel_leave_game, {});
|
context.rtmt.sendMessage(channel_leave_game, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBoardIndex = (index: number) => {
|
|
||||||
if (userKey === game.player2Id) return index + game.board.pits.length / 2;
|
|
||||||
return index;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onHoleSelect = (index: number, pit: Pit) => {
|
const onHoleSelect = (index: number, pit: Pit) => {
|
||||||
//TODO : stoneCount comes from view model!
|
//TODO : stoneCount comes from view model!
|
||||||
if (pit.stoneCount === 0) {
|
if (pit.stoneCount === 0) {
|
||||||
@ -178,149 +177,24 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
|||||||
return map[connectionState];
|
return map[connectionState];
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderNewGameButton = () => {
|
const textColorOnBoard = getColorByBrightness(
|
||||||
const newGame = (
|
context.themeManager.theme.boardColor,
|
||||||
<Button
|
|
||||||
context={context}
|
|
||||||
text={context.texts.NewGame}
|
|
||||||
color={context.themeManager.theme.holeColor}
|
|
||||||
onClick={newGameClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
if (userKeyWhoLeave) {
|
|
||||||
return newGame;
|
|
||||||
}
|
|
||||||
if (crashMessage) {
|
|
||||||
return newGame;
|
|
||||||
}
|
|
||||||
if (!game) {
|
|
||||||
return newGame;
|
|
||||||
} else if (game.state == "ended") {
|
|
||||||
return newGame;
|
|
||||||
}
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
const menuTextColor = getColorByBrightness(
|
|
||||||
context.themeManager.theme.appBarBgColor,
|
|
||||||
context.themeManager.theme.textColor,
|
context.themeManager.theme.textColor,
|
||||||
context.themeManager.theme.textLightColor
|
context.themeManager.theme.textLightColor
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<PageContainer theme={theme!}>
|
||||||
style={{
|
<FloatingPanel context={context} color={context.themeManager.theme.boardColor} visible={showConnectionState}>
|
||||||
display: "flex",
|
<span style={{ color: textColorOnBoard }}>{connectionStateText()}</span>
|
||||||
flexDirection: "column",
|
</FloatingPanel>
|
||||||
alignItems: "center",
|
<HeaderBar
|
||||||
background: theme?.background,
|
context={context}
|
||||||
flex: "1",
|
game={game}
|
||||||
minHeight: "400px"
|
userKeyWhoLeave={userKeyWhoLeave}
|
||||||
}}
|
crashMessage={crashMessage}
|
||||||
>
|
onNewGameClick={onNewGameClick}
|
||||||
{showConnectionState && (
|
onLeaveGameClick={onLeaveGameClick} />
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
bottom: "0px",
|
|
||||||
left: "0px",
|
|
||||||
padding: "15px ",
|
|
||||||
borderTopRightRadius: "1vw",
|
|
||||||
minWidth: "10vw",
|
|
||||||
minHeight: "1vw",
|
|
||||||
background: context.themeManager.theme.textColor,
|
|
||||||
color: context.themeManager.theme.textLightColor,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{connectionStateText()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "0px 4vw",
|
|
||||||
background: context.themeManager.theme.appBarBgColor,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignSelf: "stretch",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1 style={{ color: menuTextColor, margin: "10px 0px" }}>
|
|
||||||
{context.texts.Mancala}
|
|
||||||
</h1>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginRight: "1vw",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu
|
|
||||||
menuStyle={{
|
|
||||||
background: context.themeManager.theme.appBarBgColor,
|
|
||||||
}}
|
|
||||||
menuButton={
|
|
||||||
<span
|
|
||||||
style={{ color: menuTextColor }}
|
|
||||||
class="material-symbols-outlined"
|
|
||||||
>
|
|
||||||
light_mode
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
transition
|
|
||||||
align="end"
|
|
||||||
>
|
|
||||||
{context.themeManager.themes.map((theme) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
style={{
|
|
||||||
color: menuTextColor,
|
|
||||||
}}
|
|
||||||
onMouseOver={(event) =>
|
|
||||||
(event.target.style.background =
|
|
||||||
context.themeManager.theme.background)
|
|
||||||
}
|
|
||||||
onMouseOut={(event) =>
|
|
||||||
(event.target.style.background = "transparent")
|
|
||||||
}
|
|
||||||
onClick={() => (context.themeManager.theme = theme)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
borderRadius: "5vw",
|
|
||||||
background: theme.boardColor,
|
|
||||||
width: "1vw",
|
|
||||||
height: "1vw",
|
|
||||||
marginRight: "1vw",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
{theme.name}
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
{renderNewGameButton()}
|
|
||||||
{game &&
|
|
||||||
!userKeyWhoLeave &&
|
|
||||||
!crashMessage &&
|
|
||||||
(game?.state === "playing" || game?.state === "initial") && (
|
|
||||||
<Button
|
|
||||||
context={context}
|
|
||||||
color={context.themeManager.theme.holeColor}
|
|
||||||
text={context.texts.Leave}
|
|
||||||
onClick={leaveGame}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
context={context}
|
context={context}
|
||||||
game={game}
|
game={game}
|
||||||
@ -339,7 +213,7 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
|||||||
onHoleSelect={onHoleSelect}
|
onHoleSelect={onHoleSelect}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
27
src/components/FloatingPanel.tsx
Normal file
27
src/components/FloatingPanel.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { FunctionComponent } from "react";
|
||||||
|
import { Context } from "../context";
|
||||||
|
|
||||||
|
const FloatingPanel: FunctionComponent<{
|
||||||
|
context: Context;
|
||||||
|
color: string;
|
||||||
|
visible: boolean;
|
||||||
|
}> = (props) => {
|
||||||
|
if(!props.visible) return <></>
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "0px",
|
||||||
|
left: "0px",
|
||||||
|
padding: "15px",
|
||||||
|
borderTopRightRadius: "1vw",
|
||||||
|
minWidth: "10vw",
|
||||||
|
minHeight: "1vw",
|
||||||
|
backgroundColor: props.color,
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FloatingPanel;
|
||||||
125
src/components/HeaderBar.tsx
Normal file
125
src/components/HeaderBar.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { FunctionComponent } from "react";
|
||||||
|
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||||
|
import { MancalaGame } from "mancala.js";
|
||||||
|
import { Context } from "../context";
|
||||||
|
import { getColorByBrightness } from "../util/ColorUtil";
|
||||||
|
import Button from "./Button";
|
||||||
|
import "@szhsin/react-menu/dist/index.css";
|
||||||
|
import "@szhsin/react-menu/dist/transitions/slide.css";
|
||||||
|
|
||||||
|
function renderNewGameButton(context: Context, game: MancalaGame, onNewGameClick: () => void, userKeyWhoLeave: string, crashMessage: string): JSX.Element {
|
||||||
|
const newGame = (
|
||||||
|
<Button
|
||||||
|
context={context}
|
||||||
|
text={context.texts.NewGame}
|
||||||
|
color={context.themeManager.theme.holeColor}
|
||||||
|
onClick={onNewGameClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
if (userKeyWhoLeave) {
|
||||||
|
return newGame;
|
||||||
|
}
|
||||||
|
if (crashMessage) {
|
||||||
|
return newGame;
|
||||||
|
}
|
||||||
|
if (!game) {
|
||||||
|
return newGame;
|
||||||
|
} else if (game.state == "ended") {
|
||||||
|
return newGame;
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderBar: FunctionComponent<{
|
||||||
|
context: Context,
|
||||||
|
game: MancalaGame,
|
||||||
|
userKeyWhoLeave: string,
|
||||||
|
crashMessage: string,
|
||||||
|
onNewGameClick: () => void,
|
||||||
|
onLeaveGameClick: () => void
|
||||||
|
}> = (props) => {
|
||||||
|
const { context, game, userKeyWhoLeave, crashMessage, onNewGameClick, onLeaveGameClick } = props;
|
||||||
|
const textColor = getColorByBrightness(
|
||||||
|
context.themeManager.theme.appBarBgColor,
|
||||||
|
context.themeManager.theme.textColor,
|
||||||
|
context.themeManager.theme.textLightColor
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
padding: "0px 4vw",
|
||||||
|
background: context.themeManager.theme.appBarBgColor,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignSelf: "stretch",
|
||||||
|
}}>
|
||||||
|
<h1 style={{ color: textColor, margin: "10px 0px" }}>
|
||||||
|
{context.texts.Mancala}
|
||||||
|
</h1>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}} >
|
||||||
|
<div style={{
|
||||||
|
marginRight: "1vw",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}>
|
||||||
|
<ThemeSwitchMenu context={context} textColor={textColor} />
|
||||||
|
</div>
|
||||||
|
{renderNewGameButton(context, game, onNewGameClick, userKeyWhoLeave, crashMessage)}
|
||||||
|
{game &&
|
||||||
|
!userKeyWhoLeave &&
|
||||||
|
!crashMessage &&
|
||||||
|
(game?.state === "playing" || game?.state === "initial") && (
|
||||||
|
<Button
|
||||||
|
context={context}
|
||||||
|
color={context.themeManager.theme.holeColor}
|
||||||
|
text={context.texts.Leave}
|
||||||
|
onClick={onLeaveGameClick} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
export default HeaderBar;
|
||||||
|
|
||||||
|
const ThemeSwitchMenu: FunctionComponent<{ context: Context, textColor: string }> = (props) => {
|
||||||
|
const { context, textColor } = props;
|
||||||
|
const menuButton = <span
|
||||||
|
style={{ color: textColor }}
|
||||||
|
className="material-symbols-outlined">
|
||||||
|
light_mode
|
||||||
|
</span>;
|
||||||
|
const menuItems = context.themeManager.themes.map((theme, index) => {
|
||||||
|
return (<MenuItem
|
||||||
|
key={index}
|
||||||
|
style={{ color: textColor }}
|
||||||
|
onMouseOver={(event) => (event.target.style.background =
|
||||||
|
context.themeManager.theme.background)}
|
||||||
|
onMouseOut={(event) => (event.target.style.background = "transparent")}
|
||||||
|
onClick={() => (context.themeManager.theme = theme)}>
|
||||||
|
<div style={{
|
||||||
|
borderRadius: "5vw",
|
||||||
|
background: theme.boardColor,
|
||||||
|
width: "1vw",
|
||||||
|
height: "1vw",
|
||||||
|
marginRight: "1vw",
|
||||||
|
}} />
|
||||||
|
{theme.name}
|
||||||
|
</MenuItem>);
|
||||||
|
})
|
||||||
|
return (<Menu
|
||||||
|
menuStyle={{
|
||||||
|
background: context.themeManager.theme.appBarBgColor,
|
||||||
|
}}
|
||||||
|
menuButton={menuButton}
|
||||||
|
transition
|
||||||
|
align="end">
|
||||||
|
{menuItems}
|
||||||
|
</Menu>);
|
||||||
|
}
|
||||||
|
|
||||||
20
src/components/PageContainer.tsx
Normal file
20
src/components/PageContainer.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { FunctionComponent } from "react";
|
||||||
|
import { Theme } from "../theme/Theme";
|
||||||
|
|
||||||
|
const PageContainer: FunctionComponent<{theme: Theme}> = (props) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
background: props.theme?.background,
|
||||||
|
flex: "1",
|
||||||
|
minHeight: "400px"
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageContainer;
|
||||||
Loading…
Reference in New Issue
Block a user