commit
eb1a51c41f
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mancala-frontend",
|
||||
"version": "0.1.3-beta.7",
|
||||
"version": "0.1.3-beta.8",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -14,6 +14,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@szhsin/react-menu": "^3.0.2",
|
||||
"@types/": "szhsin/react-menu",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"mancala.js": "^0.0.2-beta.2",
|
||||
"react": "^17.0.2",
|
||||
|
||||
98
src/Home.tsx
98
src/Home.tsx
@ -18,6 +18,10 @@ import { GameMove } from "./models/GameMove";
|
||||
import PitAnimator from "./animation/PitAnimator";
|
||||
import BoardViewModel from "./viewmodel/BoardViewModel";
|
||||
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 { getColorByLuminance } from "./util/ColorUtil";
|
||||
|
||||
type ConnectionState = "connecting" | "error" | "connected" | "reconnecting";
|
||||
|
||||
@ -116,6 +120,12 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
context.themeManager.onThemeChange = () => {
|
||||
updateBoardViewModel(pitAnimator.getBoardViewModelFromGame(game));
|
||||
};
|
||||
}, [boardViewModel]);
|
||||
|
||||
const resetGameState = () => {
|
||||
setGame(undefined);
|
||||
setCrashMessage(undefined);
|
||||
@ -166,8 +176,9 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
const renderNewGameButton = () => {
|
||||
const newGame = (
|
||||
<Button
|
||||
context={context}
|
||||
text={context.texts.NewGame}
|
||||
color="#005f73"
|
||||
color={context.themeManager.theme.primary}
|
||||
onClick={newGameClick}
|
||||
/>
|
||||
);
|
||||
@ -184,14 +195,18 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const menuTextColor = getColorByLuminance(
|
||||
context.themeManager.theme.appBarBgColor,
|
||||
context.themeManager.theme.primary,
|
||||
context.themeManager.theme.primaryLight
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
background: "#EEEEEE",
|
||||
background: context.themeManager.theme.background,
|
||||
flex: "1",
|
||||
}}
|
||||
>
|
||||
@ -205,8 +220,8 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
borderTopRightRadius: "1vw",
|
||||
minWidth: "10vw",
|
||||
minHeight: "1vw",
|
||||
background: "#2F2504",
|
||||
color: "white",
|
||||
background: context.themeManager.theme.primary,
|
||||
color: context.themeManager.theme.primaryLight,
|
||||
}}
|
||||
>
|
||||
{connectionStateText()}
|
||||
@ -214,8 +229,8 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
padding: "0px 50px",
|
||||
background: "rgb(228, 228, 228)",
|
||||
padding: "0px 4vw",
|
||||
background: context.themeManager.theme.appBarBgColor,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@ -223,15 +238,77 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
alignSelf: "stretch",
|
||||
}}
|
||||
>
|
||||
<h1 style={{ margin: "10px 0px" }}>{context.texts.Mancala}</h1>
|
||||
<div>
|
||||
<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
|
||||
color="#005f73"
|
||||
context={context}
|
||||
color={context.themeManager.theme.primary}
|
||||
text={context.texts.Leave}
|
||||
onClick={leaveGame}
|
||||
/>
|
||||
@ -239,6 +316,7 @@ const Home: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
|
||||
</div>
|
||||
</div>
|
||||
<InfoPanel
|
||||
context={context}
|
||||
game={game}
|
||||
crashMessage={crashMessage}
|
||||
userKey={userKey}
|
||||
|
||||
@ -12,6 +12,7 @@ import { v4 } from "uuid";
|
||||
import { Context } from "../context";
|
||||
import BoardViewModelFactory from "../factory/BoardViewModelFactory";
|
||||
import { PitViewModelFactory } from "../factory/PitViewModelFactory";
|
||||
import { getColorByLuminance } from "../util/ColorUtil";
|
||||
import BoardViewModel from "../viewmodel/BoardViewModel";
|
||||
|
||||
const animationUpdateInterval = 300;
|
||||
@ -130,6 +131,11 @@ export default class PitAnimator {
|
||||
pitViewModel.pitColor = theme.pitGetRivalStonePitAnimateColor;
|
||||
pitViewModel.stoneCount = 0;
|
||||
}
|
||||
pitViewModel.stoneColor = getColorByLuminance(
|
||||
pitViewModel.pitColor,
|
||||
theme.stoneColor,
|
||||
theme.stoneLightColor
|
||||
);
|
||||
}
|
||||
|
||||
startAnimationUpdateCyle() {
|
||||
@ -156,7 +162,7 @@ export default class PitAnimator {
|
||||
}
|
||||
}
|
||||
|
||||
private getBoardViewModelFromGame(game: MancalaGame): BoardViewModel {
|
||||
public getBoardViewModelFromGame(game: MancalaGame): BoardViewModel {
|
||||
const pitViewModels = this.createPitViewModelsFromGame(game);
|
||||
return BoardViewModelFactory.create(v4(), pitViewModels);
|
||||
}
|
||||
@ -165,7 +171,7 @@ export default class PitAnimator {
|
||||
return game.board.pits.map((pit) => {
|
||||
const theme = this.context.themeManager.theme;
|
||||
const stoneCount = pit.stoneCount;
|
||||
const stoneColor = theme.ballColor;
|
||||
const stoneColor = theme.stoneColor;
|
||||
const pitColor = theme.holeColor;
|
||||
const id = pit.index.toString();
|
||||
return PitViewModelFactory.create({
|
||||
|
||||
@ -2,6 +2,7 @@ import { Bank, MancalaGame, Pit } from "mancala.js";
|
||||
import * as React from "react";
|
||||
import { FunctionComponent, useState } from "react";
|
||||
import { Context } from "../context";
|
||||
import { getColorByLuminance } from "../util/ColorUtil";
|
||||
import BoardViewModel from "../viewmodel/BoardViewModel";
|
||||
import PitViewModel from "../viewmodel/PitViewModel";
|
||||
|
||||
@ -67,6 +68,11 @@ const StoreView: FunctionComponent<{
|
||||
const balls = [...range(pitViewModel.stoneCount)].map((i) => (
|
||||
<BallView color={pitViewModel.stoneColor} />
|
||||
));
|
||||
const textColor = getColorByLuminance(
|
||||
pitViewModel.pitColor,
|
||||
context.themeManager.theme.primary,
|
||||
context.themeManager.theme.primaryLight
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -91,7 +97,7 @@ const StoreView: FunctionComponent<{
|
||||
fontFamily: "monospace",
|
||||
fontWeight: "bold",
|
||||
fontSize: "2vw",
|
||||
color: context.themeManager.theme.ballColor,
|
||||
color: textColor,
|
||||
}}
|
||||
>
|
||||
{balls.length}
|
||||
@ -150,16 +156,14 @@ const BoardView: FunctionComponent<{
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
margin: "10px",
|
||||
padding: "20px",
|
||||
margin: "1vw",
|
||||
padding: "2vw",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(8, 11vw)",
|
||||
gridTemplateRows: "repeat(2, 11vw)",
|
||||
borderRadius: "3vw",
|
||||
transition: "background-color 0.5s",
|
||||
background: isUserTurn
|
||||
? theme.boardColor
|
||||
: theme.boardColorWhenPlayerTurn,
|
||||
background: theme.boardColor,
|
||||
}}
|
||||
>
|
||||
{userKey === game.player2Id ? (
|
||||
|
||||
@ -1,22 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { FunctionComponent } from "react"
|
||||
|
||||
const Button: FunctionComponent<{ text: String,onClick: () => void, color: string }> = ({ text, color, onClick }) => {
|
||||
import * as React from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Context } from "../context";
|
||||
import { getColorByLuminance } from "../util/ColorUtil";
|
||||
|
||||
const Button: FunctionComponent<{
|
||||
context: Context;
|
||||
text: String;
|
||||
onClick: () => void;
|
||||
color: string;
|
||||
}> = ({ context, text, color, onClick }) => {
|
||||
const textColor = getColorByLuminance(
|
||||
color,
|
||||
context.themeManager.theme.primary,
|
||||
context.themeManager.theme.primaryLight
|
||||
);
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
style={{
|
||||
background: color,
|
||||
color : "white",
|
||||
color: textColor,
|
||||
margin: "5px",
|
||||
padding: "10px",
|
||||
border: "none",
|
||||
borderRadius: "3vw",
|
||||
}} >
|
||||
borderRadius: "4vw",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Button
|
||||
export default Button;
|
||||
|
||||
@ -1,66 +1,109 @@
|
||||
import { MancalaGame } from 'mancala.js';
|
||||
import * as React from 'react';
|
||||
import { FunctionComponent } from "react"
|
||||
import { context } from '../context';
|
||||
import { MancalaGame } from "mancala.js";
|
||||
import * as React from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import { Context } from "../context";
|
||||
import { getColorByLuminance } from "../util/ColorUtil";
|
||||
|
||||
function getInfoPanelTextByGameState(params: {
|
||||
context: Context;
|
||||
game: MancalaGame;
|
||||
crashMessage: string;
|
||||
userKey: string;
|
||||
userKeyWhoLeave: string;
|
||||
searchingOpponent: boolean;
|
||||
}): string {
|
||||
const {
|
||||
context,
|
||||
game,
|
||||
crashMessage,
|
||||
userKey,
|
||||
userKeyWhoLeave,
|
||||
searchingOpponent,
|
||||
} = params;
|
||||
if (searchingOpponent) {
|
||||
return context.texts.SearchingOpponent + " " + context.texts.PleaseWait;
|
||||
} else if (crashMessage) {
|
||||
return context.texts.GameCrashed + " " + crashMessage;
|
||||
} else if (userKeyWhoLeave) {
|
||||
let message = context.texts.OpponentLeavesTheGame;
|
||||
if (userKeyWhoLeave == userKey) {
|
||||
message = context.texts.YouLeftTheGame;
|
||||
}
|
||||
return message;
|
||||
} else if (game?.state == "ended") {
|
||||
const wonPlayer = game.getWonPlayerId();
|
||||
let whoWon =
|
||||
game.getWonPlayerId() === userKey
|
||||
? context.texts.YouWon
|
||||
: context.texts.YouLost;
|
||||
if (!wonPlayer) {
|
||||
whoWon = context.texts.GameDraw;
|
||||
}
|
||||
return context.texts.GameEnded + " " + whoWon;
|
||||
} else {
|
||||
return game?.checkIsPlayerTurn(userKey)
|
||||
? context.texts.YourTurn
|
||||
: context.texts.OpponentTurn;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const InfoPanelContainer: FunctionComponent<{
|
||||
context: Context;
|
||||
color: string;
|
||||
}> = (props) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: props.color,
|
||||
padding: "1vw 2vw",
|
||||
marginTop: "1vw",
|
||||
borderRadius: "10vw",
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InfoPanel: FunctionComponent<{
|
||||
game: MancalaGame,
|
||||
crashMessage: string,
|
||||
userKey: string,
|
||||
userKeyWhoLeave: string,
|
||||
searchingOpponent: boolean
|
||||
context: Context;
|
||||
game: MancalaGame;
|
||||
crashMessage: string;
|
||||
userKey: string;
|
||||
userKeyWhoLeave: string;
|
||||
searchingOpponent: boolean;
|
||||
}> = ({
|
||||
game, crashMessage, userKey, userKeyWhoLeave, searchingOpponent }) => {
|
||||
if (searchingOpponent) {
|
||||
context,
|
||||
game,
|
||||
crashMessage,
|
||||
userKey,
|
||||
userKeyWhoLeave,
|
||||
searchingOpponent,
|
||||
}) => {
|
||||
const isUserTurn = game?.checkIsPlayerTurn(userKey);
|
||||
const containerColor = isUserTurn
|
||||
? context.themeManager.theme.playerTurnColor
|
||||
: context.themeManager.theme.holeColor;
|
||||
const textColor = getColorByLuminance(
|
||||
containerColor,
|
||||
context.themeManager.theme.primary,
|
||||
context.themeManager.theme.primaryLight
|
||||
);
|
||||
return (
|
||||
<h4>{
|
||||
context.texts.SearchingOpponent + " " + context.texts.PleaseWait
|
||||
}</h4>
|
||||
)
|
||||
}
|
||||
|
||||
if (crashMessage) {
|
||||
return (
|
||||
<h4>{
|
||||
context.texts.GameCrashed + " " + crashMessage
|
||||
}</h4>
|
||||
)
|
||||
}
|
||||
|
||||
if (userKeyWhoLeave) {
|
||||
let message = context.texts.OpponentLeavesTheGame
|
||||
|
||||
if (userKeyWhoLeave == userKey) {
|
||||
message = context.texts.YouLeftTheGame
|
||||
}
|
||||
return (
|
||||
<h4>
|
||||
{message}
|
||||
<InfoPanelContainer context={context} color={containerColor}>
|
||||
<h4 style={{ margin: "0", color: textColor }}>
|
||||
{getInfoPanelTextByGameState({
|
||||
context,
|
||||
game,
|
||||
crashMessage,
|
||||
userKey,
|
||||
userKeyWhoLeave,
|
||||
searchingOpponent,
|
||||
})}
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
</InfoPanelContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
if (game) {
|
||||
if (game.state == "ended") {
|
||||
const wonPlayer = game.getWonPlayerId();
|
||||
let whoWon = game.getWonPlayerId() === userKey ? context.texts.YouWon : context.texts.YouLost
|
||||
if(!wonPlayer){
|
||||
whoWon = context.texts.GameDraw
|
||||
}
|
||||
return (
|
||||
<h4>{
|
||||
context.texts.GameEnded + " " + whoWon
|
||||
}</h4>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<h4>{game.checkIsPlayerTurn(userKey) ? context.texts.YourTurn : context.texts.OpponentTurn}</h4>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return <h4></h4>
|
||||
}
|
||||
|
||||
export default InfoPanel
|
||||
export default InfoPanel;
|
||||
|
||||
@ -2,7 +2,6 @@ import { Texts, TrTr } from "./const/texts";
|
||||
import { RTMT } from "./rtmt/rtmt";
|
||||
import { RTMTWS } from "./rtmt/rtmt_websocket";
|
||||
import { UserKeyStore, UserKeyStoreImpl } from "./store/key_store";
|
||||
import defaultTheme from "./theme/DefaultTheme";
|
||||
import ThemeManager from "./theme/ThemeManager";
|
||||
|
||||
export type Context = {
|
||||
@ -16,7 +15,7 @@ export const initContext = () => {
|
||||
const rtmt = new RTMTWS();
|
||||
const userKeyStore = new UserKeyStoreImpl();
|
||||
const texts = TrTr;
|
||||
const themeManager = new ThemeManager(defaultTheme);
|
||||
const themeManager = new ThemeManager();
|
||||
return {
|
||||
rtmt: rtmt,
|
||||
userKeyStore: userKeyStore,
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
<script src="./App.tsx"></script>
|
||||
</body>
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
|
||||
|
||||
<style>
|
||||
html{
|
||||
|
||||
32
src/theme/DarkTheme.ts
Normal file
32
src/theme/DarkTheme.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
// from https://colorhunt.co/palette/0f0e0e5412128b9a46eeeeee
|
||||
const colors = {
|
||||
primary: "#541212",
|
||||
secondary: "#0F0E0E",
|
||||
tertiary: "#8B9A46",
|
||||
quaternary: "#EEEEEE",
|
||||
};
|
||||
|
||||
const colorSpecial = "#990000";
|
||||
|
||||
const darkTheme: Theme = {
|
||||
id: "2",
|
||||
name: "Dark Theme",
|
||||
background: colors.primary,
|
||||
appBarBgColor: colors.secondary,
|
||||
primary: colors.primary,
|
||||
primaryLight: colors.quaternary,
|
||||
playerTurnColor: colors.secondary,
|
||||
boardColor: colors.secondary,
|
||||
holeColor: colors.tertiary,
|
||||
pitSelectedColor: colors.tertiary,
|
||||
stoneColor: colors.primary,
|
||||
stoneLightColor: colors.tertiary,
|
||||
pitGameMoveAnimateColor: colors.quaternary,
|
||||
pitEmptyPitAnimateColor: colorSpecial,
|
||||
pitLastStoneInBankPitAnimateColor: colorSpecial,
|
||||
pitGetRivalStonePitAnimateColor: colorSpecial,
|
||||
};
|
||||
|
||||
export default darkTheme;
|
||||
@ -1,19 +1,32 @@
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
// https://colorhunt.co/palette/7d5a50b4846ce5b299fcdec0
|
||||
const colors = {
|
||||
primary: "#7D5A50",
|
||||
secondary: "#B4846C",
|
||||
tertiary: "#E5B299",
|
||||
quaternary: "#FCDEC0",
|
||||
};
|
||||
|
||||
const colorSpecial = "#F6A9A9";
|
||||
|
||||
const defaultTheme: Theme = {
|
||||
id: "1",
|
||||
name: "Default Light Theme",
|
||||
background: "#EEEEEE",
|
||||
boardColor: "#4D606E",
|
||||
boardColorWhenPlayerTurn: "#84b8a6",
|
||||
storeColor: "#3FBAC2",
|
||||
storeColorWhenPlayerTurn: "#6cab94",
|
||||
holeColor: "#D3D4D8",
|
||||
pitSelectedColor: "#8837fa",
|
||||
ballColor: "#393E46",
|
||||
ballLightColor: "#393E46",
|
||||
pitGameMoveAnimateColor: "#c9b43c",
|
||||
pitEmptyPitAnimateColor: "#5d7322",
|
||||
pitLastStoneInBankPitAnimateColor: "#9463f7",
|
||||
pitGetRivalStonePitAnimateColor: "#ff3d44",
|
||||
appBarBgColor: colors.quaternary,
|
||||
primary: colors.primary,
|
||||
primaryLight: colors.quaternary,
|
||||
playerTurnColor: colors.secondary,
|
||||
boardColor: colors.secondary,
|
||||
holeColor: colors.quaternary,
|
||||
pitSelectedColor: colors.tertiary,
|
||||
stoneColor: colors.primary,
|
||||
stoneLightColor: colors.tertiary,
|
||||
pitGameMoveAnimateColor: colors.tertiary,
|
||||
pitEmptyPitAnimateColor: colorSpecial,
|
||||
pitLastStoneInBankPitAnimateColor: colorSpecial,
|
||||
pitGetRivalStonePitAnimateColor: colorSpecial,
|
||||
};
|
||||
|
||||
export default defaultTheme;
|
||||
|
||||
22
src/theme/OldTheme.ts
Normal file
22
src/theme/OldTheme.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
const oldTheme: Theme = {
|
||||
id: "0",
|
||||
name: "Old Theme",
|
||||
background: "#EEEEEE",
|
||||
appBarBgColor: "#e4e4e4",
|
||||
primary: "#4D606E",
|
||||
primaryLight: "#EEEEEE",
|
||||
playerTurnColor: "#84b8a6",
|
||||
boardColor: "#4D606E",
|
||||
holeColor: "#D3D4D8",
|
||||
pitSelectedColor: "#8837fa",
|
||||
stoneColor: "#393E46",
|
||||
stoneLightColor: "#EEEEEE",
|
||||
pitGameMoveAnimateColor: "#c9b43c",
|
||||
pitEmptyPitAnimateColor: "#5d7322",
|
||||
pitLastStoneInBankPitAnimateColor: "#9463f7",
|
||||
pitGetRivalStonePitAnimateColor: "#ff3d44",
|
||||
};
|
||||
|
||||
export default oldTheme;
|
||||
@ -1,13 +1,16 @@
|
||||
export type Theme = {
|
||||
id: string;
|
||||
name: string;
|
||||
primary: string;
|
||||
primaryLight: string;
|
||||
background: string;
|
||||
appBarBgColor: string;
|
||||
playerTurnColor: string;
|
||||
boardColor: string;
|
||||
boardColorWhenPlayerTurn: string;
|
||||
storeColor: string;
|
||||
storeColorWhenPlayerTurn: string;
|
||||
holeColor: string;
|
||||
pitSelectedColor: string;
|
||||
ballColor: string;
|
||||
ballLightColor: string;
|
||||
stoneColor: string;
|
||||
stoneLightColor: string;
|
||||
pitGameMoveAnimateColor: string;
|
||||
pitEmptyPitAnimateColor: string;
|
||||
pitLastStoneInBankPitAnimateColor: string;
|
||||
|
||||
@ -1,18 +1,40 @@
|
||||
import darkTheme from "./DarkTheme";
|
||||
import defaultTheme from "./DefaultTheme";
|
||||
import oldTheme from "./OldTheme";
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
export const themes = [defaultTheme, darkTheme, oldTheme];
|
||||
|
||||
const THEME_ID = "theme_id";
|
||||
|
||||
export default class ThemeManager {
|
||||
_theme: Theme;
|
||||
onThemeChange: (theme: Theme) => void;
|
||||
constructor(theme: Theme) {
|
||||
this._theme = theme;
|
||||
constructor() {
|
||||
this._theme = this.readFromLocalStorage() || defaultTheme;
|
||||
}
|
||||
|
||||
public get theme() {
|
||||
return this._theme;
|
||||
}
|
||||
|
||||
public set theme(value) {
|
||||
public set theme(value: Theme) {
|
||||
this._theme = value;
|
||||
this.onThemeChange?.(value);
|
||||
this.writetToLocalStorage(value);
|
||||
}
|
||||
|
||||
private writetToLocalStorage(value: Theme) {
|
||||
localStorage.setItem(THEME_ID, value.id);
|
||||
}
|
||||
|
||||
private readFromLocalStorage(): Theme {
|
||||
const themeID = localStorage.getItem(THEME_ID);
|
||||
const theme = themes.find((eachTheme: Theme) => themeID === eachTheme.id);
|
||||
return theme;
|
||||
}
|
||||
|
||||
public get themes(): Theme[] {
|
||||
return themes;
|
||||
}
|
||||
}
|
||||
|
||||
51
src/util/ColorUtil.ts
Normal file
51
src/util/ColorUtil.ts
Normal file
@ -0,0 +1,51 @@
|
||||
//from this gist https://gist.github.com/jfsiii/5641126
|
||||
|
||||
// from http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
export function relativeLuminanceW3C(
|
||||
R8bit: number,
|
||||
G8bit: number,
|
||||
B8bit: number
|
||||
) {
|
||||
const RsRGB = R8bit / 255;
|
||||
const GsRGB = G8bit / 255;
|
||||
const BsRGB = B8bit / 255;
|
||||
|
||||
const R =
|
||||
RsRGB <= 0.03928 ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
|
||||
const G =
|
||||
GsRGB <= 0.03928 ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
|
||||
const B =
|
||||
BsRGB <= 0.03928 ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);
|
||||
|
||||
// For the sRGB colorspace, the relative luminance of a color is defined as:
|
||||
const L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
||||
|
||||
return L;
|
||||
}
|
||||
|
||||
export function relativeLuminanceW3CHexColor(hexColor: string): number {
|
||||
const [r, g, b] = hexToRgb(hexColor);
|
||||
return relativeLuminanceW3C(r, g, b);
|
||||
}
|
||||
|
||||
// from https://www.codegrepper.com/code-examples/javascript/javascript+convert+color+string+to+rgb
|
||||
export function rgbToHex(r: number, g: number, b: number): string {
|
||||
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||
}
|
||||
|
||||
// from https://www.codegrepper.com/code-examples/javascript/javascript+convert+color+string+to+rgb
|
||||
export function hexToRgb(
|
||||
hex: string,
|
||||
result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
): number[] {
|
||||
return result ? result.map((i) => parseInt(i, 16)).slice(1) : null;
|
||||
//returns [23, 14, 45] -> reformat if needed
|
||||
}
|
||||
|
||||
export function getColorByLuminance(
|
||||
color: string,
|
||||
lightColor: string,
|
||||
darkColor: string
|
||||
): string {
|
||||
return relativeLuminanceW3CHexColor(color) < 0.5 ? darkColor : lightColor;
|
||||
}
|
||||
36
yarn.lock
36
yarn.lock
@ -1011,6 +1011,21 @@
|
||||
"@parcel/utils" "^1.11.0"
|
||||
physical-cpu-count "^2.0.0"
|
||||
|
||||
"@szhsin/react-menu@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@szhsin/react-menu/-/react-menu-3.0.2.tgz#d22971c53d56e6d404c9d3c98f533907cd8f03dc"
|
||||
integrity sha512-m9Ly+cT+CxQx3xhq90CVaOLQWU7f7UKeMxfDt1gPYV23tDwEe8Zo6PO547qPlAEGEwwb9MdA38U8OyueXKJc2g==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
react-transition-state "^1.1.4"
|
||||
|
||||
"@types/@szhsin/react-menu":
|
||||
version "3.0.2"
|
||||
resolved "https://codeload.github.com/szhsin/react-menu/tar.gz/28284b2183801fb4f6a95e9270ce580441c5da70"
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
react-transition-state "^1.1.4"
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.5"
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
|
||||
@ -3364,7 +3379,7 @@ log-symbols@^2.2.0:
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
|
||||
loose-envify@^1.1.0:
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@ -4333,6 +4348,15 @@ process@^0.11.10:
|
||||
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
|
||||
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
|
||||
|
||||
prop-types@^15.7.2:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
psl@^1.1.28:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz"
|
||||
@ -4433,6 +4457,16 @@ react-dom@^17.0.2:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-transition-state@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-state/-/react-transition-state-1.1.4.tgz#113224eaa27e0ff81661305e44d5e0348cdf61ac"
|
||||
integrity sha512-6nQLWWx95gYazCm6OdtD1zGbRiirvVXPrDtHAGsYb4xs9spMM7bA8Vx77KCpjL8PJ8qz1lXFGz2PTboCSvt7iw==
|
||||
|
||||
react@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user