diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 51d2eef1..fe253779 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -22,6 +22,7 @@ "history": "^5.3.0", "lucide-react": "^0.372.0", "react": "^18.2.0", + "react-chessboard": "^4.5.0", "react-confetti": "^6.1.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", diff --git a/apps/frontend/src/components/ChessBoard.tsx b/apps/frontend/src/components/ChessBoard.tsx index c45c4d6d..f80a01c4 100644 --- a/apps/frontend/src/components/ChessBoard.tsx +++ b/apps/frontend/src/components/ChessBoard.tsx @@ -16,7 +16,6 @@ import { useRecoilState } from 'recoil'; import { isBoardFlippedAtom, movesAtom, - userSelectedMoveIndexAtom, } from '@repo/store/chessBoard'; export function isPromoting(chess: Chess, from: Square, to: Square) { @@ -77,9 +76,6 @@ export const ChessBoard = memo(({ const { height, width } = useWindowSize(); const [isFlipped, setIsFlipped] = useRecoilState(isBoardFlippedAtom); - const [userSelectedMoveIndex, setUserSelectedMoveIndex] = useRecoilState( - userSelectedMoveIndexAtom, - ); const [moves, setMoves] = useRecoilState(movesAtom); const [lastMove, setLastMove] = useState<{ from: string; to: string } | null>( null, @@ -182,32 +178,6 @@ export const ChessBoard = memo(({ } }, [moves]); - useEffect(() => { - if (userSelectedMoveIndex !== null) { - const move = moves[userSelectedMoveIndex]; - setLastMove({ - from: move.from, - to: move.to, - }); - chess.load(move.after); - setBoard(chess.board()); - return; - } - }, [userSelectedMoveIndex]); - - useEffect(() => { - if (userSelectedMoveIndex !== null) { - chess.reset(); - moves.forEach((move) => { - chess.move({ from: move.from, to: move.to }); - }); - setBoard(chess.board()); - setUserSelectedMoveIndex(null); - } else { - setBoard(chess.board()); - } - }, [moves]); - return ( <> {gameOver && } @@ -248,15 +218,6 @@ export const ChessBoard = memo(({ if (!started) { return; } - if (userSelectedMoveIndex !== null) { - chess.reset(); - moves.forEach((move) => { - chess.move({ from: move.from, to: move.to }); - }); - setBoard(chess.board()); - setUserSelectedMoveIndex(null); - return; - } if (!from && square?.color !== chess.turn()) return; if (!isMyTurn) return; if (from != squareRepresentation) { diff --git a/apps/frontend/src/components/ChessBoardLatest.tsx b/apps/frontend/src/components/ChessBoardLatest.tsx new file mode 100644 index 00000000..78813b34 --- /dev/null +++ b/apps/frontend/src/components/ChessBoardLatest.tsx @@ -0,0 +1,68 @@ +import { BoardOrientation } from '@repo/store/chessBoard'; +import { Chess, Color } from 'chess.js'; +import { useEffect } from 'react'; +import { Chessboard } from 'react-chessboard'; +import { useRecoilState } from 'recoil'; +import { boardOrientationAtom } from '../../../../packages/store/src/atoms/chessBoard'; +import { useLiveGameBoard } from '@/hooks/useLiveGameBoard'; + +const ChessBoardLatest = ({ + gameId, + started, + myColor, + chess, + socket, +}: { + myColor: Color; + gameId: string; + started: boolean; + chess: Chess; + socket: WebSocket; +}) => { + const { + boardState, + handlePieceDrop, + liveGamePosition, + handleSquareClick, + handlePromotionPieceSelect, + handleSquareRightClick, + } = useLiveGameBoard({ + gameId, + started, + myColor, + chess, + socket, + }); + const [boardOrientation, setBoardOrientation] = + useRecoilState(boardOrientationAtom); + + useEffect(() => { + setBoardOrientation( + myColor === 'w' ? BoardOrientation.WHITE : BoardOrientation.BLACK, + ); + }, [myColor]); + + return ( + piece[0] === myColor} + customSquareStyles={{ + ...boardState.optionSquares, + ...boardState.rightClickedSquares, + }} + customBoardStyle={{ + borderRadius: '4px', + }} + promotionToSquare={boardState.moveTo} + showPromotionDialog={boardState.showPromotionDialog} + boardOrientation={boardOrientation} + /> + ); +}; + +export default ChessBoardLatest; diff --git a/apps/frontend/src/components/GameEndModal.tsx b/apps/frontend/src/components/GameEndModal.tsx index 428caade..c2653546 100644 --- a/apps/frontend/src/components/GameEndModal.tsx +++ b/apps/frontend/src/components/GameEndModal.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import WhiteKing from '../../public/wk.png'; -import BlackKing from '../../public/bk.png'; +import WhiteKing from '/wk.png'; +import BlackKing from '/bk.png'; import { GameResult, Result } from '@/screens/Game'; interface ModalProps { diff --git a/apps/frontend/src/components/MovesTable.tsx b/apps/frontend/src/components/MovesTable.tsx index a657a127..e6e2e51f 100644 --- a/apps/frontend/src/components/MovesTable.tsx +++ b/apps/frontend/src/components/MovesTable.tsx @@ -1,11 +1,12 @@ import { - isBoardFlippedAtom, - movesAtom, - userSelectedMoveIndexAtom, + BoardOrientation, + boardOrientationAtom, + liveGamePositionAtom, + selectedMoveIndexAtom, } from '@repo/store/chessBoard'; -import { Move } from 'chess.js'; +import { Chess, Move } from 'chess.js'; import { useEffect, useRef } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilState, useSetRecoilState } from 'recoil'; import { HandshakeIcon, FlagIcon, @@ -16,13 +17,14 @@ import { RefreshCw, } from 'lucide-react'; -const MovesTable = () => { - const [userSelectedMoveIndex, setUserSelectedMoveIndex] = useRecoilState( - userSelectedMoveIndexAtom, - ); - const setIsFlipped = useSetRecoilState(isBoardFlippedAtom); - const moves = useRecoilValue(movesAtom); +const MovesTable = ({ chess }: { chess: Chess }) => { + const setLiveGamePosition = useSetRecoilState(liveGamePositionAtom); + const setBoardOrientation = useSetRecoilState(boardOrientationAtom); const movesTableRef = useRef(null); + const moves = chess.history({ verbose: true }); + + const [selectedMoveIndex, setSelectedMoveIndex] = useRecoilState(selectedMoveIndexAtom) + const movesArray = moves.reduce((result, _, index: number, array: Move[]) => { if (index % 2 === 0) { result.push(array.slice(index, index + 2)); @@ -31,6 +33,9 @@ const MovesTable = () => { }, [] as Move[][]); useEffect(() => { + console.log("rerender"); + + setSelectedMoveIndex(null); if (movesTableRef && movesTableRef.current) { movesTableRef.current.scrollTo({ top: movesTableRef.current.scrollHeight, @@ -38,6 +43,32 @@ const MovesTable = () => { }); } }, [moves]); + + const handleMoveSelection = (moveIndex: number | null) => { + const move = + moveIndex !== null ? moves[moveIndex] : moves[moves.length - 1]; + setLiveGamePosition(move.after); + setSelectedMoveIndex(moveIndex); + }; + + const handleGoToMove = (moveIndex: number | null) => { + if (moveIndex === null) { + handleMoveSelection(null); + } else { + const newIndex = Math.max(0, Math.min(moves.length - 1, moveIndex)); + handleMoveSelection(newIndex); + } + }; + + const goToFirstMove = () => handleGoToMove(0); + const goToPreviousMove = () => + handleGoToMove( + selectedMoveIndex === null ? moves.length - 2 : selectedMoveIndex - 1, + ); + const goToNextMove = () => + handleGoToMove(selectedMoveIndex === null ? null : selectedMoveIndex + 1); + const goToLastMove = () => handleGoToMove(null); + return (
{ {`${index + 1}.`} {movePairs.map((move, movePairIndex) => { - const isLastIndex = - movePairIndex === movePairs.length - 1 && - movesArray.length - 1 === index; + const moveIndex = index * 2 + movePairIndex; + const isLastIndex = moveIndex === moves.length - 1; const isHighlighted = - userSelectedMoveIndex !== null - ? userSelectedMoveIndex === index * 2 + movePairIndex + selectedMoveIndex !== null + ? moveIndex === selectedMoveIndex : isLastIndex; const { san } = move; @@ -68,7 +98,8 @@ const MovesTable = () => { key={movePairIndex} className={`col-span-2 cursor-pointer flex items-center w-full pl-1 ${isHighlighted ? 'bg-[#484644] rounded border-b-[#5A5858] border-b-[3px]' : ''}`} onClick={() => { - setUserSelectedMoveIndex(index * 2 + movePairIndex); + setLiveGamePosition(move.after); + setSelectedMoveIndex(isLastIndex ? null : moveIndex); }} > {san} @@ -94,10 +125,8 @@ const MovesTable = () => {
)}
-
-
-
-
-
-
- {started && ( -
- - {getTimer( - user.id === gameMetadata?.whitePlayer?.id - ? player2TimeConsumed - : player1TimeConsumed, - )} -
- )} -
-
-
- -
-
- {started && ( -
+
+
+
+ {started && ( +
{getTimer( - user.id === gameMetadata?.blackPlayer?.id + user.id === gameMetadata?.whitePlayer?.id ? player2TimeConsumed : player1TimeConsumed, )}
+ )} -
-
+ + {started &&
+ + {getTimer( + user.id === gameMetadata?.blackPlayer?.id + ? player2TimeConsumed + : player1TimeConsumed, + )} +
}
-
- {!started && ( -
- {added ? ( -
Waiting
- ) : ( - gameId === 'random' && ( - - ) - )} -
- )} -
- + +
+ + + +
+ {!started && ( +
+ {added ? ( +
Waiting
+ ) : ( + gameId === 'random' && ( + + ) + )}
+ )} +
+
diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index 8aaf2aaf..3d2cf6fa 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -15,7 +15,7 @@ "jsx": "react-jsx", "baseUrl": ".", "paths": { - "@/*": ["./*", "./public/*"] + "@/*": ["./*", "./public/*", "./src/*"] }, /* Linting */ diff --git a/packages/store/src/atoms/chessBoard.ts b/packages/store/src/atoms/chessBoard.ts index 4a634052..c021c633 100644 --- a/packages/store/src/atoms/chessBoard.ts +++ b/packages/store/src/atoms/chessBoard.ts @@ -1,5 +1,9 @@ -import { Move } from 'chess.js'; +import { Move, Chess } from 'chess.js'; import { atom } from "recoil"; +export enum BoardOrientation { + BLACK = 'black', + WHITE = 'white', + } export const isBoardFlippedAtom = atom({ key: "isBoardFlippedAtom", @@ -11,7 +15,18 @@ export const movesAtom = atom({ default: [] }) -export const userSelectedMoveIndexAtom = atom({ - key: 'userSelectedMoveIndex', +export const selectedMoveIndexAtom = atom({ + key: 'selectedMoveIndex', default: null -}); \ No newline at end of file +}); + +export const liveGamePositionAtom = atom({ + key:"liveGamePositionAtom", + default: new Chess().fen() +}) + +export const boardOrientationAtom = atom({ + key: "boardOrientationAtom", + default: BoardOrientation.WHITE +}) + diff --git a/yarn.lock b/yarn.lock index 8ffc8388..77ba6a93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1197,7 +1197,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.24.1", "@babel/runtime@^7.7.6": +"@babel/runtime@^7.24.1", "@babel/runtime@^7.7.6", "@babel/runtime@^7.9.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== @@ -2276,6 +2276,21 @@ dependencies: "@babel/runtime" "^7.13.10" +"@react-dnd/asap@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488" + integrity sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A== + +"@react-dnd/invariant@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-4.0.2.tgz#b92edffca10a26466643349fac7cdfb8799769df" + integrity sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw== + +"@react-dnd/shallowequal@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" + integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== + "@react-native-community/cli-clean@12.3.6": version "12.3.6" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz#e8a7910bebc97266fd5068649013a03958021fc4" @@ -5020,6 +5035,15 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== +dnd-core@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-16.0.1.tgz#a1c213ed08961f6bd1959a28bb76f1a868360d19" + integrity sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng== + dependencies: + "@react-dnd/asap" "^5.0.1" + "@react-dnd/invariant" "^4.0.1" + redux "^4.2.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -6524,7 +6548,7 @@ history@^5.3.0: dependencies: "@babel/runtime" "^7.7.6" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9101,6 +9125,15 @@ rc@^1.0.1, rc@^1.1.6, rc@~1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-chessboard@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/react-chessboard/-/react-chessboard-4.5.0.tgz#01c1952f59b1b18c7cd21fe74e7c971f38f03384" + integrity sha512-YS91zFMZlaW05vIFOAkM2KnR5wp/sqBCzglUe1GJrXOtVkxzDT7Tr6cetm2txMGmSwdFQgurIi2/wKV1YQs34w== + dependencies: + react-dnd "^16.0.1" + react-dnd-html5-backend "^16.0.1" + react-dnd-touch-backend "^16.0.1" + react-confetti@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-confetti/-/react-confetti-6.1.0.tgz#03dc4340d955acd10b174dbf301f374a06e29ce6" @@ -9116,6 +9149,32 @@ react-devtools-core@^4.27.7: shell-quote "^1.6.1" ws "^7" +react-dnd-html5-backend@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" + integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw== + dependencies: + dnd-core "^16.0.1" + +react-dnd-touch-backend@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz#e73f8169e2b9fac0f687970f875cac0a4d02d6e2" + integrity sha512-NonoCABzzjyWGZuDxSG77dbgMZ2Wad7eQiCd/ECtsR2/NBLTjGksPUx9UPezZ1nQ/L7iD130Tz3RUshL/ClKLA== + dependencies: + "@react-dnd/invariant" "^4.0.1" + dnd-core "^16.0.1" + +react-dnd@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" + integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== + dependencies: + "@react-dnd/invariant" "^4.0.1" + "@react-dnd/shallowequal" "^4.0.1" + dnd-core "^16.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -9386,6 +9445,13 @@ recoil@^0.7.7: dependencies: hamt_plus "1.0.2" +redux@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" @@ -10140,7 +10206,16 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10218,7 +10293,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10232,6 +10307,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -11227,7 +11309,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11245,6 +11327,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"