mirror of
https://github.com/tiennm99/loto.git
synced 2026-08-13 16:23:02 +00:00
refactor: purge remaining TypeScript residue
After the JS+JSDoc conversion, some TS-flavored bits lingered. Removed:
- // @ts-check directives (TS-specific pragma)
- JSDoc annotations referencing TS-defined types: import('next').NextConfig,
React.MutableRefObject, React.Dispatch, React.SetStateAction
- jsconfig.json (TS-server-flavored config; only kept it for the @/* alias)
@/* imports replaced with relative paths so jsconfig is no longer needed.
Remaining JSDoc is plain @param / @returns — vanilla JS, no TS dependency.
Build, lint, dev profiles unchanged.
This commit is contained in:
@@ -6,13 +6,11 @@ const geistSans = Geist({
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
/** @type {import('next').Metadata} */
|
||||
export const metadata = {
|
||||
title: "Lô tô",
|
||||
description: "Bàn số của trò chơi Lô tô",
|
||||
};
|
||||
|
||||
/** @param {{ children: React.ReactNode }} props */
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="vi" className={`${geistSans.variable} h-full antialiased`}>
|
||||
|
||||
+6
-17
@@ -2,25 +2,19 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import PlayerBoard from "@/components/player-board";
|
||||
import PlayerBoard from "../../components/player-board";
|
||||
|
||||
const STORAGE_KEY = "loto_master";
|
||||
|
||||
/**
|
||||
* @typedef {Object} MasterState
|
||||
* @property {number[]} called numbers drawn so far, in order
|
||||
* @property {number[]} remaining numbers left to draw, pre-shuffled
|
||||
*/
|
||||
|
||||
/**
|
||||
* Master draw state shape:
|
||||
* { called: number[], remaining: number[] }
|
||||
*
|
||||
* Build the 9x10 board: columns 0-8 map to number ranges 1-9, 10-19, ..., 80-90.
|
||||
* @returns {number[][]}
|
||||
*/
|
||||
function buildBoard() {
|
||||
/** @type {number[][]} */
|
||||
const board = [];
|
||||
for (let row = 0; row < 10; row++) {
|
||||
/** @type {number[]} */
|
||||
const cells = [];
|
||||
for (let col = 0; col < 9; col++) {
|
||||
const num = col === 0 ? row + 1 : col * 10 + row;
|
||||
@@ -40,7 +34,6 @@ function buildBoard() {
|
||||
return board;
|
||||
}
|
||||
|
||||
/** @returns {MasterState} */
|
||||
function createFreshState() {
|
||||
const all = Array.from({ length: 90 }, (_, i) => i + 1);
|
||||
// Shuffle
|
||||
@@ -51,12 +44,10 @@ function createFreshState() {
|
||||
return { called: [], remaining: all };
|
||||
}
|
||||
|
||||
/** @param {MasterState} state */
|
||||
function saveState(state) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
/** @returns {MasterState | null} */
|
||||
function loadState() {
|
||||
const data = localStorage.getItem(STORAGE_KEY);
|
||||
if (!data) return null;
|
||||
@@ -71,10 +62,8 @@ const BOARD = Object.freeze(buildBoard().map((row) => Object.freeze(row)));
|
||||
const BOARD_FLAT = Object.freeze(BOARD.flatMap((r) => r));
|
||||
|
||||
export default function MasterPage() {
|
||||
/** @type {[MasterState | null, (s: MasterState | null) => void]} */
|
||||
const [state, setState] = useState(/** @type {MasterState | null} */ (null));
|
||||
/** @type {[number | null, (n: number | null) => void]} */
|
||||
const [lastCalled, setLastCalled] = useState(/** @type {number | null} */ (null));
|
||||
const [state, setState] = useState(null);
|
||||
const [lastCalled, setLastCalled] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = loadState();
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import PlayerBoard from "@/components/player-board";
|
||||
import PlayerBoard from "../components/player-board";
|
||||
|
||||
export default function Home() {
|
||||
const [showInstructions, setShowInstructions] = useState(false);
|
||||
|
||||
+16
-30
@@ -9,29 +9,23 @@ import {
|
||||
loadGrid,
|
||||
saveCrossedState,
|
||||
saveGrid,
|
||||
} from "@/lib/game-logic";
|
||||
} from "../lib/game-logic";
|
||||
|
||||
/**
|
||||
* @typedef {Object} PlayerBoardProps
|
||||
* @property {string} [storagePrefix] localStorage key prefix; allows multiple
|
||||
* independent boards (e.g. user vs master)
|
||||
* Reusable player card component.
|
||||
*
|
||||
* @param {object} props
|
||||
* @param {string} [props.storagePrefix] localStorage key prefix; allows
|
||||
* multiple independent boards (e.g. user vs master).
|
||||
*/
|
||||
|
||||
/** @param {PlayerBoardProps} props */
|
||||
export default function PlayerBoard({ storagePrefix = "loto" } = {}) {
|
||||
/** @type {[number[][] | null, (g: number[][] | null) => void]} */
|
||||
const [grid, setGrid] = useState(/** @type {number[][] | null} */ (null));
|
||||
/** @type {[boolean[][], React.Dispatch<React.SetStateAction<boolean[][]>>]} */
|
||||
const [crossed, setCrossed] = useState(/** @type {boolean[][]} */ ([]));
|
||||
const [grid, setGrid] = useState(null);
|
||||
const [crossed, setCrossed] = useState([]);
|
||||
const [showCongrats, setShowCongrats] = useState(false);
|
||||
const [congratsRow, setCongratsRow] = useState(-1);
|
||||
/** @type {[string | null, (s: string | null) => void]} */
|
||||
const [toast, setToast] = useState(/** @type {string | null} */ (null));
|
||||
/** @type {React.MutableRefObject<ReturnType<typeof setTimeout> | null>} */
|
||||
const [toast, setToast] = useState(null);
|
||||
const toastTimer = useRef(null);
|
||||
/** @type {React.MutableRefObject<Set<number>>} */
|
||||
const celebratedRows = useRef(new Set());
|
||||
/** @type {React.MutableRefObject<Set<number>>} */
|
||||
const notifiedWaitingRows = useRef(new Set());
|
||||
|
||||
const dismissToast = useCallback(() => {
|
||||
@@ -43,7 +37,6 @@ export default function PlayerBoard({ storagePrefix = "loto" } = {}) {
|
||||
}, []);
|
||||
|
||||
const showToast = useCallback(
|
||||
/** @param {string} msg */
|
||||
(msg) => {
|
||||
dismissToast();
|
||||
setToast(msg);
|
||||
@@ -124,20 +117,13 @@ export default function PlayerBoard({ storagePrefix = "loto" } = {}) {
|
||||
dismissToast();
|
||||
}, [grid, dismissToast, storagePrefix]);
|
||||
|
||||
const handleCellClick = useCallback(
|
||||
/**
|
||||
* @param {number} row
|
||||
* @param {number} col
|
||||
*/
|
||||
(row, col) => {
|
||||
setCrossed((prev) => {
|
||||
const next = prev.map((r) => [...r]);
|
||||
next[row][col] = !next[row][col];
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
const handleCellClick = useCallback((row, col) => {
|
||||
setCrossed((prev) => {
|
||||
const next = prev.map((r) => [...r]);
|
||||
next[row][col] = !next[row][col];
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -125,8 +125,8 @@ function randomANumberInRow(weights) {
|
||||
```js
|
||||
import { useCallback, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import PlayerBoard from "@/components/player-board";
|
||||
import { generateGrid } from "@/lib/game-logic";
|
||||
import PlayerBoard from "../components/player-board";
|
||||
import { generateGrid } from "../lib/game-logic";
|
||||
```
|
||||
|
||||
## Testing (Not Currently Implemented)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"noEmit": true,
|
||||
"paths": { "@/*": ["./*"] }
|
||||
},
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.mjs"],
|
||||
"exclude": ["node_modules", ".next", "out"]
|
||||
}
|
||||
+6
-8
@@ -1,5 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Lô tô card generation, persistence, and row-state helpers.
|
||||
* @module lib/game-logic
|
||||
@@ -101,22 +99,22 @@ export function generateGrid() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* Parse JSON and validate its shape with a runtime guard. Returns null on
|
||||
* either parse failure or validation failure.
|
||||
* @param {string | null} raw
|
||||
* @param {(v: unknown) => boolean} validate runtime guard; cast result to T on success
|
||||
* @returns {T | null}
|
||||
* @param {(v: any) => boolean} validate
|
||||
*/
|
||||
function safeParse(raw, validate) {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return validate(parsed) ? /** @type {T} */ (parsed) : null;
|
||||
return validate(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {unknown} v @returns {boolean} */
|
||||
/** @param {any} v */
|
||||
function isNumberMatrix(v) {
|
||||
return (
|
||||
Array.isArray(v) &&
|
||||
@@ -130,7 +128,7 @@ function isNumberMatrix(v) {
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {unknown} v @returns {boolean} */
|
||||
/** @param {any} v */
|
||||
function isBoolMatrix(v) {
|
||||
return (
|
||||
Array.isArray(v) &&
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-check
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const isCodeserver = process.env.NEXT_DEV_PROFILE === "codeserver";
|
||||
|
||||
@@ -24,7 +22,6 @@ const cs = isCodeserver ? codeserverConfig() : null;
|
||||
const basePath =
|
||||
process.env.NEXT_BASE_PATH ?? cs?.basePath ?? (isProd ? "/loto" : "");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
basePath,
|
||||
|
||||
Reference in New Issue
Block a user