From b57442bec1b0fe4317805663ee2b09777b016bf1 Mon Sep 17 00:00:00 2001 From: Lang <7system7@gmail.com> Date: Fri, 10 Apr 2026 17:57:26 +0200 Subject: [PATCH] chg: dev: massive refactor on front-end - and remove unnecessary deps #4 --- assets/js/app.jsx | 12 + assets/js/mine-seeker.js | 11 - assets/js/mine-seeker/MineSeeker.jsx | 26 + assets/js/mine-seeker/app.js | 428 ---- .../js/mine-seeker/components/GameBoard.jsx | 28 + .../components/grid/GridControl.jsx | 69 + .../mine-seeker/components/grid/GridField.jsx | 45 + .../js/mine-seeker/components/user/User.jsx | 39 + .../components/user/UserControl.jsx | 43 + assets/js/mine-seeker/constants.js | 63 + .../js/mine-seeker/contexts/GameContext.jsx | 233 ++ assets/js/mine-seeker/grid/grid-control.js | 301 --- assets/js/mine-seeker/grid/grid-field.js | 156 -- assets/js/mine-seeker/hooks/useServerComm.js | 264 +++ assets/js/mine-seeker/user/user-control.js | 82 - assets/js/mine-seeker/user/user.js | 90 - eslint.config.mjs | 6 + package-lock.json | 2050 ++++++++++++++--- package.json | 11 +- src/Util/TopicManager.php | 3 +- symfony.lock | 2 +- webpack.config.js | 82 +- 22 files changed, 2619 insertions(+), 1425 deletions(-) create mode 100644 assets/js/app.jsx delete mode 100644 assets/js/mine-seeker.js create mode 100644 assets/js/mine-seeker/MineSeeker.jsx delete mode 100644 assets/js/mine-seeker/app.js create mode 100644 assets/js/mine-seeker/components/GameBoard.jsx create mode 100644 assets/js/mine-seeker/components/grid/GridControl.jsx create mode 100644 assets/js/mine-seeker/components/grid/GridField.jsx create mode 100644 assets/js/mine-seeker/components/user/User.jsx create mode 100644 assets/js/mine-seeker/components/user/UserControl.jsx create mode 100644 assets/js/mine-seeker/constants.js create mode 100644 assets/js/mine-seeker/contexts/GameContext.jsx delete mode 100644 assets/js/mine-seeker/grid/grid-control.js delete mode 100644 assets/js/mine-seeker/grid/grid-field.js create mode 100644 assets/js/mine-seeker/hooks/useServerComm.js delete mode 100644 assets/js/mine-seeker/user/user-control.js delete mode 100644 assets/js/mine-seeker/user/user.js diff --git a/assets/js/app.jsx b/assets/js/app.jsx new file mode 100644 index 0000000..b6f2d6d --- /dev/null +++ b/assets/js/app.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MineSeeker from './mine-seeker/MineSeeker'; + +const wrapper = document.getElementById('mine-wrapper'); + +createRoot(wrapper).render( + , +); diff --git a/assets/js/mine-seeker.js b/assets/js/mine-seeker.js deleted file mode 100644 index cfb2252..0000000 --- a/assets/js/mine-seeker.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import MineSeeker from "./mine-seeker/app"; - -ReactDOM.render( - , - document.getElementById("mine-wrapper"), -); diff --git a/assets/js/mine-seeker/MineSeeker.jsx b/assets/js/mine-seeker/MineSeeker.jsx new file mode 100644 index 0000000..13be55f --- /dev/null +++ b/assets/js/mine-seeker/MineSeeker.jsx @@ -0,0 +1,26 @@ +import React, { useRef } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { GameProvider } from './contexts/GameContext'; +import { GameBoard } from './components/GameBoard'; + +const queryClient = new QueryClient(); + +const MineSeeker = ({ env, gameId }) => { + const isEnvDev = 'dev' === env; + const gameAssoc = useRef('' !== gameId ? gameId : crypto.randomUUID()).current; + const gameInherited = '' !== gameId; + + return ( + + + + + + ); +}; + +export default MineSeeker; diff --git a/assets/js/mine-seeker/app.js b/assets/js/mine-seeker/app.js deleted file mode 100644 index eaafd32..0000000 --- a/assets/js/mine-seeker/app.js +++ /dev/null @@ -1,428 +0,0 @@ -import React from 'react'; -import GridControl from './grid/grid-control'; - -class MineSeeker extends React.Component { - constructor(props) { - super(props); - - let gameAssoc = '' !== props.gameId ? props.gameId : crypto.randomUUID(); - let channel = 'mineseeker/channel/' + gameAssoc; - - this.state = { - env: props.env, - gameInherited: '' !== props.gameId, - gameAssoc: gameAssoc, - channel: channel, - stepCache: [], - connectionLost: false, - end: false, - }; - - /** SSE connection (not React state — no re-render needed) */ - this.eventSource = null; - - /** - * Users from GET /api/game/connect (inherited game). - * Passed to wSubscribe so it can determine the local player's colour. - */ - this.rpcUsers = null; - } - - currectGridSize() { - let $field = $('#mine-wrapper .grid'); - $field.height($field.width()); - - $field = $('#mine-wrapper .grid .field-wrapper'); - $field.height($field.width()); - - $('#mine-wrapper .grid .field-wrapper .field') - .height($field.width()) - .css('line-height', ($field.width() - 2) + 'px'); - } - - /** - * THE END - */ - makeGameEndIfItEnds(bluePoints, redPoints, resign = false, leftMines = []) { - let redWins = 25 < redPoints, - blueWins = 25 < bluePoints; - - if (redWins || blueWins || resign) { - this.refs.gridControl.state.sound.won.play(); - - if (false === resign) { - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: (redWins ? 'Red' : 'Blue') + ' wins the game!', - overlaySubTitle: 'Play again!', - }); - } - - this.refs.gridControl.showLeftMines(leftMines); - this.refs.gridControl.refs.userControl.setState({ activePlayer: false }); - this.refs.gridControl.refs.userControl.refs.red.setState({ desc: '' }); - this.refs.gridControl.refs.userControl.refs.blue.setState({ desc: '' }); - } - } - - resignProcess(color) { - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: color === this.refs.gridControl.state.webPlayer - ? 'You have been give up' - : 'Your opponent has been resigned', - overlaySubTitle: color === this.refs.gridControl.state.webPlayer - ? 'You LOSE!' - : 'You WIN!', - }); - - this.setState({ end: true }); - this.makeGameEndIfItEnds(0, 0, true); - } - - clickResign() { - let resignColor = this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red'; - this.publishStep({ resign: resignColor }); - this.resignProcess(this.refs.gridControl.state.webPlayer); - } - - clickResignCancel() { - this.refs.gridControl.setState({ overlay: false }); - } - - resign() { - let users = this.refs.gridControl.refs.userControl, - activePlayer = users.state.activePlayer ? 'blue' : 'red'; - - if (this.refs.gridControl.state.webPlayer === activePlayer) { - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: 'Are u sure u want to resign?!', - overlaySubTitle: ( -
- Yes - No! -
- ), - }); - } - } - - // ------------------------------------------------------------------ // - // Mercure message handlers (same logic as former WAMP callbacks) - // ------------------------------------------------------------------ // - - wSubscribe(payload, rpcUsers = null) { - 'dev' === this.state.env && console.info( - ('undefined' !== typeof payload.user ? payload.user : 'user') + ' has been subscribed to the channel!', - ); - - let firstUser = !rpcUsers; - - null === this.refs.gridControl.state.webPlayer && this.refs.gridControl.setState({ - webPlayer: payload.user === payload.users.blue - || ( - firstUser && '' !== payload.users.blueAnon - || !firstUser && ('' === rpcUsers.blueAnon && '' === rpcUsers.blue) - ) - ? 'blue' : 'red', - }); - - (900 > $(document).width()) && this.currectGridSize(); - - if ( - 2 === payload.userCnt - && ( - !this.state.connectionLost - || this.state.connectionLost && false === this.refs.gridControl.refs.userControl.state.activePlayer && !this.state.end - ) - ) { - this.makeGameStart(payload); - } - } - - wUnsubscribe(payload) { - 'dev' === this.state.env && console.info(payload.msg); - - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: 'The connection has been lost w/ your friend...', - overlaySubTitle: 'Please, restart the game!', - }); - } - - /** - * Opponent's step arrived via Mercure — apply the server-resolved revealed cells. - */ - wTopic(payload) { - if (this.refs.gridControl.state.webPlayer !== payload.data.player) { - if (null === payload.data.resign) { - 'dev' === this.state.env && console.warn( - payload.user + ' stepped to ' + payload.data.coords[0] + ',' + payload.data.coords[1], - ); - - this.refs.gridControl.refs.userControl.setState({ bombSelected: payload.data.bomb }); - this.refs.gridControl.applyStep(payload.data); - this.makeGameEndIfItEnds(payload.data.bluePoints, payload.data.redPoints, false, payload.data.leftMines); - } else { - this.resignProcess(payload.data.resign); - } - } - } - - // ------------------------------------------------------------------ // - // Mercure / SSE connection - // ------------------------------------------------------------------ // - - handleMercureMessage(payload) { - let isTopicEvent = 'undefined' !== typeof payload.data; - let isNotUnsubscribe = 'undefined' === typeof payload.msg; - - if (isTopicEvent) { - this.wTopic(payload); - } else if (isNotUnsubscribe) { - this.wSubscribe(payload, this.rpcUsers); - } else { - this.wUnsubscribe(payload); - } - - /** Reconnection: replay cached steps once both players are back */ - if (2 === payload.userCnt && this.state.connectionLost) { - 'dev' === this.state.env && console.info('Reconnection process'); - - let cache = this.state.stepCache; - cache.forEach(item => this.publishStep(item)); - this.setState({ connectionLost: false, stepCache: [] }); - } - } - - openEventSource() { - const wrapper = document.getElementById('mine-wrapper'); - const hubUrl = wrapper.dataset.mercureHubUrl; - const subscriberJwt = wrapper.dataset.mercureSubscriberJwt; - - const url = new URL(hubUrl, window.location.origin); - url.searchParams.append('topic', this.state.channel); - if (subscriberJwt) { - url.searchParams.append('authorization', subscriberJwt); - } - - if (this.eventSource) { - this.eventSource.close(); - } - - this.eventSource = new EventSource(url.toString()); - - this.eventSource.onmessage = event => { - this.handleMercureMessage(JSON.parse(event.data)); - }; - - this.eventSource.onopen = () => { - 'dev' === this.state.env && console.info('SSE connection opened'); - - if (this.state.connectionLost) { - 'dev' === this.state.env && console.info('SSE reconnected — rejoining channel'); - this.joinGame(); - } - }; - - this.eventSource.onerror = () => { - 'dev' === this.state.env && console.error('SSE connection error / lost'); - this.setState({ connectionLost: true }); - }; - } - - /** - * Initialise the grid control with an empty 16×16 grid. - * For inherited games, previously revealed cells are applied after the grid renders. - */ - wInit(revealedCells = []) { - // 16×16 grid of null — cells are filled in lazily as they are revealed by the server - let emptyGrid = Array.from({ length: 16 }, () => Array(16).fill(null)); - - this.refs.gridControl.setState({ - grid: emptyGrid, - channel: this.state.channel, - desc: { - buddy: ( -
- Your buddy is
- making a
- move. -
- ), - you: ( -
- It is your turn!
- Make a move. -
- ), - }, - overlay: true, - overlayTitle: 'We are waiting for your opponent...', - overlaySubTitle: this.state.gameAssoc - ? ( -
-

Share this unique link w/ your opponent

-
- -
- Play w/ me! -
- ) : '', - renderGridFields: this.state.gameAssoc, - }, () => { - // After the grid fields are rendered, apply any historically revealed cells - revealedCells.forEach(cell => this.refs.gridControl.applyRevealedCell(cell, cell.player)); - }); - } - - makeGameStart(payload) { - this.refs.gridControl.refs.userControl.setState({ activePlayer: 1 }); - - this.refs.gridControl.refs.userControl.refs.red.setState({ - name: '' !== payload.users.red ? payload.users.red : payload.users.redAnon, - }); - - this.refs.gridControl.refs.userControl.refs.blue.setState({ - name: '' !== payload.users.blue ? payload.users.blue : payload.users.blueAnon, - desc: 'blue' === this.refs.gridControl.state.webPlayer - ? this.refs.gridControl.state.desc.you - : this.refs.gridControl.state.desc.buddy, - active: true, - }); - - this.refs.gridControl.setState({ overlay: false }); - } - - /** POST /api/game/join — register this player, broadcast subscription event via Mercure */ - joinGame() { - return fetch('/api/game/join/' + this.state.gameAssoc, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - }).catch(e => 'dev' === this.state.env && console.error('Join error', e)); - } - - /** POST /api/game/step — persist a move, fan it out via Mercure, and return revealed cells */ - publishStep(dataPack) { - return fetch('/api/game/step/' + this.state.gameAssoc, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(dataPack), - }); - } - - // ------------------------------------------------------------------ // - // Lifecycle - // ------------------------------------------------------------------ // - - async componentDidMount() { - if (!this.state.connectionLost) { - try { - if (this.state.gameInherited) { - /** Fetch existing player info and previously revealed cells */ - const resp = await fetch('/api/game/connect/' + this.state.gameAssoc); - const b64 = await resp.text(); - const serverData = JSON.parse(window.atob(b64)); - - if ('undefined' === typeof serverData.users || null === serverData.users) { - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: 'This channel does not exists!', - overlaySubTitle: Restart game!, - }); - console.error('This channel does not exists!'); - return; - } - - this.rpcUsers = serverData.users; - this.openEventSource(); - this.wInit(serverData.revealedCells || []); - - } else { - /** Create the game record — the server generates the grid */ - await fetch('/api/game/start', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ gameAssoc: this.state.gameAssoc }), - }); - - this.openEventSource(); - this.wInit(); - } - - 'dev' === this.state.env && console.info('Connection initialised — joining channel'); - await this.joinGame(); - - } catch (e) { - 'dev' === this.state.env && console.error('Connection error', e); - setTimeout(() => this.componentDidMount(), 500); - } - - } else { - /** Hard-reconnect path */ - this.openEventSource(); - } - - /** Notify the server when the player closes / navigates away */ - window.addEventListener('pagehide', () => { - navigator.sendBeacon('/api/game/leave/' + this.state.gameAssoc); - }); - } - - cachePublish(dataPack) { - let cache = this.state.stepCache; - cache.push(dataPack); - this.setState({ stepCache: cache }); - } - - async onClick(coords) { - let activePlayer = this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red'; - - if (!this.refs.gridControl.checkFieldHasBeenNeverClicked(coords[0], coords[1])) { - return; - } - - if (activePlayer !== this.refs.gridControl.state.webPlayer) { - return; - } - - let dataPack = { - coords: coords, - player: activePlayer, - bomb: this.refs.gridControl.refs.userControl.state.bombSelected, - resign: null, - }; - - if (this.state.connectionLost) { - this.cachePublish(dataPack); - return; - } - - try { - const resp = await this.publishStep(dataPack); - const result = await resp.json(); - - this.refs.gridControl.applyStep(result); - this.makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines); - } catch (e) { - 'dev' === this.state.env && console.error('Step error', e); - } - } - - render() { - return ( - - ); - } -} - -export default MineSeeker; diff --git a/assets/js/mine-seeker/components/GameBoard.jsx b/assets/js/mine-seeker/components/GameBoard.jsx new file mode 100644 index 0000000..5b060f6 --- /dev/null +++ b/assets/js/mine-seeker/components/GameBoard.jsx @@ -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 ( +
+

Loading…

+
+ ); + } + + return ; +}; diff --git a/assets/js/mine-seeker/components/grid/GridControl.jsx b/assets/js/mine-seeker/components/grid/GridControl.jsx new file mode 100644 index 0000000..4f95d1e --- /dev/null +++ b/assets/js/mine-seeker/components/grid/GridControl.jsx @@ -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 ( +
+
+
+

{overlayTitle}

+

{overlaySubTitle}

+
+
+ +
+
+ {cells.flatMap((row, r) => + row.map((cell, c) => ( + onClick([r, c])} + onMouseEnter={() => handleHover(r, c)} + /> + )) + )} +
+
+
+ ); +}; + +export default GridControl; diff --git a/assets/js/mine-seeker/components/grid/GridField.jsx b/assets/js/mine-seeker/components/grid/GridField.jsx new file mode 100644 index 0000000..a22896e --- /dev/null +++ b/assets/js/mine-seeker/components/grid/GridField.jsx @@ -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) + ? ( +
+
+
+ ) + : currentImage ?
{currentImage}
: 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 ( +
+ + {bSrc && } + {showLast && } +
+
{inner}
+
+
+ ); +}); + +export default GridField; diff --git a/assets/js/mine-seeker/components/user/User.jsx b/assets/js/mine-seeker/components/user/User.jsx new file mode 100644 index 0000000..eb55adc --- /dev/null +++ b/assets/js/mine-seeker/components/user/User.jsx @@ -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 ( +
+
+
{color}
+ {active && } + +
+
{name}
+
+
{desc}
+
+ +
{mines}
+
+
+
+
+
+
+ ); +}); + +export default User; diff --git a/assets/js/mine-seeker/components/user/UserControl.jsx b/assets/js/mine-seeker/components/user/UserControl.jsx new file mode 100644 index 0000000..3b591b6 --- /dev/null +++ b/assets/js/mine-seeker/components/user/UserControl.jsx @@ -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 ( +
+ handleBombClick('blue', 1)} + /> +
+ +
+
{mines}
+
+
+ +
+
+ handleBombClick('red', 0)} + /> + +
+ ); +} + +export default UserControl; diff --git a/assets/js/mine-seeker/constants.js b/assets/js/mine-seeker/constants.js new file mode 100644 index 0000000..368eccf --- /dev/null +++ b/assets/js/mine-seeker/constants.js @@ -0,0 +1,63 @@ +import React from 'react'; + +export const ROWS = 16; +export const COLS = 16; +export const IMG = '/images/'; + +export const WAVES = { + 1: 'bg-wave-1-outbg.png', + 2: 'bg-wave-1-outbg.png', + 3: 'bg-wave-2-outbg.png', +}; + +export const PLAYER_DEF = { + name: '...', desc: '', active: false, mines: 0, haveBomb: true, enabledBomb: true, +}; + +export const DESC = { + buddy:
Your buddy is
making a
move.
, + you:
It is your turn!
Make a move.
, +}; + +export const BOMB_SYMBOLS = [ + [null, null], [0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2], + [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], + [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], + [null, null], [null, null], +]; + +export const bombRadius = (row, col, rows, cols) => { + const centre = 1 < row && row < rows - 2 && 1 < col && col < cols - 2; + if (!centre) { + col = Math.max(2, Math.min(col, cols - 3)); + row = Math.max(2, Math.min(row, rows - 3)); + } + return [ + [row, col], [row - 2, col - 2], [row - 2, col], [row - 2, col + 2], + [row, col - 2], [row, col + 2], [row + 2, col - 2], [row + 2, col], + [row + 2, col + 2], [row - 2, col + 1], [row - 2, col - 1], + [row - 1, col - 2], [row - 1, col - 1], [row - 1, col], [row - 1, col + 1], [row - 1, col + 2], + [row, col - 1], [row, col + 1], [row + 1, col - 2], [row + 1, col - 1], [row + 1, col], + [row + 1, col + 1], [row + 1, col + 2], [row + 2, col - 1], [row + 2, col + 1], + ]; +}; + +export const patchCells = (prev, patches) => { + const next = prev.map(r => [...r]); + for (const { row, col, ...rest } of patches) { + next[row][col] = { ...next[row][col], ...rest }; + } + return next; +}; + +export const initCells = () => + Array.from({ length: ROWS }, () => + Array.from({ length: COLS }, () => ({ + currentImage: IMG + WAVES[Math.floor(Math.random() * 3) + 1], + currentObj: 'w', + active: false, + lastClickedRed: false, + lastClickedBlue: false, + bombTargetArea: null, + })), + ); diff --git a/assets/js/mine-seeker/contexts/GameContext.jsx b/assets/js/mine-seeker/contexts/GameContext.jsx new file mode 100644 index 0000000..3c8f4a3 --- /dev/null +++ b/assets/js/mine-seeker/contexts/GameContext.jsx @@ -0,0 +1,233 @@ +import React, { createContext, useContext, useRef, useState } from 'react'; +import { Howl } from 'howler'; +import { IMG, PLAYER_DEF, DESC, patchCells, initCells } from '../constants'; + +// ── Context ────────────────────────────────────────────────────────────────── + +const GameContext = createContext(null); + +export const useGame = () => useContext(GameContext); + +// ── Provider ───────────────────────────────────────────────────────────────── + +export const GameProvider = ({ children }) => { + // Refs that are read inside async callbacks (stay in sync with state via sync* helpers) + const webPlayerRef = useRef(null); + const activePlayerRef = useRef(false); + const bombSelectedRef = useRef(false); + const connectionLostRef = useRef(false); + const redRef = useRef({ ...PLAYER_DEF }); + const blueRef = useRef({ ...PLAYER_DEF }); + const lastClickedRef = useRef({ red: null, blue: null }); + const endRef = useRef(false); + + const sounds = useRef({ + click: new Howl({ src: ['/sound/click.mp3'] }), + bomb: new Howl({ src: ['/sound/bomb.mp3'] }), + mine: new Howl({ src: ['/sound/mine.mp3'] }), + warning: new Howl({ src: ['/sound/warning.mp3'] }), + won: new Howl({ src: ['/sound/won.mp3'] }), + }); + + // Display state + const [webPlayer, setWebPlayer] = useState(null); + const [activePlayer, setActivePlayer] = useState(false); + const [overlay, setOverlay] = useState(true); + const [overlayTitle, setOverlayTitle] = useState(''); + const [overlaySubTitle, setOverlaySubTitle] = useState(''); + const [mines, setMines] = useState(51); + const [bombSelected, setBombSelected] = useState(false); + const [foundMines, setFoundMines] = useState(false); + const [red, setRed] = useState({ ...PLAYER_DEF }); + const [blue, setBlue] = useState({ ...PLAYER_DEF }); + const [cells, setCells] = useState(initCells); + const [gridReady, setGridReady] = useState(false); + const [connectionLost, setConnectionLost] = useState(false); + + // ── Sync helpers (keep ref + state in lockstep) ────────────────────────── + const syncWebPlayer = v => { + webPlayerRef.current = v; + setWebPlayer(v); + }; + const syncActivePlayer = v => { + activePlayerRef.current = v; + setActivePlayer(v); + }; + const syncBombSelected = v => { + bombSelectedRef.current = v; + setBombSelected(v); + }; + const syncConnLost = v => { + connectionLostRef.current = v; + setConnectionLost(v); + }; + const syncRed = fn => { + const n = fn(redRef.current); + redRef.current = n; + setRed(n); + }; + const syncBlue = fn => { + const n = fn(blueRef.current); + blueRef.current = n; + setBlue(n); + }; + + // ── Overlay ─────────────────────────────────────────────────────────────── + const showOverlay = (title, sub) => { + setOverlay(true); + setOverlayTitle(title); + setOverlaySubTitle(sub); + }; + + const hideOverlay = () => setOverlay(false); + + // ── Cell helpers ────────────────────────────────────────────────────────── + const applyRevealedCell = (cell, player, isMainCell = false) => { + const { row, col, value } = cell; + setCells(prev => { + if (prev[row][col].active) return prev; + const patch = 'm' === value + ? { currentImage: `${IMG}bg-flag-${player}-outbg.png`, currentObj: 'm', active: true } + : { currentImage: value, currentObj: value, active: true }; + if (isMainCell) { + patch.lastClickedRed = 'red' === player; + patch.lastClickedBlue = 'blue' === player; + } + return patchCells(prev, [{ row, col, ...patch }]); + }); + if (isMainCell) lastClickedRef.current = { ...lastClickedRef.current, [player]: [row, col] }; + }; + + const showLeftMines = (leftMines = []) => { + if (!leftMines.length) return; + setCells(prev => { + const patches = leftMines + .filter(({ row, col }) => !prev[row][col].active) + .map(({ row, col }) => ({ row, col, currentImage: IMG + 'bg-left-mine-outbg.png' })); + return patches.length ? patchCells(prev, patches) : prev; + }); + }; + + // ── Game logic ──────────────────────────────────────────────────────────── + const changePlayer = () => { + const wasColor = activePlayerRef.current ? 'blue' : 'red'; + const nextColor = activePlayerRef.current ? 'red' : 'blue'; + const nextVal = activePlayerRef.current ? 0 : 1; + const desc = wasColor === webPlayerRef.current ? DESC.buddy : DESC.you; + + syncActivePlayer(nextVal); + syncRed(p => ({ ...p, active: 'red' === nextColor, desc: 'red' === nextColor ? desc : '' })); + syncBlue(p => ({ ...p, active: 'blue' === nextColor, desc: 'blue' === nextColor ? desc : '' })); + }; + + const applyStep = stepData => { + const { player, bomb: isBomb, minesFound = 0, revealedCells = [], redPoints: rp, bluePoints: bp } = stepData; + + if (isBomb) { + sounds.current.bomb.play(); + } else if (0 < minesFound) { + const cur = ('red' === player ? redRef : blueRef).current.mines; + sounds.current[20 < cur + minesFound ? 'warning' : 'mine'].play(); + } else { + sounds.current.click.play(); + } + + const lc = lastClickedRef.current[player]; + setCells(prev => { + let next = prev; + if (lc) next = patchCells(next, [{ row: lc[0], col: lc[1], lastClickedRed: false, lastClickedBlue: false }]); + + revealedCells.forEach(({ row, col, value }, idx) => { + if (next[row][col].active) return; + const patch = 'm' === value + ? { currentImage: `${IMG}bg-flag-${player}-outbg.png`, currentObj: 'm', active: true } + : { currentImage: value, currentObj: value, active: true }; + if (0 === idx) { + patch.lastClickedRed = 'red' === player; + patch.lastClickedBlue = 'blue' === player; + } + next = patchCells(next, [{ row, col, ...patch }]); + }); + + if (isBomb) next = next.map(r => r.map(c => null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c)); + return next; + }); + + if (0 < revealedCells.length) { + lastClickedRef.current = { ...lastClickedRef.current, [player]: [revealedCells[0].row, revealedCells[0].col] }; + } + + if (0 < minesFound) { + setMines(51 - rp - bp); + setFoundMines(true); + setTimeout(() => setFoundMines(false), 500); + syncRed(p => ({ ...p, mines: 'red' === player ? rp : p.mines })); + syncBlue(p => ({ ...p, mines: 'blue' === player ? bp : p.mines })); + } + + syncRed(p => ({ ...p, enabledBomb: rp <= bp })); + syncBlue(p => ({ ...p, enabledBomb: bp <= rp })); + + if (isBomb || 0 === minesFound) changePlayer(); + + if (isBomb) { + syncBombSelected(false); + syncRed(p => 'red' === player ? { ...p, haveBomb: false } : p); + syncBlue(p => 'blue' === player ? { ...p, haveBomb: false } : p); + } + }; + + const makeGameEndIfItEnds = (bluePoints, redPoints, resign = false, leftMines = []) => { + const redWins = 25 < redPoints; + const blueWins = 25 < bluePoints; + + if (redWins || blueWins || resign) { + sounds.current.won.play(); + + if (!resign) showOverlay((redWins ? 'Red' : 'Blue') + ' wins the game!', 'Play again!'); + + showLeftMines(leftMines); + syncActivePlayer(false); + syncRed(p => ({ ...p, desc: '' })); + syncBlue(p => ({ ...p, desc: '' })); + } + }; + + const resignProcess = color => { + const wp = webPlayerRef.current; + showOverlay( + color === wp ? 'You have been give up' : 'Your opponent has been resigned', + color === wp ? 'You LOSE!' : 'You WIN!', + ); + endRef.current = true; + makeGameEndIfItEnds(0, 0, true); + }; + + const onBombToggle = () => { + const next = !bombSelectedRef.current; + syncBombSelected(next); + if (!next) { + setCells(prev => prev.map(r => r.map(c => null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c))); + } + }; + + // ── Context value ───────────────────────────────────────────────────────── + const value = { + // State (for rendering) + webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle, + mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, + // Refs (needed by useServerComm for async-safe reads) + webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef, + // Setters needed by useServerComm + setCells, setGridReady, + syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue, + // Game logic called by useServerComm + showOverlay, hideOverlay, + applyRevealedCell, applyStep, + makeGameEndIfItEnds, resignProcess, + // UI action (bomb toggle is pure state, no server call) + onBombToggle, + }; + + return {children}; +}; diff --git a/assets/js/mine-seeker/grid/grid-control.js b/assets/js/mine-seeker/grid/grid-control.js deleted file mode 100644 index 449c497..0000000 --- a/assets/js/mine-seeker/grid/grid-control.js +++ /dev/null @@ -1,301 +0,0 @@ -import React from 'react'; -import GridField from './grid-field'; -import UserControl from '../user/user-control'; -import { Howl } from 'howler'; - -class GridControl extends React.Component { - constructor(props) { - super(props); - - let click = new Howl({ src: ['/sound/click.mp3'] }), - bomb = new Howl({ src: ['/sound/bomb.mp3'] }), - mine = new Howl({ src: ['/sound/mine.mp3'] }), - warning = new Howl({ src: ['/sound/warning.mp3'] }), - won = new Howl({ src: ['/sound/won.mp3'] }); - - this.state = { - env: props.env, - webPlayer: null, - grid: null, - desc: null, - renderGridFields: false, - gridFields: [], - bombFieldCache: [], - overlay: false, - overlayTitle: '', - overlaySubTitle: '', - sound: { - click: click, - bomb: bomb, - mine: mine, - warning: warning, - won: won, - }, - lastClicked: { - red: null, - blue: null, - }, - }; - } - - refString(row, col) { - return 'gridField_' + row + '_' + col; - } - - checkFieldHasBeenNeverClicked(row, col) { - return !this.refs[this.refString(row, col)].state.active; - } - - getBombRadius(row, col) { - let isBombTargetCenter = 1 < row && row < this.state.grid.length - 2 && 1 < col && col < this.state.grid[row].length - 2; - - if (!isBombTargetCenter) { - col = 2 > col ? 2 : col; - row = 2 > row ? 2 : row; - row = row > this.state.grid.length - 3 ? this.state.grid.length - 3 : row; - col = col > this.state.grid[0].length - 3 ? this.state.grid[0].length - 3 : col; - } - - return [ - [row, col], [row - 2, col - 2], [row - 2, col], [row - 2, col + 2], [row, col - 2], [row, col + 2], - [row + 2, col - 2], [row + 2, col], [row + 2, col + 2], [row - 2, col + 1], [row - 2, col - 1], - [row - 1, col - 2], [row - 1, col - 1], [row - 1, col], [row - 1, col + 1], [row - 1, col + 2], - [row, col - 1], [row, col + 1], [row + 1, col - 2], [row + 1, col - 1], [row + 1, col], - [row + 1, col + 1], [row + 1, col + 2], [row + 2, col - 1], [row + 2, col + 1], - ]; - } - - getBombFieldRadius() { - return [ - [null, null], [0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2], - [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], - [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], - [null, null], [null, null], - ]; - } - - bombClear() { - if (this.state.bombFieldCache.length) { - for (var i = 0, j = this.state.bombFieldCache.length; i < j; i++) { - var cacheItem = this.state.bombFieldCache[i]; - this.refs[this.refString(cacheItem[0], cacheItem[1])] - .setState({ bombTargetArea: null }); - } - this.state.bombFieldCache = []; - } - } - - bombCreate(row, col) { - var bombFieldSymbols = this.getBombFieldRadius(), - bombFields = this.getBombRadius(row, col); - - for (var i = 0, j = bombFields.length; i < j; i++) { - this.state.bombFieldCache.push(bombFields[i]); - - this.refs[this.refString(bombFields[i][0], bombFields[i][1])] - .setState({ bombTargetArea: bombFieldSymbols[i] }); - } - } - - /** - * Show unrevealed mines at the end of the game. - * leftMines comes from the server and contains only cells not yet revealed. - */ - showLeftMines(leftMines = []) { - for (let mine of leftMines) { - let currentField = this.refs[this.refString(mine.row, mine.col)]; - if (currentField && !currentField.state.active) { - currentField.setState({ - currentImage: currentField.state.icons.root + currentField.state.icons.left, - }); - } - } - } - - /** set __ACTIVE__ player in the UserControl */ - changePlayer() { - var userControl = this.refs.userControl, - activePlayer = userControl.state.activePlayer ? 'blue' : 'red', - inactivePlayer = userControl.state.activePlayer ? 'red' : 'blue'; - - userControl.setState({ activePlayer: userControl.state.activePlayer ? 0 : 1 }); - - userControl.refs[activePlayer].setState({ active: false, desc: '' }); - userControl.refs[inactivePlayer].setState({ - active: true, - desc: activePlayer === this.state.webPlayer - ? this.state.desc.buddy - : this.state.desc.you, - }); - } - - /** - * Apply a single revealed cell to the grid UI. - * Called both for live steps and when reconstructing the board for a joining player. - * - * @param {object} cell - { row, col, value } value is 'm' | 0-8 - * @param {string} player - 'red' | 'blue' - * @param {boolean} isMainCell - true for the primary clicked cell (sets last-clicked highlight) - */ - applyRevealedCell(cell, player, isMainCell = false) { - let ref = this.refs[this.refString(cell.row, cell.col)]; - if (!ref || ref.state.active) { - return; - } - - if ('m' === cell.value) { - ref.setState({ - currentImage: ref.state.icons.root + ref.state.icons.flag[player], - currentObj: 'm', - active: true, - }); - } else { - ref.setState({ - currentImage: cell.value, - currentObj: cell.value, - active: true, - }); - } - - if (isMainCell) { - this.state.lastClicked[player] = [cell.row, cell.col]; - ref.setState({ - lastClickedRed: 'red' === player, - lastClickedBlue: 'blue' === player, - }); - } - } - - /** - * Apply a full step response from the server. - * stepData shape: { coords, player, bomb, revealedCells, minesFound, redPoints, bluePoints, resign, gameOver, - * leftMines } - */ - applyStep(stepData) { - let player = stepData.player; - let isBomb = stepData.bomb; - let minesFound = stepData.minesFound || 0; - let revealedCells = stepData.revealedCells || []; - - // Reset previous last-clicked highlight for this player - let lastClicked = this.state.lastClicked[player]; - if (lastClicked) { - let lastRef = this.refs[this.refString(lastClicked[0], lastClicked[1])]; - if (lastRef) { - lastRef.setState({ lastClickedRed: false, lastClickedBlue: false }); - } - } - - // Sound - if (isBomb) { - this.state.sound.bomb.play(); - } else if (0 < minesFound) { - let currentMines = this.refs.userControl.refs[player].state.mines; - this.state.sound[20 < (currentMines + minesFound) ? 'warning' : 'mine'].play(); - } else { - this.state.sound.click.play(); - } - - // Apply each revealed cell; first cell gets the last-clicked highlight - revealedCells.forEach((cell, idx) => { - this.applyRevealedCell(cell, player, 0 === idx); - }); - - // Update scores - let userControl = this.refs.userControl; - let inactivePlayer = 'red' === player ? 'blue' : 'red'; - - if (0 < minesFound) { - userControl.setState({ - mines: 51 - stepData.redPoints - stepData.bluePoints, - foundMines: true, - }, () => { - setTimeout(() => userControl.setState({ foundMines: false }), 500); - userControl.refs[player].setState({ - mines: 'red' === player ? stepData.redPoints : stepData.bluePoints, - }); - }); - } - - // Bomb-enabled status: a player may use their bomb when their score <= opponent's - userControl.refs.red.setState({ enabledBomb: stepData.redPoints <= stepData.bluePoints }); - userControl.refs.blue.setState({ enabledBomb: stepData.bluePoints <= stepData.redPoints }); - - // Change active player: always after a bomb, or when no mine was found on a normal click - if (isBomb || 0 === minesFound) { - this.changePlayer(); - } - - // Clean up bomb state - if (isBomb) { - userControl.setState({ bombSelected: false }); - userControl.refs[player].setState({ haveBomb: false }); - this.bombClear(); - } - } - - /** - * On Hover when you want to drop BOMB — show target area overlay - */ - onHoverGridField(coords) { - if (this.refs.userControl.state.bombSelected) { - var activePlayer = this.refs.userControl.state.activePlayer ? 'blue' : 'red'; - - if (activePlayer === this.state.webPlayer) { - this.bombClear(); - this.bombCreate(coords[0], coords[1]); - } else { - this.refs.userControl.setState({ bombSelected: false }); - } - } - } - - renderGridFields() { - for (let i = 0, j = this.state.grid.length; i < j; i++) { - for (let k = 0, l = this.state.grid[i].length; k < l; k++) { - this.state.gridFields.push( - , - ); - } - } - } - - render() { - /** Render the grid fields just one time in one party #12 */ - this.state.renderGridFields && this.renderGridFields(); - this.state.renderGridFields = false; - - return ( -
-
-
-

{this.state.overlayTitle}

-

{this.state.overlaySubTitle}

-
-
- -
-
- <> - {this.state.gridFields} - -
-
-
- ); - } -} - -export default GridControl; diff --git a/assets/js/mine-seeker/grid/grid-field.js b/assets/js/mine-seeker/grid/grid-field.js deleted file mode 100644 index 497e0c7..0000000 --- a/assets/js/mine-seeker/grid/grid-field.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; - -class GridField extends React.Component { - constructor(props) { - super(props); - - this.state = { - currentObj: 'w', - currentImage: null, - active: false, - lastClickedRed: false, - lastClickedBlue: false, - bombTargetArea: null, - icons: { - root: '/images/', - water: { - 1: 'bg-wave-1-outbg.png', - 2: 'bg-wave-1-outbg.png', - 3: 'bg-wave-2-outbg.png', - }, - flag: { - red: 'bg-flag-red-outbg.png', - blue: 'bg-flag-blue-outbg.png', - }, - target: { - lastBlue: 'bg-last-blue-outbg.png', - lastRed: 'bg-last-red-outbg.png', - crosshair: 'bg-target-outbg.png', - crosshairBomb: 'bg-target-bomb-outbg.png', - }, - left: 'bg-left-mine-outbg.png', - }, - }; - } - - componentWillMount() { - var wave = Math.floor(Math.random() * 3) + 1; - - this.setState({ - currentImage: this.state.icons.root + this.state.icons.water[wave], - }); - } - - classNameWhenActive() { - return 'field' - + (true === this.state.active ? ' active' : '') - + (true === this.state.active && 'm' === this.state.currentObj ? ' mine' : '') - + ' color-' + this.state.currentObj; - } - - currentImage() { - return isNaN(this.state.currentImage) - ? ( -
- current image -
-
- ) : this.state.currentImage ?
{this.state.currentImage}
: ''; - } - - lastClickedClass() { - return 'field-' - + (this.state.lastClickedRed ? 'red' : '') - + (this.state.lastClickedBlue ? 'blue' : '') + '-last last-clicked'; - } - - lastClickedSrc() { - return this.state.lastClickedRed - ? '/images/bg-last-red-outbg.png' - : '/images/bg-last-blue-outbg.png'; - } - - currentLastClicked() { - return this.state.lastClickedRed || this.state.lastClickedBlue - ? ( - blue last - ) : ''; - } - - createBombTarget() { - if (null !== this.state.bombTargetArea) { - let vert, hor = ''; - - switch (this.state.bombTargetArea[0]) { - case 0: - vert = 'left'; - break; - case 1: - vert = 'center'; - break; - case 2: - vert = 'right'; - break; - default: - vert = null; - break; - } - - switch (this.state.bombTargetArea[1]) { - case 0: - hor = 'top'; - break; - case 1: - hor = 'middle'; - break; - case 2: - hor = 'bottom'; - break; - default: - vert = null; - break; - } - - var src = null === vert - ? '/images/bg-bomb-empty-outbg.png' - : '/images/bg-bomb-' + hor + '-' + vert + '-outbg.png'; - - return ( - bomb target - ); - } - } - - render() { - return ( -
- target - {this.createBombTarget()} - {this.currentLastClicked()} -
-
- {this.currentImage()} -
-
-
- ); - } -} - -export default GridField; diff --git a/assets/js/mine-seeker/hooks/useServerComm.js b/assets/js/mine-seeker/hooks/useServerComm.js new file mode 100644 index 0000000..55731cc --- /dev/null +++ b/assets/js/mine-seeker/hooks/useServerComm.js @@ -0,0 +1,264 @@ +import React, { useEffect, useRef } from 'react'; +import { useQuery, useMutation } from '@tanstack/react-query'; +import { useGame } from '../contexts/GameContext'; +import { DESC } from '../constants'; + +/** + * Handles all server communication: SSE (Mercure), REST calls, and the + * initialization lifecycle. Exposes only the UI-facing callbacks the + * component needs: onClick, resign. + */ +const useServerComm = (gameAssoc, gameInherited, isEnvDev) => { + const { + // Async-safe refs + webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef, + // State setters + setGridReady, + // Sync helpers + syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue, + // Game logic + showOverlay, hideOverlay, + applyRevealedCell, applyStep, + makeGameEndIfItEnds, resignProcess, + // Current cells snapshot (for active-check in onClick) + cells, + } = useGame(); + + const eventSourceRef = useRef(null); + const rpcUsersRef = useRef(null); + const stepCacheRef = useRef([]); + + // ── Helpers ─────────────────────────────────────────────────────────────── + + const correctGridSize = () => { + let $f = $('#mine-wrapper .grid'); + $f.height($f.width()); + $f = $('#mine-wrapper .grid .field-wrapper'); + $f.height($f.width()); + $('#mine-wrapper .grid .field-wrapper .field').height($f.width()).css('line-height', ($f.width() - 2) + 'px'); + }; + + // ── REST mutations / queries ────────────────────────────────────────────── + + const connectQuery = useQuery({ + queryKey: ['game-connect', gameAssoc], + queryFn: () => fetch('/api/game/connect/' + gameAssoc) + .then(r => r.text()) + .then(b64 => JSON.parse(window.atob(b64))), + enabled: false, + retry: false, + }); + + const startMutation = useMutation({ + mutationFn: () => fetch('/api/game/start', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ gameAssoc }), + }), + }); + + const joinMutation = useMutation({ + mutationFn: () => fetch('/api/game/join/' + gameAssoc, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }).catch(e => isEnvDev && console.error('Join error', e)), + }); + + const stepMutation = useMutation({ + mutationFn: dataPack => fetch('/api/game/step/' + gameAssoc, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(dataPack), + }).then(r => r.json()), + }); + + // ── Game-start helpers (triggered by server events) ─────────────────────── + + const wInit = (revealedCells = []) => { + setGridReady(true); + showOverlay('We are waiting for your opponent...', gameAssoc ? ( +
+

Share this unique link w/ your opponent

+
+ +
+ Play w/ me! +
+ ) : ''); + + setTimeout(() => revealedCells.forEach(cell => applyRevealedCell(cell, cell.player)), 0); + }; + + const makeGameStart = payload => { + syncActivePlayer(1); + syncRed(p => ({ ...p, name: payload.users.red || payload.users.redAnon || p.name })); + syncBlue(p => ({ + ...p, + name: payload.users.blue || payload.users.blueAnon || p.name, + desc: 'blue' === webPlayerRef.current ? DESC.you : DESC.buddy, + active: true, + })); + hideOverlay(); + }; + + // ── Mercure / SSE message handlers ─────────────────────────────────────── + + const wSubscribe = (payload, rpcUsers = null) => { + isEnvDev && console.info((payload.user ?? 'user') + ' subscribed'); + const firstUser = !rpcUsers; + + if (null === webPlayerRef.current) { + const isBlue = payload.user === payload.users.blue + || (firstUser ? '' !== payload.users.blueAnon : '' === rpcUsers.blueAnon && '' === rpcUsers.blue); + syncWebPlayer(isBlue ? 'blue' : 'red'); + } + + 900 > $(document).width() && correctGridSize(); + + if ( + 2 === payload.userCnt + && (!connectionLostRef.current + || (connectionLostRef.current && false === activePlayerRef.current && !endRef.current)) + ) { + makeGameStart(payload); + } + }; + + const wUnsubscribe = payload => { + isEnvDev && console.info(payload.msg); + showOverlay('The connection has been lost w/ your friend...', 'Please, restart the game!'); + }; + + const wTopic = payload => { + if (webPlayerRef.current !== payload.data.player) { + if (null === payload.data.resign) { + isEnvDev && console.warn(payload.user + ' stepped to ' + payload.data.coords.join(',')); + syncBombSelected(payload.data.bomb); + applyStep(payload.data); + makeGameEndIfItEnds(payload.data.bluePoints, payload.data.redPoints, false, payload.data.leftMines); + } else { + resignProcess(payload.data.resign); + } + } + }; + + const handleMercureMessage = payload => { + if (undefined !== payload.data) { + wTopic(payload); + } else if (undefined === payload.msg) { + wSubscribe(payload, rpcUsersRef.current); + } else { + wUnsubscribe(payload); + } + + if (2 === payload.userCnt && connectionLostRef.current) { + isEnvDev && console.info('Reconnection'); + stepCacheRef.current.forEach(item => stepMutation.mutate(item)); + stepCacheRef.current = []; + syncConnLost(false); + } + }; + + const openEventSource = () => { + const wrapper = document.getElementById('mine-wrapper'); + const hubUrl = wrapper.dataset.mercureHubUrl; + const subscriberJwt = wrapper.dataset.mercureSubscriberJwt; + const url = new URL(hubUrl, window.location.origin); + + url.searchParams.append('topic', 'mineseeker/channel/' + gameAssoc); + if (subscriberJwt) url.searchParams.append('authorization', subscriberJwt); + + if (eventSourceRef.current) eventSourceRef.current.close(); + + const es = new EventSource(url.toString()); + es.onmessage = e => handleMercureMessage(JSON.parse(e.data)); + es.onopen = () => { + isEnvDev && console.info('SSE opened'); + if (connectionLostRef.current) { isEnvDev && console.info('SSE reconnected'); joinMutation.mutate(); } + }; + es.onerror = () => { isEnvDev && console.error('SSE error'); syncConnLost(true); }; + eventSourceRef.current = es; + }; + + // ── Initialization ──────────────────────────────────────────────────────── + + useEffect(() => { + (async () => { + if (connectionLostRef.current) { openEventSource(); return; } + try { + if (gameInherited) { + const serverData = await connectQuery.refetch().then(r => { + if (r.error) throw r.error; + return r.data; + }); + + if ('undefined' === typeof serverData.users || null === serverData.users) { + showOverlay('This channel does not exists!', Restart game!); + console.error('This channel does not exists!'); + return; + } + + rpcUsersRef.current = serverData.users; + openEventSource(); + wInit(serverData.revealedCells || []); + } else { + await startMutation.mutateAsync(); + openEventSource(); + wInit(); + } + + isEnvDev && console.info('Connection initialised — joining channel'); + await joinMutation.mutateAsync(); + } catch (e) { + isEnvDev && console.error('Connection error', e); + setTimeout(() => window.location.reload(), 500); + } + })(); + + window.addEventListener('pagehide', () => navigator.sendBeacon('/api/game/leave/' + gameAssoc)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // ── UI-facing callbacks ─────────────────────────────────────────────────── + + const onClick = async coords => { + const activeColor = activePlayerRef.current ? 'blue' : 'red'; + if (activeColor !== webPlayerRef.current) return; + + const [r, c] = coords; + if (cells[r]?.[c]?.active) return; + + const dataPack = { coords, player: activeColor, bomb: bombSelectedRef.current, resign: null }; + + if (connectionLostRef.current) { stepCacheRef.current.push(dataPack); return; } + + try { + const result = await stepMutation.mutateAsync(dataPack); + applyStep(result); + makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines); + } catch (e) { + isEnvDev && console.error('Step error', e); + } + }; + + const clickResign = () => { + const color = activePlayerRef.current ? 'blue' : 'red'; + stepMutation.mutate({ resign: color }); + resignProcess(webPlayerRef.current); + }; + + const resign = () => { + const activeColor = activePlayerRef.current ? 'blue' : 'red'; + if (webPlayerRef.current !== activeColor) return; + showOverlay('Are u sure u want to resign?!', ( +
+ Yes + No! +
+ )); + }; + + return { onClick, resign }; +}; + +export default useServerComm; diff --git a/assets/js/mine-seeker/user/user-control.js b/assets/js/mine-seeker/user/user-control.js deleted file mode 100644 index 17e16d9..0000000 --- a/assets/js/mine-seeker/user/user-control.js +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import User from './user'; - -class UserControl extends React.Component { - constructor() { - super(); - - /** - * activePlayer - red: 0, blue: 1 - * @type {{activePlayer: boolean, mines: number, bombSelected: boolean, foundMines: boolean}} - */ - this.state = { - activePlayer: false, - mines: 51, - bombSelected: false, - foundMines: false, - }; - } - - youCanSelectBomb(activePlayer, clickedPlayer) { - return this.refs[activePlayer].state.haveBomb - && this.refs[activePlayer].state.enabledBomb - && this.state.activePlayer === clickedPlayer; - } - - onClickBombSelector(clickedPlayer) { - let activePlayer = this.state.activePlayer ? 'blue' : 'red'; - - if (this.youCanSelectBomb(activePlayer, clickedPlayer)) { - this.state.bombSelected = !this.state.bombSelected; - - if (!this.state.bombSelected) { - this.props.bombClear(); - } - } - } - - getResignClass(webPlayer) { - let activePlayer = 1 === this.state.activePlayer ? 'blue' : 'red'; - return 'resign' + (webPlayer !== activePlayer ? ' disabled' : ''); - } - - activeMines() { - return 'active-mines' + (this.state.foundMines ? ' found-mine' : ''); - } - - render() { - return ( -
- -
- -
-
{this.state.mines}
-
-
- -
-
- - -
- ); - } -} - -export default UserControl; diff --git a/assets/js/mine-seeker/user/user.js b/assets/js/mine-seeker/user/user.js deleted file mode 100644 index ff93fa3..0000000 --- a/assets/js/mine-seeker/user/user.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; - -class User extends React.Component { - constructor(props) { - super(props); - - this.state = { - name: '...', - desc: '', - active: props.active, - color: 'blue' === props.color ? 1 : 0, - mines: 0, - srcRoot: '/images/', - haveBomb: true, - enabledBomb: true, - }; - } - - setColor(color) { - return 'user-container user-' + color; - } - - getSrc(color) { - return this.state.srcRoot + 'bg-flag-' + color + '-outbg.png'; - } - - getBombBuzzClass(webPlayer) { - let activePlayer = 1 === this.state.color ? 'blue' : 'red'; - - return 'bomb-container' - + ( - this.state.active && (activePlayer === webPlayer) && this.state.haveBomb && this.state.enabledBomb - ? ' buzz' - : '' - ); - } - - getBomb() { - let src = this.state.srcRoot; - - if (this.state.haveBomb) { - src += this.state.enabledBomb && this.state.active - ? 'bg-bomb-outbg.png' - : 'bg-bomb-disabled-outbg.png'; - } else { - src += 'bg-bomb-exploded-outbg.png'; - } - - return src; - } - - getFigure(color) { - return this.state.srcRoot + 'bg-figure-' + color + '-outbg.png'; - } - - getCursor(state, color) { - return state - ? cursor - : ''; - } - - render() { - return ( -
-
-
{this.props.color}
- {this.getCursor(this.props.active, this.props.color)} - figure -
-
{this.state.name}
-
-
{this.state.desc}
-
- flag -
- {this.state.mines} -
-
-
- bomb -
-
-
-
-
- ); - } -} - -export default User; diff --git a/eslint.config.mjs b/eslint.config.mjs index bbf4bdf..17325ce 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,6 +7,12 @@ export default [ { ignores: ['node_modules/**', 'vendor/**', 'var/**', 'public/build/**'], }, + { + files: ['**/*.jsx'], + rules: { + 'react/jsx-uses-vars': 'error', + }, + }, { files: ['**/*.{js,mjs,cjs,jsx,ts,tsx}'], plugins: { diff --git a/package-lock.json b/package-lock.json index 7b29fcb..aec8b64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,19 +10,17 @@ "license": "UNLICENSED", "dependencies": { "@fortawesome/fontawesome-free": "^5.2.0", - "autobahn": "^19.10.1", + "@tanstack/react-query": "^5.97.0", "bootstrap": "3", - "buffer": "^5.4.3", "howler": "^2.1.2", "jquery": "^3.4.1", - "js-base64": "^2.1.9", + "lodash": "^4.18.1", "prop-types": "^15.7.2", - "react": "^16.11.0", - "react-dom": "^16.11.0", - "uglify-js": "^2.7.4", - "uglifycss": "0.0.25" + "react": "^19.2.5", + "react-dom": "^19.2.5" }, "devDependencies": { + "@babel/core": "^7.29.0", "@babel/preset-env": "^7.23.0", "@babel/preset-react": "^7.6.3", "@eslint/eslintrc": "^3.3.5", @@ -33,7 +31,7 @@ "babel-loader": "^9.0", "core-js": "^3.0.0", "eslint": "^9.0.0", - "eslint-plugin-react": "^4.0.0", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^15.0.0", "sass": "^1.77.0", @@ -64,6 +62,37 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, "node_modules/@babel/generator": { "version": "7.29.1", "dev": true, @@ -313,6 +342,20 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.29.2", "dev": true, @@ -1735,6 +1778,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "dev": true, @@ -2397,6 +2451,32 @@ "node": ">=10" } }, + "node_modules/@tanstack/query-core": { + "version": "5.97.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.97.0.tgz", + "integrity": "sha512-QdpLP5VzVMgo4VtaPppRA2W04UFjIqX+bxke/ZJhE5cfd5UPkRzqIAJQt9uXkQJjqE8LBOMbKv7f8HCsZltXlg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.97.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.97.0.tgz", + "integrity": "sha512-y4So4eGcQoK2WVMAcDNZE9ofB/p5v1OlKvtc1F3uqHwrtifobT7q+ZnXk2mRkc8E84HKYSlAE9z6HXl2V0+ySQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.97.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "dev": true, @@ -3149,28 +3229,6 @@ "ajv": "^8.8.2" } }, - "node_modules/align-text": { - "version": "0.1.4", - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/align-text/node_modules/kind-of": { - "version": "3.2.2", - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "dev": true, @@ -3234,11 +3292,51 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "dev": true, "license": "MIT" }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "1.0.2", "dev": true, @@ -3258,6 +3356,104 @@ "node": ">=0.10.0" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assets-webpack-plugin": { "version": "7.0.0", "dev": true, @@ -3274,27 +3470,14 @@ "webpack": ">=5.0.0" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/autobahn": { - "version": "19.10.1", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, "license": "MIT", - "dependencies": { - "cbor": ">= 3.0.0", - "crypto-js": ">=3.1.8", - "msgpack5": ">= 3.6.0", - "tweetnacl": ">= 0.14.3", - "when": ">= 3.7.7", - "ws": ">= 1.1.4" - }, "engines": { - "node": ">= 4.2.6" - }, - "optionalDependencies": { - "bufferutil": ">= 1.2.1", - "utf-8-validate": ">= 1.2.1" + "node": ">= 0.4" } }, "node_modules/autoprefixer": { @@ -3332,6 +3515,22 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/babel-loader": { "version": "9.2.1", "dev": true, @@ -3389,10 +3588,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.3.0", - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { "version": "2.10.16", "dev": true, @@ -3417,13 +3612,6 @@ "node": "*" } }, - "node_modules/bignumber.js": { - "version": "9.0.0", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "dev": true, @@ -3435,14 +3623,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bl": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, "node_modules/body-parser": { "version": "1.20.4", "dev": true, @@ -3568,28 +3748,11 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.4.3", - "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, "node_modules/buffer-from": { "version": "1.1.1", "dev": true, "license": "MIT" }, - "node_modules/bufferutil": { - "version": "4.0.1", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "~3.7.0" - } - }, "node_modules/bytes": { "version": "3.0.0", "dev": true, @@ -3598,6 +3761,25 @@ "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "dev": true, @@ -3676,28 +3858,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/cbor": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0", - "nofilter": "^1.0.3" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/center-align": { - "version": "0.1.3", - "license": "MIT", - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -3766,15 +3926,6 @@ "webpack": ">=4.0.0 <6.0.0" } }, - "node_modules/cliui": { - "version": "2.1.0", - "license": "ISC", - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "dev": true, @@ -3935,6 +4086,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "dev": true, @@ -3968,6 +4126,7 @@ }, "node_modules/core-util-is": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -3983,10 +4142,6 @@ "node": ">= 8" } }, - "node_modules/crypto-js": { - "version": "3.1.8", - "license": "MIT" - }, "node_modules/css-declaration-sorter": { "version": "7.4.0", "dev": true, @@ -4238,6 +4393,60 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.4.3", "dev": true, @@ -4254,13 +4463,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4279,6 +4481,24 @@ "node": ">= 10" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "dev": true, @@ -4287,6 +4507,24 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/del": { "version": "4.1.1", "dev": true, @@ -4357,6 +4595,19 @@ "node": ">=6" } }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dom-converter": { "version": "0.2.0", "dev": true, @@ -4499,6 +4750,75 @@ "stackframe": "^1.3.4" } }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "dev": true, @@ -4515,6 +4835,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.0.0", "dev": true, @@ -4531,6 +4879,53 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.2.0", "dev": true, @@ -4616,11 +5011,37 @@ } }, "node_modules/eslint-plugin-react": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-4.3.0.tgz", - "integrity": "sha512-ajQ9S74FUln2GcwgpPUQqRLcT6UFDhvAMIiDX4F68tDnuihNXcAA7LI19MmRGGOuJnpMVDXugJg+wf9K+bf6kg==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } }, "node_modules/eslint-plugin-react-hooks": { "version": "5.2.0", @@ -4635,6 +5056,40 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -5292,6 +5747,22 @@ "dev": true, "license": "MIT" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/forwarded": { "version": "0.2.0", "dev": true, @@ -5353,6 +5824,57 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "dev": true, @@ -5407,6 +5929,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.1.5", "dev": true, @@ -5452,6 +5992,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "6.1.0", "dev": true, @@ -5501,6 +6058,19 @@ "dev": true, "license": "MIT" }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "dev": true, @@ -5509,6 +6079,35 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "dev": true, @@ -5520,6 +6119,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "dev": true, @@ -5706,10 +6321,6 @@ "postcss": "^8.1.0" } }, - "node_modules/ieee754": { - "version": "1.1.11", - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5855,8 +6466,24 @@ }, "node_modules/inherits": { "version": "2.0.3", + "dev": true, "license": "ISC" }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "3.1.1", "dev": true, @@ -5873,6 +6500,60 @@ "node": ">= 10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "dev": true, @@ -5884,9 +6565,35 @@ "node": ">=8" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "license": "MIT" + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-core-module": { "version": "2.16.1", @@ -5902,6 +6609,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "2.2.1", "dev": true, @@ -5924,6 +6666,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, @@ -5932,6 +6690,26 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "dev": true, @@ -5943,6 +6721,32 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -5951,6 +6755,23 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-cwd": { "version": "2.2.0", "dev": true, @@ -6003,6 +6824,54 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "dev": true, @@ -6014,6 +6883,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "dev": true, @@ -6027,6 +6993,7 @@ }, "node_modules/isarray": { "version": "1.0.0", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -6042,6 +7009,24 @@ "node": ">=0.10.0" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jest-util": { "version": "29.7.0", "dev": true, @@ -6101,10 +7086,6 @@ "version": "3.4.1", "license": "MIT" }, - "node_modules/js-base64": { - "version": "2.4.5", - "license": "BSD-3-Clause" - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -6168,6 +7149,22 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6195,13 +7192,6 @@ "shell-quote": "^1.8.3" } }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -6254,7 +7244,6 @@ }, "node_modules/lodash": { "version": "4.18.1", - "dev": true, "license": "MIT" }, "node_modules/lodash.debounce": { @@ -6279,13 +7268,6 @@ "dev": true, "license": "MIT" }, - "node_modules/longest": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", @@ -6460,16 +7442,6 @@ "dev": true, "license": "MIT" }, - "node_modules/msgpack5": { - "version": "4.2.1", - "license": "MIT", - "dependencies": { - "bl": "^2.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.6", - "safe-buffer": "^5.1.2" - } - }, "node_modules/multicast-dns": { "version": "7.2.5", "dev": true, @@ -6525,6 +7497,25 @@ "license": "MIT", "optional": true }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/node-forge": { "version": "1.4.0", "dev": true, @@ -6533,16 +7524,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-gyp-build": { - "version": "3.7.0", - "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-notifier": { "version": "9.0.1", "dev": true, @@ -6572,13 +7553,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nofilter": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "dev": true, @@ -6635,6 +7609,91 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/obuf": { "version": "1.1.2", "dev": true, @@ -6726,6 +7785,24 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-map": { "version": "2.1.0", "dev": true, @@ -6928,6 +8005,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.9", "dev": true, @@ -7488,15 +8575,18 @@ }, "node_modules/process-nextick-args": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/prop-types": { - "version": "15.7.2", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/proxy-addr": { @@ -7582,47 +8672,32 @@ } }, "node_modules/react": { - "version": "16.11.0", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "16.11.0", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.17.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^16.0.0" + "react": "^19.2.5" } }, "node_modules/react-is": { - "version": "16.11.0", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/readable-stream": { - "version": "2.3.6", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, "node_modules/readdirp": { "version": "4.1.2", "dev": true, @@ -7646,6 +8721,29 @@ "node": ">= 10.13.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "dev": true, @@ -7667,6 +8765,27 @@ "dev": true, "license": "MIT" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "6.4.0", "dev": true, @@ -7730,13 +8849,6 @@ "node": ">=8" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "dev": true, @@ -7824,20 +8936,80 @@ "node": ">= 4" } }, - "node_modules/right-align": { - "version": "0.1.3", + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, "license": "MIT", "dependencies": { - "align-text": "^0.1.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.1.2", + "dev": true, "license": "MIT" }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "dev": true, @@ -7907,12 +9079,10 @@ } }, "node_modules/scheduler": { - "version": "0.17.0", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/schema-utils": { "version": "4.3.3", @@ -8099,6 +9269,55 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "dev": true, @@ -8312,8 +9531,23 @@ "node": ">= 0.8" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string_decoder": { "version": "1.1.1", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -8351,6 +9585,104 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "dev": true, @@ -8713,10 +10045,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/tweetnacl": { - "version": "1.0.1", - "license": "Unlicense" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8761,40 +10089,101 @@ "node": ">= 0.6" } }, - "node_modules/uglify-js": { - "version": "2.8.29", - "license": "BSD-2-Clause", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", "dependencies": { - "source-map": "~0.5.1", - "yargs": "~3.10.0" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, - "optionalDependencies": { - "uglify-to-browserify": "~1.0.0" - } - }, - "node_modules/uglify-js/node_modules/source-map": { - "version": "0.5.7", - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uglify-to-browserify": { - "version": "1.0.2", + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/uglifycss": { - "version": "0.0.25", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, "license": "MIT", - "bin": { - "uglifycss": "uglifycss" + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -8880,17 +10269,9 @@ "punycode": "^2.1.0" } }, - "node_modules/utf-8-validate": { - "version": "5.0.2", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "~3.7.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "license": "MIT" }, "node_modules/utila": { @@ -9270,10 +10651,6 @@ "node": ">=0.8.0" } }, - "node_modules/when": { - "version": "3.7.8", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "dev": true, @@ -9288,17 +10665,107 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wildcard": { "version": "2.0.1", "dev": true, "license": "MIT" }, - "node_modules/window-size": { - "version": "0.1.0", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -9309,40 +10776,16 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "0.0.2", - "license": "MIT/X11", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "dev": true, "license": "ISC" }, - "node_modules/ws": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "async-limiter": "^1.0.0" - } - }, "node_modules/yallist": { "version": "3.0.2", "dev": true, "license": "ISC" }, - "node_modules/yargs": { - "version": "3.10.0", - "license": "MIT", - "dependencies": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, "node_modules/yargs-parser": { "version": "21.1.1", "dev": true, @@ -9351,13 +10794,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/camelcase": { - "version": "1.2.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/yocto-queue": { "version": "1.2.2", "dev": true, diff --git a/package.json b/package.json index 81a2ffa..70b0cc4 100644 --- a/package.json +++ b/package.json @@ -14,17 +14,14 @@ "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^5.2.0", + "@tanstack/react-query": "^5.97.0", "bootstrap": "3", - "buffer": "^5.4.3", "howler": "^2.1.2", "jquery": "^3.4.1", - "js-base64": "^2.1.9", "lodash": "^4.18.1", "prop-types": "^15.7.2", - "react": "^16.11.0", - "react-dom": "^16.11.0", - "uglify-js": "^2.7.4", - "uglifycss": "0.0.25" + "react": "^19.2.5", + "react-dom": "^19.2.5" }, "devDependencies": { "@babel/core": "^7.29.0", @@ -38,7 +35,7 @@ "babel-loader": "^9.0", "core-js": "^3.0.0", "eslint": "^9.0.0", - "eslint-plugin-react": "^4.0.0", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^15.0.0", "sass": "^1.77.0", diff --git a/src/Util/TopicManager.php b/src/Util/TopicManager.php index 3ba96b9..5263ece 100644 --- a/src/Util/TopicManager.php +++ b/src/Util/TopicManager.php @@ -352,9 +352,10 @@ class TopicManager implements TopicManagerInterface private function getLeftMines(array $grid, array $alreadyRevealed): array { $mines = []; + foreach ($grid as $r => $row) { foreach ($row as $c => $value) { - if ('m' === $value && !isset($alreadyRevealed[$r . ',' . $c])) { + if ('m' === $value && !isset($alreadyRevealed["$r,$c"])) { $mines[] = ['row' => $r, 'col' => $c]; } } diff --git a/symfony.lock b/symfony.lock index da7b284..2edad4d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -373,7 +373,7 @@ }, "files": [ "assets/css/app.css", - "assets/js/app.js", + "assets/js/MineSeeker.js", "config/packages/assets.yaml", "config/packages/prod/webpack_encore.yaml", "config/packages/webpack_encore.yaml", diff --git a/webpack.config.js b/webpack.config.js index 55e4288..ac6dbb5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,59 +4,59 @@ var webpack = require('webpack'); // Manually configure the runtime environment if not already configured yet by the "encore" command. // It's useful when you use tools that rely on webpack.config.js file. if (!Encore.isRuntimeEnvironmentConfigured()) { - Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); + Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); } // the project directory where compiled assets will be stored Encore - .setOutputPath('public/build/') - // the public path used by the web server to access the previous directory - .setPublicPath('/build') + .setOutputPath('public/build/') +// the public path used by the web server to access the previous directory + .setPublicPath('/build') - // .cleanupOutputBeforeBuild() - .enableBuildNotifications() - .enableSourceMaps(!Encore.isProduction()) - // uncomment to create hashed filenames (e.g. app.abc123.css) - .enableVersioning(Encore.isProduction()) +// .cleanupOutputBeforeBuild() + .enableBuildNotifications() + .enableSourceMaps(!Encore.isProduction()) +// uncomment to create hashed filenames (e.g. app.abc123.css) + .enableVersioning(Encore.isProduction()) - // .copyFiles([ - // {from: './public/bundles/goswebsocket/', to: '/[path][name].[ext]', pattern: /\.(js|css|map)$/, includeSubdirectories: false}, - // ]) +// .copyFiles([ +// {from: './public/bundles/goswebsocket/', to: '/[path][name].[ext]', pattern: /\.(js|css|map)$/, includeSubdirectories: false}, +// ]) - // uncomment to define the assets of the project - // .addEntry('js/app', './assets/js/app.js') - // .addStyleEntry('css/app', './assets/css/app.scss') +// uncomment to define the assets of the project +// .addEntry('js/app', './assets/js/MineSeeker.js') +// .addStyleEntry('css/app', './assets/css/app.scss') - // .addEntry('mineseeker', ['babel-polyfill', './assets/js/mine-seeker.js']) - .addEntry('mineseeker', './assets/js/mine-seeker.js') - .addEntry('mineseekerStyle', './assets/css/style.mineseeker.scss') - .addEntry('homeStyle', './assets/css/style.layout.scss') +// .addEntry('mineseeker', ['babel-polyfill', './assets/js/MineSeeker.js']) + .addEntry('mineseeker', './assets/js/app.jsx') + .addEntry('mineseekerStyle', './assets/css/style.mineseeker.scss') + .addEntry('homeStyle', './assets/css/style.layout.scss') - // uncomment if you use Sass/SCSS files - .enableSassLoader(options => { - options.api = 'modern'; - options.sassOptions = { silenceDeprecations: ['import'] }; - }) - .configureCssLoader(options => { - // don't process absolute URLs (e.g. /images/...) — served by the web server - options.url = { filter: url => !url.startsWith('/') }; - }) +// uncomment if you use Sass/SCSS files + .enableSassLoader(options => { + options.api = 'modern'; + options.sassOptions = { silenceDeprecations: ['import'] }; + }) + .configureCssLoader(options => { + // don't process absolute URLs (e.g. /images/...) — served by the web server + options.url = { filter: url => !url.startsWith('/') }; + }) - // provide $/jQuery as global variables for Bootstrap 3 and legacy code - .addPlugin(new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - 'window.jQuery': 'jquery', - })) +// provide $/jQuery as global variables for Bootstrap 3 and legacy code + .addPlugin(new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + 'window.jQuery': 'jquery', + })) - // .configureBabel(function (babelConfig) { - // babelConfig.presets.push('env'); - // }) + .enableReactPreset() + .configureBabel(babelConfig => { + const idx = babelConfig.presets.findIndex(p => (Array.isArray(p) ? p[0] : p).includes('preset-react')); + if (-1 < idx) babelConfig.presets[idx] = [require.resolve('@babel/preset-react'), { runtime: 'automatic' }]; + }) - .enableReactPreset() - - // .enableSingleRuntimeChunk() - .disableSingleRuntimeChunk() +// .enableSingleRuntimeChunk() + .disableSingleRuntimeChunk() ; module.exports = Encore.getWebpackConfig();