diff --git a/backend/internal/game/chess.go b/backend/internal/game/chess.go
index 11978f2..ed5d279 100644
--- a/backend/internal/game/chess.go
+++ b/backend/internal/game/chess.go
@@ -1,7 +1,8 @@
package game
import (
- "fmt"
+ "errors"
+ "log/slog"
"time"
"github.com/corentings/chess/v2"
@@ -71,17 +72,24 @@ func (c *chessGame) Move(sender string, mv *GameMove) error {
return err
}
- moveStr := rowCol2Move(mv.From) + rowCol2Move(mv.To)
- if mv.Change != "" {
- moveStr += mv.Change
+ from := chess.Square((7-mv.From.Row)*8 + mv.From.Col)
+ to := chess.Square((7-mv.To.Row)*8 + mv.To.Col)
+ promo := change2Piece(mv.Change)
+
+ var move *chess.Move
+ for _, m := range c.game.ValidMoves() {
+ if m.S1() == from && m.S2() == to && m.Promo() == promo {
+ move = &m
+ break
+ }
}
- notation := chess.UCINotation{}
- move, err := notation.Decode(c.game.Position(), moveStr)
- if err != nil {
- return fmt.Errorf("invalid move format %q: %w", moveStr, err)
+ if move == nil {
+ return errors.New("invalid move: not legal in this position")
}
- if c.game.Move(move, nil) != nil {
- return fmt.Errorf("invalid move %q", moveStr)
+
+ if err := c.game.Move(move, nil); err != nil {
+ slog.Error("Internal error", "error", err)
+ return errors.New("internal error")
}
if c.game.Outcome() != chess.NoOutcome {
@@ -89,9 +97,13 @@ func (c *chessGame) Move(sender string, mv *GameMove) error {
c.endedAt = time.Now()
switch c.game.Outcome() {
case chess.WhiteWon:
+ c.status = StatusFin
c.winner = c.players[0]
case chess.BlackWon:
+ c.status = StatusFin
c.winner = c.players[1]
+ case chess.Draw:
+ c.status = StatusFin
}
}
c.turn = 1 - c.turn
@@ -102,8 +114,18 @@ func (c *chessGame) Move(sender string, mv *GameMove) error {
return nil
}
-func rowCol2Move(pos Position) string {
- return fmt.Sprintf("%c%d", 'a'+pos.Col, 8-pos.Row)
+func change2Piece(change string) chess.PieceType {
+ switch change {
+ case "q", "Q":
+ return chess.Queen
+ case "r", "R":
+ return chess.Rook
+ case "b", "B":
+ return chess.Bishop
+ case "n", "N":
+ return chess.Knight
+ }
+ return chess.NoPieceType
}
func pieceToCode(piece chess.Piece) int {
diff --git a/backend/internal/game/game.go b/backend/internal/game/game.go
index a44c98e..81bced1 100644
--- a/backend/internal/game/game.go
+++ b/backend/internal/game/game.go
@@ -47,7 +47,7 @@ type Game interface {
getBoardLocked() any
getValidMovesLocked() []GameMove
updateLoop()
- handleDisconnects()
+ handleDisconnectLocked()
}
type GameUpdate struct {
@@ -158,20 +158,7 @@ func (b *baseGame) Leave(player string, intentional bool) {
b.players = slices.Delete(b.players, idx, idx+1)
}
- if len(b.players) == 0 {
- b.status = StatusFin
- b.endedAt = time.Now()
- b.notify(GameUpdate{
- State: b.stateLocked(),
- Action: DeleteAction,
- })
- b.Stop()
- return
- }
- b.notify(GameUpdate{
- State: b.stateLocked(),
- Action: UpdateAction,
- })
+ b.handleDisconnectLocked()
}
func (b *baseGame) checkTurnLocked(sender string) (int, error) {
@@ -260,11 +247,11 @@ func (b *baseGame) updateLoop() {
b.players = slices.Delete(b.players, idx, idx+1)
}
}
- b.self.handleDisconnects()
+ b.self.handleDisconnectLocked()
}
}
-func (b *baseGame) handleDisconnects() {
+func (b *baseGame) handleDisconnectLocked() {
// just stop game if player doesn't come back.
b.status = StatusFin
b.endedAt = time.Now()
diff --git a/backend/internal/live/client.go b/backend/internal/live/client.go
index 31879fc..a1b5b3c 100644
--- a/backend/internal/live/client.go
+++ b/backend/internal/live/client.go
@@ -50,14 +50,15 @@ func (c *client) start() {
func (c *client) stop() {
c.cancel()
- c.hub.unregister <- c
close(c.send)
close(c.recv)
c.conn.Close(websocket.StatusNormalClosure, "client leaving")
}
func (c *client) readPump() {
- defer c.stop()
+ defer func() {
+ c.hub.unregister <- c
+ }()
for {
msgType, msgRaw, err := c.conn.Read(c.ctx)
@@ -99,11 +100,18 @@ func (c *client) writePump() {
if !ok {
return
}
+
writeCtx, cancelWrite := context.WithTimeout(c.ctx, c.cfg.WriteTimeout)
err := c.conn.Write(writeCtx, websocket.MessageText, message)
cancelWrite()
+
if err != nil {
- slog.Error("writePump: WebSocket write error", "error", err, "client", c.ID)
+ if websocket.CloseStatus(err) == websocket.StatusNormalClosure ||
+ websocket.CloseStatus(err) == websocket.StatusGoingAway {
+ slog.Debug("WebSocket connection closed", "client", c.ID)
+ } else {
+ slog.Error("writePump: WebSocket write error", "error", err, "client", c.ID)
+ }
c.cancel()
return
}
@@ -179,6 +187,12 @@ func (c *client) processPump() {
}
func (c *client) trySend(msg []byte) {
+ defer func() {
+ if r := recover(); r != nil {
+ slog.Warn("trySend: Attempted to send on closed channel", "client", c.ID, "recover", r)
+ }
+ }()
+
select {
case c.send <- msg:
default:
diff --git a/backend/internal/live/hub.go b/backend/internal/live/hub.go
index eb65210..e2ba83c 100644
--- a/backend/internal/live/hub.go
+++ b/backend/internal/live/hub.go
@@ -4,6 +4,7 @@ import (
"letsgo/internal/config"
"letsgo/internal/game"
"log/slog"
+ "time"
)
type hub struct {
@@ -52,6 +53,7 @@ func (h *hub) run() {
delete(h.clients, client)
slog.Debug("Unregistered: ", "client", client.ID)
}
+ time.AfterFunc(100*time.Millisecond, client.stop)
case pair := <-h.joinRoom:
slog.Debug("join room")
diff --git a/frontend/src/app/live/boardgame/page.tsx b/frontend/src/app/live/boardgame/page.tsx
index e16ece6..77e1527 100644
--- a/frontend/src/app/live/boardgame/page.tsx
+++ b/frontend/src/app/live/boardgame/page.tsx
@@ -7,8 +7,8 @@ import { ChessBoard } from '@/components/games/chess/ChessBoard';
import { GameStatus } from '@/components/games/GameStatus';
import { CreateGame } from '@/components/games/CreateGame';
import { GameBoardProps } from '@/types/gameTypes';
-import useBoardGame from '@/hooks/useBoardGame';
-import { useWebSocket } from '@/hooks/webSocket';
+import useBoardGame from '@/hooks/useWSGame';
+import { useWebSocket } from '@/hooks/useWebsocket';
import { GAME_DISPLAY_NAMES } from '@/config/consts';
import { Trophy } from 'lucide-react';
@@ -36,7 +36,7 @@ export default function BoardGamePage() {
{(gameState.status === 'finished') &&
- {gameState.winner ?? 'Nobody'} Wins!
+ {gameState.winner == '' ? 'Nobody' : gameState.winner} Wins!
}
@@ -56,7 +56,7 @@ export default function BoardGamePage() {
leaveGame={leaveGame}
/>
-
+
{gameState.gameName !== ''
? GameBoard ?
diff --git a/frontend/src/app/live/draw/page.tsx b/frontend/src/app/live/draw/page.tsx
index 15a2fd1..1fc8079 100644
--- a/frontend/src/app/live/draw/page.tsx
+++ b/frontend/src/app/live/draw/page.tsx
@@ -1,7 +1,7 @@
'use client';
import { useDraw } from '@/hooks/useDraw';
-import { useWebSocket } from '@/hooks/webSocket';
+import { useWebSocket } from '@/hooks/useWebsocket';
import { DrawToolbar } from '@/components/draw/DrawToolbar';
import { useState, useRef, useEffect } from 'react';
import { DRAW_CANVAS_WIDTH, DRAW_CANVAS_HEIGHT } from '@/config/consts';
@@ -65,7 +65,6 @@ export default function DrawPage() {
return () => window.removeEventListener('resize', updateZoom);
}, [containerRef]);
-
const getCanvasCoords = (clientX: number, clientY: number) => {
if (!containerRef.current) return { x: 0, y: 0 };
const rect = containerRef.current.getBoundingClientRect();
@@ -75,6 +74,24 @@ export default function DrawPage() {
};
};
+ const handleZoom = (zoom: number) => {
+ if (!containerRef.current) return;
+ const rect = containerRef.current.getBoundingClientRect();
+ const centerX = rect.width / 2;
+ const centerY = rect.height / 2;
+
+ const x = ((centerX / containerZoom) - viewState.offset.x) / viewState.zoom;
+ const y = ((centerY / containerZoom) - viewState.offset.y) / viewState.zoom;
+
+ setViewState({
+ zoom: zoom,
+ offset: {
+ x: (centerX / containerZoom) - x * zoom,
+ y: (centerY / containerZoom) - y * zoom,
+ },
+ });
+ }
+
const startPan = (clientX: number, clientY: number) => {
setIsPanning(true);
panStartRef.current = {
@@ -88,7 +105,7 @@ export default function DrawPage() {
const handlePan = (clientX: number, clientY: number) => {
if (!isPanning || !panStartRef.current) return;
- const scale = 1 / (containerZoom * viewState.zoom);
+ const scale = 1 / (containerZoom);
const dx = (clientX - panStartRef.current.x) * scale;
const dy = (clientY - panStartRef.current.y) * scale;
@@ -197,14 +214,14 @@ export default function DrawPage() {
currentZoom={viewState.zoom}
changeColor={color => setDrawState(s => ({ ...s, color }))}
changeWidth={lineWidth => setDrawState(s => ({ ...s, lineWidth }))}
- zoomIn={() => setViewState(s => ({ ...s, zoom: Math.min(s.zoom * 1.1, 3) }))}
- zoomOut={() => setViewState(s => ({ ...s, zoom: Math.max(s.zoom * 0.9, 1) }))}
+ zoomIn={() => handleZoom(Math.min(viewState.zoom * 1.1, 3))}
+ zoomOut={() => handleZoom(Math.max(viewState.zoom * 0.9, 1))}
zoomReset={() => setViewState({ zoom: 1, offset: { x: 0, y: 0 } })}
/>
;
}
diff --git a/frontend/src/components/UI/Dropdown.tsx b/frontend/src/components/UI/Dropdown.tsx
index edae9a4..451617d 100644
--- a/frontend/src/components/UI/Dropdown.tsx
+++ b/frontend/src/components/UI/Dropdown.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useState, useRef, useEffect, useCallback } from 'react';
+import { useState, useRef, useEffect, useCallback } from 'react';
import Link from 'next/link';
import Button from './Button';
diff --git a/frontend/src/components/UI/NavBar.tsx b/frontend/src/components/UI/NavBar.tsx
index 3a6c64d..6bd0a28 100644
--- a/frontend/src/components/UI/NavBar.tsx
+++ b/frontend/src/components/UI/NavBar.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import Link from 'next/link';
import Dropdown from './Dropdown';
import ThemeSwitcher from './ThemeSwitcher';
diff --git a/frontend/src/components/UI/ThemeSwitcher.tsx b/frontend/src/components/UI/ThemeSwitcher.tsx
index 5eacc4f..215831b 100644
--- a/frontend/src/components/UI/ThemeSwitcher.tsx
+++ b/frontend/src/components/UI/ThemeSwitcher.tsx
@@ -1,7 +1,6 @@
'use client';
-import React from 'react';
-import { useThemeStore, Theme } from '@/hooks/themeStore';
+import { useThemeStore, Theme } from '@/hooks/useTheme';
export default function ThemeSwitcher() {
const { theme, setTheme } = useThemeStore();
diff --git a/frontend/src/components/games/Connect4Board.tsx b/frontend/src/components/games/Connect4Board.tsx
index 7faa38c..9296a24 100644
--- a/frontend/src/components/games/Connect4Board.tsx
+++ b/frontend/src/components/games/Connect4Board.tsx
@@ -1,40 +1,38 @@
-import { useState } from 'react';
import { GameBoardProps } from '@/types/gameTypes';
-import { useUserStore } from '@/hooks/userStore';
+import { useUserStore } from '@/hooks/useUserStore';
import { cn } from '@/lib/utils';
+import { useGameBoard } from '@/hooks/useGameBoard';
export function Connect4Board({ gameState, makeMove }: GameBoardProps) {
- const [hoveredCol, setHoveredCol] = useState
(-1);
const username = useUserStore(state => state.username);
const yourIdx = gameState.players.indexOf(username);
const isYourTurn = gameState.status == 'in_progress'
&& gameState.turn === yourIdx;
- const handleClick = () => {
- if (!isYourTurn || hoveredCol < 0) return;
- makeMove({ to: { row: 0, col: hoveredCol } });
+ const handleCellClick = (col: number) => {
+ console.log(col);
+ if (!isYourTurn) return;
+ makeMove({ to: { row: 0, col } });
};
- return setHoveredCol(-1)}
+ const { getCellProps, hoveredCell } = useGameBoard({
+ onCellClick: handleCellClick,
+ });
+
+ return
{[...Array(7)].map((_, col) => (
setHoveredCol(col)}
- onPointerLeave={() => setHoveredCol(-1)}
+ {...getCellProps(col)}
>
- {hoveredCol === col && (
+ {hoveredCell === col && (
)}
diff --git a/frontend/src/components/games/CreateGame.tsx b/frontend/src/components/games/CreateGame.tsx
index 90f3d73..a8cdce5 100644
--- a/frontend/src/components/games/CreateGame.tsx
+++ b/frontend/src/components/games/CreateGame.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import { useState } from 'react';
import { GameName, GAME_NAMES } from '@/types/wsTypes';
import { GAME_DISPLAY_NAMES } from '@/config/consts';
diff --git a/frontend/src/components/games/GameStatus.tsx b/frontend/src/components/games/GameStatus.tsx
index 5ed0196..c2aea62 100644
--- a/frontend/src/components/games/GameStatus.tsx
+++ b/frontend/src/components/games/GameStatus.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useUserStore } from '@/hooks/userStore';
+import { useUserStore } from '@/hooks/useUserStore';
import { BoardGameState } from '@/types/wsTypes';
import Button from '@/components/UI/Button';
diff --git a/frontend/src/components/games/TicTacToeBoard.tsx b/frontend/src/components/games/TicTacToeBoard.tsx
index 14592be..48e53a0 100644
--- a/frontend/src/components/games/TicTacToeBoard.tsx
+++ b/frontend/src/components/games/TicTacToeBoard.tsx
@@ -1,26 +1,32 @@
'use client';
-import { useCallback, useState } from 'react';
+import { useCallback } from 'react';
import { GameBoardProps } from '@/types/gameTypes';
-import { useUserStore } from '@/hooks/userStore';
+import { useUserStore } from '@/hooks/useUserStore';
import { cn } from '@/lib/utils';
import { X, Circle } from 'lucide-react';
+import { useGameBoard } from '@/hooks/useGameBoard';
export function TicTacToeBoard({ gameState, makeMove }: GameBoardProps) {
- const [hoveredCell, setHoveredCell] = useState<{ row: number, col: number } | null>(null);
const username = useUserStore(state => state.username);
const yourIdx = gameState.players.indexOf(username);
const isYourTurn = gameState.status === 'in_progress' && gameState.turn === yourIdx;
- const handleCellClick = useCallback((row: number, col: number) => {
+ const handleCellClick = useCallback((cell: number) => {
+ const row = Math.floor(cell / 3);
+ const col = cell % 3;
if (gameState.status === 'in_progress' && gameState.board[row][col] === 0 && isYourTurn)
makeMove({ to: { row, col } });
}, [gameState.status, gameState.board, makeMove, isYourTurn]);
+ const { getCellProps, hoveredCell } = useGameBoard({
+ onCellClick: handleCellClick,
+ });
+
return
{gameState.board.map((cellRow, row) =>
cellRow.map((cell, col) => {
- const isHovered = hoveredCell?.row === row && hoveredCell?.col === col;
+ const isHovered = row * 3 + col === hoveredCell;
return
0 && yourIdx !== -1 && cell !== yourIdx + 1,
}
)}
- onClick={() => handleCellClick(row, col)}
- onMouseEnter={() => setHoveredCell({ row, col })}
- onMouseLeave={() => setHoveredCell(null)}
+ {...getCellProps(row * 3 + col)}
>
{isHovered &&
}
- {cell === 1 ?
- : cell === 2 ?
+ {cell === 1 ?
+ : cell === 2 ?
: null}
;
})
diff --git a/frontend/src/components/games/chess/ChessBoard.tsx b/frontend/src/components/games/chess/ChessBoard.tsx
index 27c48a1..a350c49 100644
--- a/frontend/src/components/games/chess/ChessBoard.tsx
+++ b/frontend/src/components/games/chess/ChessBoard.tsx
@@ -1,71 +1,40 @@
'use client';
-import { useState, useRef, useEffect, useMemo } from 'react';
+import { useMemo } from 'react';
import { GameBoardProps } from '@/types/gameTypes';
-import { useUserStore } from '@/hooks/userStore';
+import { useUserStore } from '@/hooks/useUserStore';
import { cn } from '@/lib/utils';
import { numToPiece } from './pieceMapping';
-
-type Position = { row: number; col: number };
-
-interface DragState {
- piece: number;
- from: Position;
- pos: { x: number; y: number };
-}
+import { useGameBoard } from '@/hooks/useGameBoard';
export function ChessBoard({ gameState, makeMove }: GameBoardProps) {
const username = useUserStore(state => state.username);
- const hover = useRef
(null);
- const [drag, setDrag] = useState(null);
-
const idx = gameState.players.indexOf(username);
const yourTurn = gameState.status === 'in_progress' && gameState.turn === idx;
- const handlePointerDown = (e: React.PointerEvent, piece: number, row: number, col: number) => {
- if (e.pointerType === 'mouse' && e.button !== 0) return;
- e.preventDefault();
- setDrag({
- piece: piece,
- from: { row, col },
- pos: { x: e.clientX, y: e.clientY },
- });
- };
-
- const validSquares = useMemo(() => gameState.validMoves.filter(vMove =>
- vMove.from?.row === drag?.from.row && vMove.from?.col === drag?.from.col)
- , [gameState.validMoves, drag?.from.row, drag?.from.col]);
+ const { getCellProps, hoveredCell, dragging } = useGameBoard({
+ onCellDrop: (from, to) => {
+ if (!yourTurn) return;
+ const fromRow = Math.floor(from / 8);
+ const fromCol = from % 8;
+ const toRow = Math.floor(to / 8);
+ const toCol = to % 8;
- useEffect(() => {
- if (!drag) return;
- const handlePointerMove = (e: PointerEvent) => {
- setDrag(prev => ({
- ...prev!,
- pos: { x: e.clientX, y: e.clientY },
- }));
- };
- const handlePointerUp = () => {
- const hoverSquare = hover.current;
- if (hoverSquare && yourTurn && validSquares.some(vMove =>
- vMove.to.row === hoverSquare.row && vMove.to.col === hoverSquare.col)) {//todo: remove valid check, make sure backend enforces it
- makeMove({
- from: drag.from,
- to: hoverSquare,
- });
- }
- setDrag(null);
- };
+ if (gameState.validMoves.some(mv =>
+ mv.from?.row === fromRow && mv.from?.col === fromCol &&
+ mv.to.row === toRow && mv.to.col === toCol))
+ makeMove({ from: { row: fromRow, col: fromCol }, to: { row: toRow, col: toCol } });
+ }
+ });
- document.addEventListener('pointermove', handlePointerMove);
- document.addEventListener('pointerup', handlePointerUp, { once: true });
- return () => {
- document.removeEventListener('pointermove', handlePointerMove);
- document.removeEventListener('pointerup', handlePointerUp);
- };
- }, [drag, makeMove, validSquares, yourTurn]);
+ const validSquares = useMemo(() => gameState.validMoves.filter(vMove => {
+ if (dragging.from === null) return false;
+ const row = Math.floor(dragging.from / 8);
+ const col = dragging.from % 8;
+ return vMove.from?.row === row && vMove.from?.col === col;
+ }), [gameState.validMoves, dragging.from]);
- return
-
attention: work in progress
+ return
{gameState.board.map((cellRow, row) =>
cellRow.map((cell, col) => {
const isLight = (row + col) % 2 === 0;
@@ -74,22 +43,33 @@ export function ChessBoard({ gameState, makeMove }: GameBoardProps) {
return
move.to.row === row && move.to.col === col)
? isLight ? 'bg-primary/33' : 'bg-primary/66'
: isLight ? 'bg-secondary/10' : 'bg-secondary/50',
+ hoveredCell === row * 8 + col && 'bg-accent/30',
+ dragging.from === row * 8 + col && 'bg-accent/60'
)}
- onPointerEnter={() => hover.current = { row, col }}
- onPointerLeave={() => hover.current = null}
- onPointerDown={(e) => handlePointerDown(e, cell, row, col)}
+ {...getCellProps(row * 8 + col)}
>
{Piece
- && !(drag && drag.from.row === row && drag.from.col === col)
- &&
}
+ && !(dragging.from === row * 8 + col)
+ &&
}
})
)}
+ {dragging.from !== null && (() => {
+ const row = Math.floor(dragging.from / 8);
+ const col = dragging.from % 8;
+ const pieceNum = gameState.board[row][col];
+ const Piece = numToPiece[pieceNum];
+ if (!Piece) return null;
+ return
;
+ })()}
;
}
diff --git a/frontend/src/config/consts.ts b/frontend/src/config/consts.ts
index dbe0ca0..4b0013e 100644
--- a/frontend/src/config/consts.ts
+++ b/frontend/src/config/consts.ts
@@ -1,6 +1,6 @@
import { GameName } from '@/types/wsTypes';
-export const RECONNECT_INITIAL_DELAY = 5000;
+export const RECONNECT_INITIAL_DELAY = 100;
export const RECONNECT_MAX_DELAY = 10000;
export const RECONNECT_MAX_ATTEMPTS = 10;
diff --git a/frontend/src/hooks/useDraw.ts b/frontend/src/hooks/useDraw.ts
index dd59b96..15443a2 100644
--- a/frontend/src/hooks/useDraw.ts
+++ b/frontend/src/hooks/useDraw.ts
@@ -1,7 +1,7 @@
'use client';
import { useRef, useEffect, useState } from 'react';
-import { useWebSocket } from './webSocket';
+import { useWebSocket } from './useWebsocket';
import type { DrawPayload } from '@/types/wsTypes';
import { msgRawSignal } from '@/types/wsTypes';
import { DRAW_START_COLOR, DRAW_STROKE_INTERVAL, DRAW_START_WIDTH } from '@/config/consts';
diff --git a/frontend/src/hooks/useGameBoard.ts b/frontend/src/hooks/useGameBoard.ts
new file mode 100644
index 0000000..e660254
--- /dev/null
+++ b/frontend/src/hooks/useGameBoard.ts
@@ -0,0 +1,113 @@
+import { useState, useRef, useEffect } from 'react';
+
+type useBoardProps = {
+ onCellClick?: (cellIndex: number) => void;
+ onCellDrop?: (fromIndex: number, toIndex: number) => void;
+ touchOffset?: { x: number; y: number };
+};
+
+const noDrag = { from: null, pos: { x: 0, y: 0 } };
+
+export function useGameBoard({ onCellClick, onCellDrop, touchOffset = { x: 0, y: 10 } }: useBoardProps) {
+ const [hoveredCell, setHoveredCell] = useState
(null);
+ const [dragging, setDrag] = useState<{ from: number | null; pos: { x: number; y: number } }>(noDrag);
+ const sameCell = useRef(true); // for cursor cell click
+
+ const lastInputType = useRef(null);
+ const setInputType = (type: React.PointerEvent['pointerType']) => {
+ if (lastInputType.current && lastInputType.current !== type) setDrag({ ...noDrag });
+ lastInputType.current = type;
+ };
+
+ /* causing problems with mouse drag. Prolly need to be used with manual hit test */
+ // const captured = useRef<{ ele: Element | null; ptr: number | null }>
+ // ({ ele: null, ptr: null });
+ // const capture = (e: React.PointerEvent) => {
+ // e.currentTarget.setPointerCapture(e.pointerId);
+ // captured.current = { ele: e.currentTarget, ptr: e.pointerId };
+ // }
+ // const release = () => {
+ // if (captured.current.ele && captured.current.ptr !== null) {
+ // try {
+ // captured.current.ele.releasePointerCapture(captured.current.ptr);
+ // } finally {
+ // captured.current = { ele: null, ptr: null };
+ // }
+ // }
+ // }
+
+
+ const getCellProps = (cellIndex: number) => ({
+ onPointerDown: (e: React.PointerEvent) => {
+ e.stopPropagation()
+ setInputType(e.pointerType);
+ // capture(e);
+ if (e.pointerType === 'touch') {
+ if (onCellClick) onCellClick(cellIndex);
+
+ if (dragging.from === null) {
+ const rect = e.currentTarget.getBoundingClientRect();
+ const x = rect.left + rect.width / 2 + touchOffset.x;
+ const y = rect.top + rect.height / 2 - touchOffset.y;
+ setDrag({ from: cellIndex, pos: { x, y } })
+ } else {
+ setDrag({ ...noDrag })
+ if (onCellDrop) onCellDrop(dragging.from, cellIndex);
+ }
+ } else {
+ setDrag({ from: cellIndex, pos: { x: e.clientX, y: e.clientY } });
+ sameCell.current = true;
+ }
+ },
+ onPointerUp: (e: React.PointerEvent) => {
+ e.stopPropagation()
+ // release();
+ setInputType(e.pointerType);
+ if (e.pointerType === 'touch') return;
+
+ if (onCellClick && sameCell.current)
+ onCellClick(cellIndex);
+ if (onCellDrop && dragging.from !== null && hoveredCell !== null)
+ onCellDrop(dragging.from, hoveredCell);
+ setDrag({ ...noDrag });
+ },
+
+ onPointerEnter: () => { setHoveredCell(cellIndex) },
+ onPointerLeave: () => {
+ setHoveredCell(null);
+ sameCell.current = false;
+ },
+ });
+
+ //
+ useEffect(() => {
+ if (dragging.from === null) return
+ const onPointerMove = (e: PointerEvent) => {
+ if (e.pointerType === 'touch') return;
+ setDrag(d => ({ ...d, pos: { x: e.clientX, y: e.clientY } }))
+ }
+
+ const reset = () => {
+ // release();
+ setDrag({ ...noDrag })
+ }
+
+ //cancel drag if outside. Up needed for mouse and down for touch
+ window.addEventListener('pointermove', onPointerMove);
+ window.addEventListener('pointerup', reset);
+ window.addEventListener('pointerdown', reset);
+ window.addEventListener('pointercancel', reset);
+ return () => {
+ window.removeEventListener('pointermove', onPointerMove);
+ window.removeEventListener('pointerup', reset);
+ window.removeEventListener('pointerdown', reset);
+ window.removeEventListener('pointercancel', reset);
+ };
+ }, [dragging.from]);
+
+ return {
+ hoveredCell,
+ dragging,
+ getCellProps,
+ };
+}
\ No newline at end of file
diff --git a/frontend/src/hooks/useGroupCall.ts b/frontend/src/hooks/useGroupCall.ts
index c1e4202..404ac77 100644
--- a/frontend/src/hooks/useGroupCall.ts
+++ b/frontend/src/hooks/useGroupCall.ts
@@ -1,8 +1,8 @@
'use client';
import { useState, useReducer, useCallback, useEffect } from 'react';
-import { useWebSocket } from '@/hooks/webSocket';
-import { useUserStore } from '@/hooks/userStore';
+import { useWebSocket } from '@/hooks/useWebsocket';
+import { useUserStore } from '@/hooks/useUserStore';
import { VidSignalMsg } from '@/types/wsTypes';
interface Peer {
diff --git a/frontend/src/hooks/themeStore.ts b/frontend/src/hooks/useTheme.ts
similarity index 96%
rename from frontend/src/hooks/themeStore.ts
rename to frontend/src/hooks/useTheme.ts
index b8d80ec..e811b93 100644
--- a/frontend/src/hooks/themeStore.ts
+++ b/frontend/src/hooks/useTheme.ts
@@ -72,7 +72,7 @@ export const useThemeStore = create()(
)
);
-// Optional: Listen for system preference changes and update if theme is 'system'
+//for handling system pref change
if (typeof window !== 'undefined') {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const currentTheme = useThemeStore.getState().theme;
diff --git a/frontend/src/hooks/userStore.ts b/frontend/src/hooks/useUserStore.ts
similarity index 100%
rename from frontend/src/hooks/userStore.ts
rename to frontend/src/hooks/useUserStore.ts
diff --git a/frontend/src/hooks/useBoardGame.ts b/frontend/src/hooks/useWSGame.ts
similarity index 95%
rename from frontend/src/hooks/useBoardGame.ts
rename to frontend/src/hooks/useWSGame.ts
index 335e334..285a12f 100644
--- a/frontend/src/hooks/useBoardGame.ts
+++ b/frontend/src/hooks/useWSGame.ts
@@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react';
-import { useWebSocket } from './webSocket';
+import { useWebSocket } from './useWebsocket';
import { BoardGameState, GameMove, GameName } from '@/types/wsTypes';
-export default function useBoardGame() {
+export default function useWebSocketGame() {
const { sendGameMsg, setGameHandler } = useWebSocket();
const [isLoading, setIsLoading] = useState(false);
diff --git a/frontend/src/hooks/webSocket.ts b/frontend/src/hooks/useWebsocket.ts
similarity index 97%
rename from frontend/src/hooks/webSocket.ts
rename to frontend/src/hooks/useWebsocket.ts
index 8d2c2a7..db0bc6a 100644
--- a/frontend/src/hooks/webSocket.ts
+++ b/frontend/src/hooks/useWebsocket.ts
@@ -1,7 +1,7 @@
'use client';
import { useEffect } from 'react';
-import { useUserStore } from './userStore';
+import { useUserStore } from './useUserStore';
import * as t from '@/types/wsTypes';
import { create } from 'zustand';
import { RECONNECT_INITIAL_DELAY, WS_URL } from '@/config/consts';
@@ -117,11 +117,6 @@ export const useWebSocket = create()((set, get) => ({
}
}
- ws.onerror = (event) => {
- console.error('WS error:', event);
- set({ error: 'WS error' });
- };
-
ws.onclose = (event) => {
console.debug('WS closed:', event.code, event.reason);
set({ error: `WS disconnected: code ${event.code}, reason: ${event.reason || 'Unknown'}.` });