Private
Public Access
1
0

chg: dev: massive refactor on front-end - and remove unnecessary deps #4

This commit is contained in:
2026-04-10 17:57:26 +02:00
parent 086d6c601e
commit b57442bec1
22 changed files with 2619 additions and 1425 deletions

View File

@@ -0,0 +1,28 @@
/*
* This file is part of the SplendidBear Websites' projects.
*
* Copyright (c) 2026 @ www.splendidbear.org
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react';
import { useGame } from '../contexts/GameContext';
import useServerComm from '../hooks/useServerComm';
import GridControl from './grid/GridControl';
export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
const { gridReady } = useGame();
const { onClick, resign } = useServerComm(gameAssoc, gameInherited, isEnvDev);
if (!gridReady) {
return (
<div className="game-overlay">
<div className="game-overlay-window"><h1>Loading</h1></div>
</div>
);
}
return <GridControl onClick={onClick} resign={resign} />;
};

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { useGame } from '../../contexts/GameContext';
import GridField from './GridField';
import UserControl from '../user/UserControl';
import { BOMB_SYMBOLS, bombRadius } from '../../constants';
const GridControl = ({ onClick, resign }) => {
const {
overlay, overlayTitle, overlaySubTitle,
webPlayer, activePlayer, mines, foundMines, bombSelected,
red, blue, cells, setCells, onBombToggle,
} = useGame();
const handleHover = (row, col) => {
if (!bombSelected) return;
const activeColor = activePlayer ? 'blue' : 'red';
if (activeColor !== webPlayer) return;
setCells(prev => {
const next = prev.map(r => r.map(c =>
null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c,
));
bombRadius(row, col, prev.length, prev[0]?.length ?? 0).forEach(([r, c], i) => {
if (!next[r]?.[c]) return;
next[r] = [...next[r]];
next[r][c] = { ...next[r][c], bombTargetArea: BOMB_SYMBOLS[i] };
});
return next;
});
};
return (
<div className="game-wrapper">
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
<div className="game-overlay-window">
<h1>{overlayTitle}</h1>
<h2>{overlaySubTitle}</h2>
</div>
</div>
<UserControl
webPlayer={webPlayer}
activePlayer={activePlayer}
mines={mines}
foundMines={foundMines}
red={red}
blue={blue}
onBombToggle={onBombToggle}
resign={resign}
/>
<div className="grid-container">
<div className="grid">
{cells.flatMap((row, r) =>
row.map((cell, c) => (
<GridField
key={`${r}_${c}`}
cell={cell}
onClick={() => onClick([r, c])}
onMouseEnter={() => handleHover(r, c)}
/>
))
)}
</div>
</div>
</div>
);
};
export default GridControl;

View File

@@ -0,0 +1,45 @@
import React, { memo } from 'react';
import { IMG } from '../../constants';
const bombSrc = area => {
if (null === area) return null;
const vert = ['left', 'center', 'right'][area[0]] ?? null;
const hor = ['top', 'middle', 'bottom'][area[1]] ?? null;
if (null === vert || null === hor) return IMG + 'bg-bomb-empty-outbg.png';
return `${IMG}bg-bomb-${hor}-${vert}-outbg.png`;
};
const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
const { currentImage, currentObj, active, lastClickedRed, lastClickedBlue, bombTargetArea } = cell;
const fieldClass = 'field'
+ (active ? ' active' : '')
+ (active && 'm' === currentObj ? ' mine' : '')
+ ' color-' + currentObj;
const inner = isNaN(currentImage)
? (
<div className="flag-mine"><img src={currentImage} alt="" />
<div className="flag-mine-base" />
</div>
)
: currentImage ? <div className="flag-number">{currentImage}</div> : null;
const bSrc = bombSrc(bombTargetArea);
const showLast = lastClickedRed || lastClickedBlue;
const lastClass = 'field-' + (lastClickedRed ? 'red' : 'blue') + '-last last-clicked';
const lastSrc = lastClickedRed ? IMG + 'bg-last-red-outbg.png' : IMG + 'bg-last-blue-outbg.png';
return (
<div className="field-wrapper" onClick={onClick} onMouseEnter={onMouseEnter}>
<img className="field-target" src={IMG + 'bg-target-outbg.png'} alt="" />
{bSrc && <img className="field-bomb-target" src={bSrc} alt="" />}
{showLast && <img className={lastClass} src={lastSrc} alt="" />}
<div className={fieldClass}>
<div className="field-corner">{inner}</div>
</div>
</div>
);
});
export default GridField;

View File

@@ -0,0 +1,39 @@
import React, { memo } from 'react';
const SRC = '/images/';
const User = memo(function User({
color, webPlayer,
name, desc, active, mines, haveBomb, enabledBomb,
onClickBombSelector,
}) {
const buzzClass = 'bomb-container'
+ (active && color === webPlayer && haveBomb && enabledBomb ? ' buzz' : '');
const bombImg = haveBomb
? SRC + (enabledBomb && active ? 'bg-bomb-outbg.png' : 'bg-bomb-disabled-outbg.png')
: SRC + 'bg-bomb-exploded-outbg.png';
return (
<div className={`user-container user-${color}`}>
<div className="user-header">
<div className="user-color">{color}</div>
{active && <img src={`${SRC}bg-cursor-${color}-outbg.png`} alt="" className="user-cursor" />}
<img src={`${SRC}bg-figure-${color}-outbg.png`} alt="" />
</div>
<div className="user-name"> {name} </div>
<div className="user-caret"><i className="fa fa-caret-down" /></div>
<div className="user-desc"> {desc} </div>
<div className="user-control">
<img src={`${SRC}bg-flag-${color}-outbg.png`} alt="" />
<div className="user-control-mines">{mines}</div>
<div className={buzzClass} onClick={onClickBombSelector}>
<div className="bomb"><img src={bombImg} alt="" /></div>
</div>
<div className="clear" />
</div>
</div>
);
});
export default User;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import User from './User';
const UserControl = ({ webPlayer, activePlayer, mines, foundMines, red, blue, onBombToggle, resign }) => {
const activeColor = activePlayer ? 'blue' : 'red';
const resignClass = 'resign' + (activeColor !== webPlayer ? ' disabled' : '');
const minesClass = 'active-mines' + (foundMines ? ' found-mine' : '');
const handleBombClick = (color, player) => {
const p = 'red' === color ? red : blue;
if (p.haveBomb && p.enabledBomb && activePlayer === player) {
onBombToggle();
}
};
return (
<div className="users">
<User
color="blue" webPlayer={webPlayer} {...blue}
onClickBombSelector={() => handleBombClick('blue', 1)}
/>
<div className="active-mines-container">
<i className="fa fa-star" />
<div className={minesClass}>
<div className="active-mines-nbr">{mines}</div>
<div className="active-mines-shine" />
</div>
<i className="fa fa-star" />
</div>
<div className="clear" />
<User
color="red" webPlayer={webPlayer} {...red}
onClickBombSelector={() => handleBombClick('red', 0)}
/>
<button className={resignClass} onClick={resign}>
<div className="resign-shine" />
Resign
</button>
</div>
);
}
export default UserControl;