From 8ca29666a6a933e90ca4fc8cc1875a07ef508ee7 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Wed, 3 Jul 2024 23:13:18 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=203=D1=85=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BE=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 5 + .idea/codeStyles/Project.xml | 57 ++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 ++ .idea/modules.xml | 8 ++ .idea/react-memo.iml | 12 +++ .idea/vcs.xml | 6 ++ .../AttemptsCounter/AttemptsCounter.jsx | 9 ++ .../AttemptsCounter.module.css | 15 +++ src/components/Cards/Cards.jsx | 49 +++++++-- src/context/Context.jsx | 25 +++++ src/hooks/useCustomContext.js | 6 ++ src/index.js | 5 +- src/pages/SelectLevelPage/SelectLevelPage.jsx | 7 ++ .../SelectLevelPage.module.css | 100 ++++++++++-------- 15 files changed, 261 insertions(+), 54 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/react-memo.iml create mode 100644 .idea/vcs.xml create mode 100644 src/components/AttemptsCounter/AttemptsCounter.jsx create mode 100644 src/components/AttemptsCounter/AttemptsCounter.module.css create mode 100644 src/context/Context.jsx create mode 100644 src/hooks/useCustomContext.js diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..3e22f4b71 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..03d9549ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..b870cb1a5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/react-memo.iml b/.idea/react-memo.iml new file mode 100644 index 000000000..24643cc37 --- /dev/null +++ b/.idea/react-memo.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/AttemptsCounter/AttemptsCounter.jsx b/src/components/AttemptsCounter/AttemptsCounter.jsx new file mode 100644 index 000000000..56bd82b82 --- /dev/null +++ b/src/components/AttemptsCounter/AttemptsCounter.jsx @@ -0,0 +1,9 @@ +import styles from "./AttemptsCounter.module.css"; + +export function AttemptsCounter({ value }) { + return ( +
+ Осталось {value} попытки +
+ ); +} diff --git a/src/components/AttemptsCounter/AttemptsCounter.module.css b/src/components/AttemptsCounter/AttemptsCounter.module.css new file mode 100644 index 000000000..4cc96e92e --- /dev/null +++ b/src/components/AttemptsCounter/AttemptsCounter.module.css @@ -0,0 +1,15 @@ +.attemptsBlock { + width: 672px; + margin: 0 auto; + padding: 26px; + padding-top: 22px; + box-sizing: border-box; +} + +.attemptsText { + color: #fff; + font-family: StratosSkyeng, serif; + font-size: 48px; + font-style: normal; + font-weight: 400; +} \ No newline at end of file diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 7526a56c8..353222811 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -5,6 +5,8 @@ import styles from "./Cards.module.css"; import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; +import { AttemptsCounter } from "../AttemptsCounter/AttemptsCounter"; +import { useCustomContext } from "../../hooks/useCustomContext"; // Игра закончилась const STATUS_LOST = "STATUS_LOST"; @@ -26,9 +28,9 @@ function getTimerValue(startDate, endDate) { endDate = new Date(); } - const diffInSecconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); - const minutes = Math.floor(diffInSecconds / 60); - const seconds = diffInSecconds % 60; + const diffInSeconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); + const minutes = Math.floor(diffInSeconds / 60); + const seconds = diffInSeconds % 60; return { minutes, seconds, @@ -41,8 +43,10 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { + const { attempts, startLife, handleAttemptsChange } = useCustomContext(); // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта const [cards, setCards] = useState([]); + // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); @@ -50,6 +54,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { const [gameStartDate, setGameStartDate] = useState(null); // Дата конца игры const [gameEndDate, setGameEndDate] = useState(null); + const [playerLost, setPlayerLost] = useState(false); // Стейт для таймера, высчитывается в setInteval на основе gameStartDate и gameEndDate const [timer, setTimer] = useState({ @@ -57,10 +62,19 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { minutes: 0, }); + useEffect(() => { + if (attempts === 0) { + setPlayerLost(attempts); + finishGame(STATUS_LOST); + handleAttemptsChange(startLife); + } + }, [attempts]); + function finishGame(status = STATUS_LOST) { setGameEndDate(new Date()); setStatus(status); } + function startGame() { const startDate = new Date(); setGameEndDate(null); @@ -68,16 +82,16 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); } + function resetGame() { setGameStartDate(null); setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); } - /** * Обработка основного действия в игре - открытие карты. - * После открытия карты игра может пепереходит в следующие состояния + * После открытия карты игра может пепереходить в следующие состояния * - "Игрок выиграл", если на поле открыты все карты * - "Игрок проиграл", если на поле есть две открытые карты без пары * - "Игра продолжается", если не случилось первых двух условий @@ -92,13 +106,11 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { if (card.id !== clickedCard.id) { return card; } - return { ...card, open: true, }; }); - setCards(nextCards); const isPlayerWon = nextCards.every(card => card.open); @@ -115,19 +127,35 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Ищем открытые карты, у которых нет пары среди других открытых const openCardsWithoutPair = openCards.filter(card => { const sameCards = openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank); - if (sameCards.length < 2) { return true; } - return false; }); + // ниже мне нужно для каждой карточки из массива openCardsWithoutPair проставить статус open: false, + if (openCardsWithoutPair.length >= 2 && attempts >= 1) { + handleAttemptsChange(attempts - 1); + + const closeCards = cards.map(card => { + const shouldClose = openCardsWithoutPair.some(openCard => openCard.id === card.id); - const playerLost = openCardsWithoutPair.length >= 2; + if (shouldClose) { + return { + ...card, + open: false, + }; + } + + return card; + }); + + setCards(closeCards); + } // "Игрок проиграл", т.к на поле есть две открытые карты без пары if (playerLost) { finishGame(STATUS_LOST); + handleAttemptsChange(startLife); return; } @@ -208,6 +236,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { rank={card.rank} /> ))} + {isGameEnded ? ( diff --git a/src/context/Context.jsx b/src/context/Context.jsx new file mode 100644 index 000000000..afcda3dcb --- /dev/null +++ b/src/context/Context.jsx @@ -0,0 +1,25 @@ +import { createContext, useState } from "react"; + +export const Context = createContext(null); + +export const ContextProvider = ({ children }) => { + const ONE_LIFE = 1; + const THREE_LIFE = 3; + const [attempts, setAttempts] = useState(ONE_LIFE); + const [startLife, setStartLife] = useState(ONE_LIFE); + const handleAttemptsChangeOnStart = () => { + const newLife = attempts === ONE_LIFE ? THREE_LIFE : ONE_LIFE; + setAttempts(newLife); + setStartLife(newLife); + }; + const handleAttemptsChange = num => { + setAttempts(num); + }; + console.log(startLife); + + return ( + + {children} + + ); +}; diff --git a/src/hooks/useCustomContext.js b/src/hooks/useCustomContext.js new file mode 100644 index 000000000..fed05cce1 --- /dev/null +++ b/src/hooks/useCustomContext.js @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { Context } from "../context/Context"; + +export const useCustomContext = () => { + return useContext(Context); +}; diff --git a/src/index.js b/src/index.js index f689c5f0b..b5b8a9972 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,13 @@ import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; +import { ContextProvider } from "./context/Context"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + , ); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 758942e51..d8cc161af 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,7 +1,10 @@ import { Link } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; +import { useCustomContext } from "../../hooks/useCustomContext"; export function SelectLevelPage() { + const { handleAttemptsChangeOnStart } = useCustomContext(); + return (
@@ -23,6 +26,10 @@ export function SelectLevelPage() { +
); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 390ac0def..53627415f 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -1,64 +1,78 @@ .container { - width: 100%; - min-height: 100%; - display: flex; - align-items: center; - justify-content: center; + width: 100%; + min-height: 100%; + display: flex; + align-items: center; + justify-content: center; } .modal { - width: 480px; - height: 459px; - border-radius: 12px; - background: #c2f5ff; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + width: 480px; + height: 459px; + border-radius: 12px; + background: #c2f5ff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } .title { - color: #004980; - text-align: center; - font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; - font-size: 40px; - font-style: normal; - font-weight: 400; - line-height: 48px; + color: #004980; + text-align: center; + font-variant-numeric: lining-nums proportional-nums; + font-family: StratosSkyeng; + font-size: 40px; + font-style: normal; + font-weight: 400; + line-height: 48px; } .levels { - display: flex; - flex-direction: row; - gap: 26px; - margin-top: 48px; - margin-bottom: 28px; + display: flex; + flex-direction: row; + gap: 26px; + margin-top: 48px; + margin-bottom: 28px; } .level { - display: flex; - width: 97px; - height: 98px; - flex-direction: column; - justify-content: center; - flex-shrink: 0; + display: flex; + width: 97px; + height: 98px; + flex-direction: column; + justify-content: center; + flex-shrink: 0; - border-radius: 12px; - background: #fff; + border-radius: 12px; + background: #fff; } .levelLink { - color: #0080c1; - text-align: center; - font-family: StratosSkyeng; - font-size: 64px; - font-style: normal; - font-weight: 400; - line-height: 72px; - text-decoration: none; + color: #0080c1; + text-align: center; + font-family: StratosSkyeng; + font-size: 64px; + font-style: normal; + font-weight: 400; + line-height: 72px; + text-decoration: none; } .levelLink:visited { - color: #0080c1; + color: #0080c1; +} + +.checkBox { + cursor: pointer; + width: 20px; + height: 20px; +} + +.labelText { + display: flex; + align-items: center; + font-family: inherit; + color: #004980; + font-size: 20px; } From ff70e0c36f5d4374ba5d7a0d1616993e3ff32a18 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Wed, 3 Jul 2024 23:16:28 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9b90842c4..d14d2a961 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,7 @@ https://skypro-web-developer.github.io/react-memo/ Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом. Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения. + +## Домашняя работа №1 +- Ожидаемое время исполнение: 3 часа. +- Затраченное время: 4 часа. \ No newline at end of file From 9cfa03a5761d937f310599ef44a42591d56f6ef5 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Wed, 3 Jul 2024 23:55:17 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/vcs.xml | 2 +- README.md | 2 +- package-lock.json | 536 ++++++++++++++++++++-------------------- package.json | 2 +- src/context/Context.jsx | 1 - 5 files changed, 275 insertions(+), 268 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1ddfb..94a25f7f4 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index d14d2a961..b258a85ee 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ В этом репозитории реализован MVP карточкой игры "Мемо" по [тех.заданию](./docs/mvp-spec.md) Проект задеплоен на gh pages: -https://skypro-web-developer.github.io/react-memo/ +https://aleks3y1.github.io/react-memo/ ## Разработка diff --git a/package-lock.json b/package-lock.json index edaf5083f..c315326d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,9 +36,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -64,12 +64,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -154,13 +154,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -282,31 +282,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -426,28 +429,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } @@ -487,22 +490,23 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1973,32 +1977,32 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2006,12 +2010,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3099,13 +3103,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3120,9 +3124,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -3142,9 +3146,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -5716,12 +5720,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -5729,7 +5733,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -5808,11 +5812,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6287,9 +6291,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -7277,9 +7281,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" }, @@ -8330,16 +8334,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8552,9 +8556,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8641,9 +8645,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -12962,9 +12966,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -13465,9 +13469,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -13642,9 +13646,9 @@ } }, "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "funding": [ { "type": "opencollective", @@ -13660,9 +13664,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -15044,9 +15048,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -16196,9 +16200,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -17532,9 +17536,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -17710,9 +17714,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, @@ -18299,9 +18303,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -18393,9 +18397,9 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" }, "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" }, "@alloc/quick-lru": { "version": "5.2.0", @@ -18412,12 +18416,12 @@ } }, "@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { @@ -18477,13 +18481,13 @@ } }, "@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "requires": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, @@ -18575,25 +18579,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "requires": { + "@babel/types": "^7.24.7" + } }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-member-expression-to-functions": { @@ -18674,22 +18681,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==" }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/helper-validator-option": { "version": "7.22.5", @@ -18717,19 +18724,20 @@ } }, "@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -19674,39 +19682,39 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "requires": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, @@ -20426,13 +20434,13 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "requires": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -20441,9 +20449,9 @@ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/source-map": { "version": "0.3.5", @@ -20460,9 +20468,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -22378,12 +22386,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -22391,7 +22399,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -22459,11 +22467,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -22805,9 +22813,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -23489,9 +23497,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "requires": { "jake": "^10.8.5" } @@ -24243,16 +24251,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -24431,9 +24439,9 @@ "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -24501,9 +24509,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "for-each": { "version": "0.3.3", @@ -27568,9 +27576,9 @@ } }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -27924,9 +27932,9 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "picomatch": { "version": "2.3.1", @@ -28048,13 +28056,13 @@ } }, "postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" } }, "postcss-attribute-case-insensitive": { @@ -28869,9 +28877,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -29711,9 +29719,9 @@ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-loader": { "version": "3.0.2", @@ -30704,9 +30712,9 @@ } }, "webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "requires": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -30825,9 +30833,9 @@ } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "requires": {} } } @@ -31297,9 +31305,9 @@ } }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "requires": {} }, "xml-name-validator": { diff --git a/package.json b/package.json index e9b7a089e..dd4111729 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "memo-card-game", "version": "0.1.0", "private": true, - "homepage": "/react-memo/", + "homepage": "https://aleks3y1.github.io/react-memo/", "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/src/context/Context.jsx b/src/context/Context.jsx index afcda3dcb..3cbc2a1a7 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -15,7 +15,6 @@ export const ContextProvider = ({ children }) => { const handleAttemptsChange = num => { setAttempts(num); }; - console.log(startLife); return ( From 294c5699f0d2c187f2887e3f23b42f24c7f06459 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Thu, 4 Jul 2024 22:05:19 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b258a85ee..3e87644c8 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,5 @@ https://aleks3y1.github.io/react-memo/ ## Домашняя работа №1 - Ожидаемое время исполнение: 3 часа. -- Затраченное время: 4 часа. \ No newline at end of file +- Затраченное время: 4 часа. +- ПРОВЕРКА! \ No newline at end of file From a8c2a77e6eea0df14259f69e36d40ac8fafaf08a Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sun, 7 Jul 2024 22:28:20 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F.=20?= =?UTF-8?q?=D0=94=D0=BE=D0=BB=D0=B6=D0=BD=D0=BE=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D1=8F=D1=82=D1=8C=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B2=D0=BE=20=D0=B6=D0=B8=D0=B7=D0=BD=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B2=20localStorage.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- docs/mvp-spec.md | 7 +- .../AttemptsCounter.module.css | 22 ++-- src/components/Cards/Cards.jsx | 44 +++---- src/context/Context.jsx | 43 +++++-- src/pages/SelectLevelPage/SelectLevelPage.jsx | 63 +++++++--- .../SelectLevelPage.module.css | 119 ++++++++++-------- 7 files changed, 189 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 3e87644c8..7e8f1bb91 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ https://aleks3y1.github.io/react-memo/ Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения. ## Домашняя работа №1 + - Ожидаемое время исполнение: 3 часа. - Затраченное время: 4 часа. -- ПРОВЕРКА! \ No newline at end of file +- ПРОВЕРКА! diff --git a/docs/mvp-spec.md b/docs/mvp-spec.md index fab47685e..e083d7788 100644 --- a/docs/mvp-spec.md +++ b/docs/mvp-spec.md @@ -14,9 +14,10 @@ Количество карточек для каждого уровня сложности можете назначать и свои или выбрать готовый пресет. Предлагаем следующее пресеты: - - Легкий уровень - 6 карточек (3 пары) - - Средний уровень - 12 карточек (6 пар) - - Сложный уровень - 18 карточек (9 пар) + +- Легкий уровень - 6 карточек (3 пары) +- Средний уровень - 12 карточек (6 пар) +- Сложный уровень - 18 карточек (9 пар) Как только уровень сложности выбран, игроку показывается на игровой поле. diff --git a/src/components/AttemptsCounter/AttemptsCounter.module.css b/src/components/AttemptsCounter/AttemptsCounter.module.css index 4cc96e92e..0298ba2c2 100644 --- a/src/components/AttemptsCounter/AttemptsCounter.module.css +++ b/src/components/AttemptsCounter/AttemptsCounter.module.css @@ -1,15 +1,15 @@ .attemptsBlock { - width: 672px; - margin: 0 auto; - padding: 26px; - padding-top: 22px; - box-sizing: border-box; + width: 672px; + margin: 0 auto; + padding: 26px; + padding-top: 22px; + box-sizing: border-box; } .attemptsText { - color: #fff; - font-family: StratosSkyeng, serif; - font-size: 48px; - font-style: normal; - font-weight: 400; -} \ No newline at end of file + color: #fff; + font-family: StratosSkyeng, serif; + font-size: 48px; + font-style: normal; + font-weight: 400; +} diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 353222811..86ee40e36 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -64,9 +64,8 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { useEffect(() => { if (attempts === 0) { - setPlayerLost(attempts); + setPlayerLost(true); finishGame(STATUS_LOST); - handleAttemptsChange(startLife); } }, [attempts]); @@ -88,7 +87,10 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); + handleAttemptsChange(startLife); // Сброс количества жизней при перезапуске игры + setPlayerLost(false); } + /** * Обработка основного действия в игре - открытие карты. * После открытия карты игра может пепереходить в следующие состояния @@ -101,6 +103,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { if (clickedCard.open) { return; } + // Игровое поле после открытия кликнутой карты const nextCards = cards.map(card => { if (card.id !== clickedCard.id) { @@ -127,29 +130,28 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Ищем открытые карты, у которых нет пары среди других открытых const openCardsWithoutPair = openCards.filter(card => { const sameCards = openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank); - if (sameCards.length < 2) { - return true; - } - return false; + return sameCards.length < 2; }); - // ниже мне нужно для каждой карточки из массива openCardsWithoutPair проставить статус open: false, - if (openCardsWithoutPair.length >= 2 && attempts >= 1) { + + if (openCardsWithoutPair.length >= 2 && attempts > 0) { handleAttemptsChange(attempts - 1); - const closeCards = cards.map(card => { - const shouldClose = openCardsWithoutPair.some(openCard => openCard.id === card.id); + setTimeout(() => { + const closeCards = cards.map(card => { + const shouldClose = openCardsWithoutPair.some(openCard => openCard.id === card.id); - if (shouldClose) { - return { - ...card, - open: false, - }; - } + if (shouldClose) { + return { + ...card, + open: false, + }; + } - return card; - }); + return card; + }); - setCards(closeCards); + setCards(closeCards); + }, 1000); // Таймаут 1 секунда } // "Игрок проиграл", т.к на поле есть две открытые карты без пары @@ -213,12 +215,12 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { <>
min
-
{timer.minutes.toString().padStart("2", "0")}
+
{timer.minutes.toString().padStart(2, "0")}
.
sec
-
{timer.seconds.toString().padStart("2", "0")}
+
{timer.seconds.toString().padStart(2, "0")}
)} diff --git a/src/context/Context.jsx b/src/context/Context.jsx index 3cbc2a1a7..a8499a365 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -1,23 +1,48 @@ -import { createContext, useState } from "react"; +import React, { createContext, useState, useCallback, useEffect } from "react"; export const Context = createContext(null); export const ContextProvider = ({ children }) => { const ONE_LIFE = 1; const THREE_LIFE = 3; - const [attempts, setAttempts] = useState(ONE_LIFE); - const [startLife, setStartLife] = useState(ONE_LIFE); - const handleAttemptsChangeOnStart = () => { - const newLife = attempts === ONE_LIFE ? THREE_LIFE : ONE_LIFE; + + const [attempts, setAttempts] = useState(() => { + const savedAttempts = localStorage.getItem("attempts"); + return savedAttempts ? Number(savedAttempts) : ONE_LIFE; + }); + + const [startLife, setStartLife] = useState(() => { + const savedStartLife = localStorage.getItem("startLife"); + return savedStartLife ? Number(savedStartLife) : ONE_LIFE; + }); + + useEffect(() => { + localStorage.setItem("attempts", attempts); + }, [attempts]); + + useEffect(() => { + localStorage.setItem("startLife", startLife); + }, [startLife]); + + const handleAttemptsChangeOnStart = useCallback(() => { + const newLife = startLife === ONE_LIFE ? THREE_LIFE : ONE_LIFE; setAttempts(newLife); setStartLife(newLife); - }; - const handleAttemptsChange = num => { + }, [startLife]); + + const handleAttemptsChange = useCallback(num => { setAttempts(num); - }; + }, []); + + const handleStartLifeChange = useCallback(newLife => { + setStartLife(newLife); + setAttempts(newLife); + }, []); return ( - + {children} ); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index d8cc161af..31fab92c6 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,35 +1,66 @@ -import { Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; import { useCustomContext } from "../../hooks/useCustomContext"; +import { Button } from "../../components/Button/Button"; +import { useState, useCallback, useEffect } from "react"; export function SelectLevelPage() { - const { handleAttemptsChangeOnStart } = useCustomContext(); + const { handleStartLifeChange } = useCustomContext(); + const [gameLevel, setGameLevel] = useState(0); + const [isEasyMode, setIsEasyMode] = useState(() => { + const savedEasyMode = localStorage.getItem("isEasyMode"); + return savedEasyMode ? JSON.parse(savedEasyMode) : false; + }); + const navigate = useNavigate(); + + useEffect(() => { + if (isEasyMode) { + handleStartLifeChange(3); + } else { + handleStartLifeChange(1); + } + }, [isEasyMode, handleStartLifeChange]); + + useEffect(() => { + localStorage.setItem("isEasyMode", JSON.stringify(isEasyMode)); + }, [isEasyMode]); + + const handleLevelChange = useCallback(level => { + setGameLevel(level); + }, []); + + const handleCheckboxChange = useCallback(() => { + setIsEasyMode(prevMode => !prevMode); + }, []); + + const handlePlayClick = useCallback(() => { + if (gameLevel === 0) { + alert(`Не выбрана сложность`); + } else { + navigate(`/game/${gameLevel}`); + } + }, [gameLevel, navigate]); return (

Выбери сложность

    -
  • - - 1 - +
  • handleLevelChange(3)}> + 1
  • -
  • - - 2 - +
  • handleLevelChange(6)}> + 2
  • -
  • - - 3 - +
  • handleLevelChange(9)}> + 3
+
); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 53627415f..32a4db0e8 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -1,78 +1,95 @@ .container { - width: 100%; - min-height: 100%; - display: flex; - align-items: center; - justify-content: center; + width: 100%; + min-height: 100%; + display: flex; + align-items: center; + justify-content: center; } .modal { - width: 480px; - height: 459px; - border-radius: 12px; - background: #c2f5ff; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + width: 480px; + height: 459px; + border-radius: 12px; + background: #c2f5ff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } .title { - color: #004980; - text-align: center; - font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; - font-size: 40px; - font-style: normal; - font-weight: 400; - line-height: 48px; + color: #004980; + text-align: center; + font-variant-numeric: lining-nums proportional-nums; + font-family: StratosSkyeng; + font-size: 40px; + font-style: normal; + font-weight: 400; + line-height: 48px; } .levels { - display: flex; - flex-direction: row; - gap: 26px; - margin-top: 48px; - margin-bottom: 28px; + display: flex; + flex-direction: row; + gap: 26px; + margin-top: 48px; + margin-bottom: 28px; } .level { - display: flex; - width: 97px; - height: 98px; - flex-direction: column; - justify-content: center; - flex-shrink: 0; + display: flex; + width: 97px; + height: 98px; + flex-direction: column; + justify-content: center; + flex-shrink: 0; + color: #0080c1; + text-align: center; + font-family: StratosSkyeng; + font-size: 64px; + font-style: normal; + font-weight: 400; + line-height: 72px; + text-decoration: none; + cursor: pointer; - border-radius: 12px; - background: #fff; + border-radius: 12px; + background: #fff; } .levelLink { - color: #0080c1; - text-align: center; - font-family: StratosSkyeng; - font-size: 64px; - font-style: normal; - font-weight: 400; - line-height: 72px; - text-decoration: none; + color: #0080c1; + text-align: center; + font-family: StratosSkyeng; + font-size: 64px; + font-style: normal; + font-weight: 400; + line-height: 72px; + text-decoration: none; } .levelLink:visited { - color: #0080c1; + color: #0080c1; } .checkBox { - cursor: pointer; - width: 20px; - height: 20px; + cursor: pointer; + width: 30px; + height: 29px; } .labelText { - display: flex; - align-items: center; - font-family: inherit; - color: #004980; - font-size: 20px; + display: flex; + align-items: center; + font-family: inherit; + color: #004980; + font-size: 24px; + line-height: 32px; + margin-bottom: 38px; +} + +.buttonLink { + text-decoration: none; + color: inherit; + font-variant-numeric: lining-nums proportional-nums; } From 7d037ba10df1ce84c8046c392e566837b5b02290 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Mon, 8 Jul 2024 20:47:55 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=D0=A1=D1=82=D0=B8=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D1=83=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D1=8F=20=D1=81=D0=BB=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SelectLevelPage/LevelStyle.js | 8 ++++++++ src/pages/SelectLevelPage/SelectLevelPage.jsx | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/pages/SelectLevelPage/LevelStyle.js diff --git a/src/pages/SelectLevelPage/LevelStyle.js b/src/pages/SelectLevelPage/LevelStyle.js new file mode 100644 index 000000000..ca7d864c4 --- /dev/null +++ b/src/pages/SelectLevelPage/LevelStyle.js @@ -0,0 +1,8 @@ +export const LevelStyle = (currentLevel, level) => { + const isLevelSelected = currentLevel === level; + + return { + backgroundColor: isLevelSelected ? "#0080c1" : "#fff", + color: isLevelSelected ? "#fff" : "#0080c1", + }; +}; diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 31fab92c6..ef10cd536 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -3,10 +3,12 @@ import styles from "./SelectLevelPage.module.css"; import { useCustomContext } from "../../hooks/useCustomContext"; import { Button } from "../../components/Button/Button"; import { useState, useCallback, useEffect } from "react"; +import { LevelStyle } from "./LevelStyle.js"; export function SelectLevelPage() { const { handleStartLifeChange } = useCustomContext(); const [gameLevel, setGameLevel] = useState(0); + //const [changeStyle, setChangeStyle] = useState(null); const [isEasyMode, setIsEasyMode] = useState(() => { const savedEasyMode = localStorage.getItem("isEasyMode"); return savedEasyMode ? JSON.parse(savedEasyMode) : false; @@ -46,13 +48,13 @@ export function SelectLevelPage() {

Выбери сложность

    -
  • handleLevelChange(3)}> +
  • handleLevelChange(3)} style={LevelStyle(gameLevel, 3)}> 1
  • -
  • handleLevelChange(6)}> +
  • handleLevelChange(6)} style={LevelStyle(gameLevel, 6)}> 2
  • -
  • handleLevelChange(9)}> +
  • handleLevelChange(9)} style={LevelStyle(gameLevel, 9)}> 3
From 793e7f16e193cddaf134ff38a9fa89ff69524351 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sat, 13 Jul 2024 16:30:24 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D1=83=20=D0=BB=D0=B8=D0=B4=D0=B5=D1=80=D0=BE=D0=B2?= =?UTF-8?q?.=20=D0=9A=D0=BE=D0=BB-=D0=B2=D0=BE=20=D0=BB=D0=B8=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=207.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.js | 26 +++++++ src/components/Cards/Cards.jsx | 1 + src/components/Cards/Cards.module.css | 11 ++- src/components/EndGameModal/EndGameModal.jsx | 78 ++++++++++++++++--- .../EndGameModal/EndGameModal.module.css | 35 ++++++++- .../LeaderboardBlock/LeaderboardBlock.jsx | 48 ++++++++++++ src/context/Context.jsx | 27 ++++++- src/pages/Leaderboard/Leaderboard.jsx | 5 ++ src/pages/Leaderboard/Leaderboard.module.css | 48 ++++++++++++ src/pages/SelectLevelPage/SelectLevelPage.jsx | 14 +++- .../SelectLevelPage.module.css | 15 +++- src/router.js | 5 ++ 12 files changed, 290 insertions(+), 23 deletions(-) create mode 100644 src/api.js create mode 100644 src/components/LeaderboardBlock/LeaderboardBlock.jsx create mode 100644 src/pages/Leaderboard/Leaderboard.jsx create mode 100644 src/pages/Leaderboard/Leaderboard.module.css diff --git a/src/api.js b/src/api.js new file mode 100644 index 000000000..aa64340f9 --- /dev/null +++ b/src/api.js @@ -0,0 +1,26 @@ +export const getLeaderboard = async () => { + const response = await fetch("https://wedev-api.sky.pro/api/leaderboard"); + if (!response.ok) { + throw new Error("Ошибка получения список лидеров"); + } + const result = await response.json(); + return result; +}; + +export const addLeader = async (name, time) => { + const payload = JSON.stringify({ name, time }); + + const response = await fetch("https://wedev-api.sky.pro/api/leaderboard", { + method: "POST", + body: payload, + }); + + if (!response.ok) { + const errorText = await response.text(); + console.log("Ошибка:", errorText); + throw new Error("Ошибка добавление лидера"); + } + + const result = await response.json(); + return result; +}; diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 86ee40e36..1a4e8903a 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -247,6 +247,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { isWon={status === STATUS_WON} gameDurationSeconds={timer.seconds} gameDurationMinutes={timer.minutes} + hardMode={startLife} onClick={resetGame} />
diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 000c5006c..5ebcb2513 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -1,8 +1,7 @@ .container { width: 672px; margin: 0 auto; - padding: 26px; - padding-top: 22px; + padding: 22px 26px 26px 26px; box-sizing: border-box; } @@ -28,16 +27,16 @@ .header { display: flex; justify-content: space-between; - align-items: end; + align-items: flex-end; margin-bottom: 35px; } .timer { display: flex; - align-items: end; + align-items: flex-end; color: #fff; - font-family: StratosSkyeng; + font-family: StratosSkyeng, serif; font-size: 64px; font-style: normal; font-weight: 400; @@ -62,7 +61,7 @@ .timerDescription { color: #fff; font-variant-numeric: lining-nums proportional-nums; - font-family: StratosSkyeng; + font-family: StratosSkyeng, serif; font-size: 16px; font-style: normal; font-weight: 400; diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 722394833..42a2635b5 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -1,27 +1,87 @@ import styles from "./EndGameModal.module.css"; - import { Button } from "../Button/Button"; - import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; +import { useCustomContext } from "../../hooks/useCustomContext"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { addLeader, getLeaderboard } from "../../api"; -export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { +export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, hardMode }) { const title = isWon ? "Вы победили!" : "Вы проиграли!"; - const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; + const imgAlt = isWon ? "celebration emoji" : "dead emoji"; + const isHardMode = hardMode === 3; + const { handleLeaderboardChange } = useCustomContext(); + const resultTime = gameDurationMinutes * 60 + gameDurationSeconds; + const [isInLeaderboard, setIsInLeaderboard] = useState(false); + const [name, setName] = useState(""); + const navigate = useNavigate(); + + useEffect(() => { + if (isWon && isHardMode) { + getLeaderboard() + .then(data => { + handleLeaderboardChange(data.leaders); + const leadersTimes = data.leaders.map(leader => leader.time); + if (resultTime < Math.max(...leadersTimes) || data.leaders.length < 10) { + setIsInLeaderboard(true); + } + }) + .catch(error => { + console.error("Ошибка:", error); + }); + } + }, [isWon, resultTime, handleLeaderboardChange, isHardMode]); - const imgAlt = isWon ? "celebration emodji" : "dead emodji"; + const handleSaveResult = async () => { + const playerName = name.trim() || "Пользователь"; + try { + const updatedLeaderboard = await addLeader(playerName, resultTime); + handleLeaderboardChange(updatedLeaderboard.leaders); + } catch (error) { + console.log("Ошибка:", error); + } + }; + + const handlePlayAgain = async () => { + if (isWon && isHardMode && isInLeaderboard) { + await handleSaveResult(); + } + onClick(); + }; + + const handleGoToLeaderboard = async () => { + if (isWon && isHardMode && isInLeaderboard) { + await handleSaveResult(); + } + navigate("/leaderboard"); + }; return (
{imgAlt} -

{title}

+ {isHardMode && isInLeaderboard && ( + <> +

Вы попали на Лидерборд!

+ setName(e.target.value)} + /> + + )} + {!isInLeaderboard &&

{title}

}

Затраченное время:

- {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")} + {gameDurationMinutes.toString().padStart(2, "0")}.{gameDurationSeconds.toString().padStart(2, "0")}
- - + +

+ Перейти к лидерборду +

); } diff --git a/src/components/EndGameModal/EndGameModal.module.css b/src/components/EndGameModal/EndGameModal.module.css index 9368cb8b5..3fbc3b521 100644 --- a/src/components/EndGameModal/EndGameModal.module.css +++ b/src/components/EndGameModal/EndGameModal.module.css @@ -1,6 +1,6 @@ .modal { width: 480px; - height: 459px; + min-height: 459px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -12,7 +12,7 @@ .image { width: 96px; height: 96px; - margin-bottom: 8px; + margin: 36px 0 8px 0; } .title { @@ -23,8 +23,8 @@ font-style: normal; font-weight: 400; line-height: 48px; - margin-bottom: 28px; + text-align: center; } .description { @@ -46,6 +46,33 @@ font-style: normal; font-weight: 400; line-height: 72px; - + text-align: center; margin-bottom: 40px; } + +.leader_name { + width: 276px; + height: 45px; + top: 334px; + left: 374px; + border-radius: 10px; + margin-bottom: 28px; + font-family: StratosSkyeng; + + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: center; +} + +.link_leader { + font-family: StratosSkyeng; + color: #004980; + font-size: 18px; + font-weight: 400; + line-height: 32px; + text-align: left; + text-decoration: underline; + margin: 18px 0 48px 0; + cursor: pointer; +} diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx new file mode 100644 index 000000000..f5310bda2 --- /dev/null +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -0,0 +1,48 @@ +import { useCustomContext } from "../../hooks/useCustomContext"; +import styles from "../../pages/Leaderboard/Leaderboard.module.css"; +import { Button } from "../Button/Button"; +import { useNavigate } from "react-router-dom"; + +export function LeaderboardBlock() { + const { leaderboard } = useCustomContext(); + const navigate = useNavigate(); + const sortedLeaderboard = leaderboard + .slice() + .sort((a, b) => a.time - b.time) + .slice(0, 7); + + const startGame = () => { + navigate("/"); + }; + + return ( +
+
+
+

Лидерборд

+ +
+
    +
  • +
    +

    Позиция

    +

    Пользователь

    +

    +

    Время

    +
    +
  • + {sortedLeaderboard.map((leader, index) => ( +
  • +
    +

    {index + 1}

    +

    {leader.name}

    +

    +

    {leader.time} сек

    +
    +
  • + ))} +
+
+
+ ); +} diff --git a/src/context/Context.jsx b/src/context/Context.jsx index a8499a365..910915dbb 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -1,4 +1,5 @@ import React, { createContext, useState, useCallback, useEffect } from "react"; +import { getLeaderboard } from "../api"; export const Context = createContext(null); @@ -16,6 +17,22 @@ export const ContextProvider = ({ children }) => { return savedStartLife ? Number(savedStartLife) : ONE_LIFE; }); + const [leaderboard, setLeaderboard] = useState([]); + + const handleLeaderboardChange = useCallback(leaders => { + setLeaderboard(leaders); + }, []); + + useEffect(() => { + getLeaderboard() + .then(result => { + setLeaderboard(result.leaders); + }) + .catch(error => { + console.log("Ошибка:", error); + }); + }, []); + useEffect(() => { localStorage.setItem("attempts", attempts); }, [attempts]); @@ -41,7 +58,15 @@ export const ContextProvider = ({ children }) => { return ( {children} diff --git a/src/pages/Leaderboard/Leaderboard.jsx b/src/pages/Leaderboard/Leaderboard.jsx new file mode 100644 index 000000000..a2c24e965 --- /dev/null +++ b/src/pages/Leaderboard/Leaderboard.jsx @@ -0,0 +1,5 @@ +import { LeaderboardBlock } from "../../components/LeaderboardBlock/LeaderboardBlock"; + +export function Leaderboard() { + return ; +} diff --git a/src/pages/Leaderboard/Leaderboard.module.css b/src/pages/Leaderboard/Leaderboard.module.css new file mode 100644 index 000000000..3f158e6d9 --- /dev/null +++ b/src/pages/Leaderboard/Leaderboard.module.css @@ -0,0 +1,48 @@ +.container_leader { + width: 100%; + min-height: 100%; + display: flex; + justify-content: center; + color: white; +} + +.container_leader_top { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0 40px 40px 40px; +} + +.container_leader_block { + display: flex; + flex-direction: column; + box-sizing: border-box; + margin-top: 52px; + font-family: StratosSkyeng; + font-size: 24px; + font-weight: 400; + line-height: 32px; + text-align: left; + max-width: 944px; + width: 100%; +} + +.position { + height: 64px; + top: 142px; + left: 40px; + border-radius: 12px; + background: #ffffff; + list-style-type: none; + margin-bottom: 15px; + overflow: hidden; +} + +.position_information { + box-sizing: border-box; + display: grid; + grid-template-columns: 2fr 4fr 1fr 1fr; + gap: 66px; + color: #999999; + margin: 16px 20px 16px 20px; +} diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index ef10cd536..879ca7a77 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -8,7 +8,6 @@ import { LevelStyle } from "./LevelStyle.js"; export function SelectLevelPage() { const { handleStartLifeChange } = useCustomContext(); const [gameLevel, setGameLevel] = useState(0); - //const [changeStyle, setChangeStyle] = useState(null); const [isEasyMode, setIsEasyMode] = useState(() => { const savedEasyMode = localStorage.getItem("isEasyMode"); return savedEasyMode ? JSON.parse(savedEasyMode) : false; @@ -43,10 +42,18 @@ export function SelectLevelPage() { } }, [gameLevel, navigate]); + const handleLeaderboard = () => { + navigate(`/leaderboard`); + }; + return (
-

Выбери сложность

+

+ Выбери +
+ сложность +

  • handleLevelChange(3)} style={LevelStyle(gameLevel, 3)}> 1 @@ -63,6 +70,9 @@ export function SelectLevelPage() { Легкий режим (3 жизни) +

    + Перейти к лидерборду +

); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 32a4db0e8..b7182641f 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -8,7 +8,7 @@ .modal { width: 480px; - height: 459px; + min-height: 459px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -26,6 +26,7 @@ font-style: normal; font-weight: 400; line-height: 48px; + margin-top: 48px; } .levels { @@ -93,3 +94,15 @@ color: inherit; font-variant-numeric: lining-nums proportional-nums; } + +.leader_link { + font-family: StratosSkyeng; + color: #004980; + font-size: 18px; + font-weight: 400; + line-height: 32px; + text-align: left; + text-decoration: underline; + margin: 18px 0 48px 0; + cursor: pointer; +} diff --git a/src/router.js b/src/router.js index da6e94b51..4826abf63 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import { createBrowserRouter } from "react-router-dom"; import { GamePage } from "./pages/GamePage/GamePage"; import { SelectLevelPage } from "./pages/SelectLevelPage/SelectLevelPage"; +import { Leaderboard } from "./pages/Leaderboard/Leaderboard"; export const router = createBrowserRouter( [ @@ -12,6 +13,10 @@ export const router = createBrowserRouter( path: "/game/:pairsCount", element: , }, + { + path: "/leaderboard", + element: , + }, ], /** * basename нужен для корректной работы в gh pages From ad7c53d0ede9b3c144e56247b1248944fa08d622 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sat, 13 Jul 2024 16:43:51 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7e8f1bb91..df32337b4 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,7 @@ https://aleks3y1.github.io/react-memo/ - Ожидаемое время исполнение: 3 часа. - Затраченное время: 4 часа. - ПРОВЕРКА! + +## Домашняя работа №2 +- Ожидаемое время исполнение: 3 часа. +- Затраченное время: 5 часа. \ No newline at end of file From e69c938179971320a473c25b51760b86783edc99 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sat, 13 Jul 2024 16:49:19 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=D0=A3=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BA=D0=BE=D0=BB-=D0=B2=D0=BE=20=D0=BB=D0=B8?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B4=D0=BE=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LeaderboardBlock/LeaderboardBlock.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx index f5310bda2..320f7ed52 100644 --- a/src/components/LeaderboardBlock/LeaderboardBlock.jsx +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -9,7 +9,7 @@ export function LeaderboardBlock() { const sortedLeaderboard = leaderboard .slice() .sort((a, b) => a.time - b.time) - .slice(0, 7); + .slice(0, 10); const startGame = () => { navigate("/"); From 21d2cc4dd2ed97abe932ab0d974156dcf642aa92 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sun, 14 Jul 2024 08:49:40 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=85=D0=B0=D1=80=D0=B4=D0=BC=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- src/components/EndGameModal/EndGameModal.jsx | 4 ++-- src/context/Context.jsx | 11 +++++++++++ src/pages/SelectLevelPage/SelectLevelPage.jsx | 3 ++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index df32337b4..004cc963a 100644 --- a/README.md +++ b/README.md @@ -52,5 +52,6 @@ https://aleks3y1.github.io/react-memo/ - ПРОВЕРКА! ## Домашняя работа №2 + - Ожидаемое время исполнение: 3 часа. -- Затраченное время: 5 часа. \ No newline at end of file +- Затраченное время: 5 часа. diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 42a2635b5..1d418fb6d 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -11,8 +11,8 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, const title = isWon ? "Вы победили!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emoji" : "dead emoji"; - const isHardMode = hardMode === 3; - const { handleLeaderboardChange } = useCustomContext(); + const { handleLeaderboardChange, handleHardGameChange } = useCustomContext(); + const isHardMode = hardMode !== 3 && handleHardGameChange === 9; const resultTime = gameDurationMinutes * 60 + gameDurationSeconds; const [isInLeaderboard, setIsInLeaderboard] = useState(false); const [name, setName] = useState(""); diff --git a/src/context/Context.jsx b/src/context/Context.jsx index 910915dbb..e5af9d58f 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -23,6 +23,8 @@ export const ContextProvider = ({ children }) => { setLeaderboard(leaders); }, []); + const [hardGame, setHardGame] = useState(null); + useEffect(() => { getLeaderboard() .then(result => { @@ -41,6 +43,10 @@ export const ContextProvider = ({ children }) => { localStorage.setItem("startLife", startLife); }, [startLife]); + useEffect(() => { + localStorage.setItem("hardGame", hardGame); + }, [hardGame]); + const handleAttemptsChangeOnStart = useCallback(() => { const newLife = startLife === ONE_LIFE ? THREE_LIFE : ONE_LIFE; setAttempts(newLife); @@ -56,6 +62,10 @@ export const ContextProvider = ({ children }) => { setAttempts(newLife); }, []); + const handleHardGameChange = useCallback(num => { + setHardGame(num); + }, []); + return ( { handleStartLifeChange, leaderboard, handleLeaderboardChange, + handleHardGameChange, }} > {children} diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 879ca7a77..a4e75c9bc 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -6,7 +6,7 @@ import { useState, useCallback, useEffect } from "react"; import { LevelStyle } from "./LevelStyle.js"; export function SelectLevelPage() { - const { handleStartLifeChange } = useCustomContext(); + const { handleStartLifeChange, handleHardGameChange } = useCustomContext(); const [gameLevel, setGameLevel] = useState(0); const [isEasyMode, setIsEasyMode] = useState(() => { const savedEasyMode = localStorage.getItem("isEasyMode"); @@ -28,6 +28,7 @@ export function SelectLevelPage() { const handleLevelChange = useCallback(level => { setGameLevel(level); + handleHardGameChange(level); }, []); const handleCheckboxChange = useCallback(() => { From 27bfb39e7646f30e1e38de5f33869557a8926d65 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Tue, 23 Jul 2024 23:02:46 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 3 ++- src/components/EndGameModal/EndGameModal.jsx | 26 +++++++++++-------- .../LeaderboardBlock/LeaderboardBlock.jsx | 13 ++++++---- src/context/Context.jsx | 7 ++++- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 1a4e8903a..ec27241ef 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -43,7 +43,7 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - const { attempts, startLife, handleAttemptsChange } = useCustomContext(); + const { attempts, startLife, handleAttemptsChange, hardGame } = useCustomContext(); // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта const [cards, setCards] = useState([]); @@ -248,6 +248,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { gameDurationSeconds={timer.seconds} gameDurationMinutes={timer.minutes} hardMode={startLife} + hardGame={hardGame} onClick={resetGame} /> diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 1d418fb6d..32a7129ee 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -7,26 +7,31 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { addLeader, getLeaderboard } from "../../api"; -export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, hardMode }) { +export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, hardGame }) { const title = isWon ? "Вы победили!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emoji" : "dead emoji"; - const { handleLeaderboardChange, handleHardGameChange } = useCustomContext(); - const isHardMode = hardMode !== 3 && handleHardGameChange === 9; + const { handleLeaderboardChange } = useCustomContext(); + const [isHardMode, setIsHardMode] = useState(false); const resultTime = gameDurationMinutes * 60 + gameDurationSeconds; const [isInLeaderboard, setIsInLeaderboard] = useState(false); const [name, setName] = useState(""); const navigate = useNavigate(); + useEffect(() => { + setIsHardMode(hardGame === 9); + }, [hardGame]); + useEffect(() => { if (isWon && isHardMode) { getLeaderboard() .then(data => { - handleLeaderboardChange(data.leaders); - const leadersTimes = data.leaders.map(leader => leader.time); - if (resultTime < Math.max(...leadersTimes) || data.leaders.length < 10) { - setIsInLeaderboard(true); - } + const leaders = data.leaders.sort((a, b) => a.time - b.time).slice(0, 10); + handleLeaderboardChange(leaders); + const leadersTimes = leaders.map(leader => leader.time); + const isTopTen = leadersTimes.length < 10 || resultTime < Math.max(...leadersTimes); + setIsInLeaderboard(isTopTen); + console.log(leadersTimes); }) .catch(error => { console.error("Ошибка:", error); @@ -39,6 +44,7 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, try { const updatedLeaderboard = await addLeader(playerName, resultTime); handleLeaderboardChange(updatedLeaderboard.leaders); + navigate("/leaderboard"); } catch (error) { console.log("Ошибка:", error); } @@ -52,9 +58,6 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, }; const handleGoToLeaderboard = async () => { - if (isWon && isHardMode && isInLeaderboard) { - await handleSaveResult(); - } navigate("/leaderboard"); }; @@ -71,6 +74,7 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, value={name} onChange={e => setName(e.target.value)} /> + )} {!isInLeaderboard &&

{title}

} diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx index 320f7ed52..d2911178d 100644 --- a/src/components/LeaderboardBlock/LeaderboardBlock.jsx +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -6,15 +6,18 @@ import { useNavigate } from "react-router-dom"; export function LeaderboardBlock() { const { leaderboard } = useCustomContext(); const navigate = useNavigate(); - const sortedLeaderboard = leaderboard - .slice() - .sort((a, b) => a.time - b.time) - .slice(0, 10); + const sortedLeaderboard = leaderboard.sort((a, b) => a.time - b.time).slice(0, 10); const startGame = () => { navigate("/"); }; + const formatTime = timeInSeconds => { + const minutes = Math.floor(timeInSeconds / 60); + const seconds = timeInSeconds % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + }; + return (
@@ -37,7 +40,7 @@ export function LeaderboardBlock() {

{index + 1}

{leader.name}

-

{leader.time} сек

+

{formatTime(leader.time)}

))} diff --git a/src/context/Context.jsx b/src/context/Context.jsx index e5af9d58f..796570b77 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -23,7 +23,11 @@ export const ContextProvider = ({ children }) => { setLeaderboard(leaders); }, []); - const [hardGame, setHardGame] = useState(null); + // Инициализация hardGame из localStorage + const [hardGame, setHardGame] = useState(() => { + const savedHardGame = localStorage.getItem("hardGame"); + return savedHardGame ? Number(savedHardGame) : null; + }); useEffect(() => { getLeaderboard() @@ -77,6 +81,7 @@ export const ContextProvider = ({ children }) => { leaderboard, handleLeaderboardChange, handleHardGameChange, + hardGame, }} > {children} From 9012e61e6d262ebe1cf84d9fe7b4e7611bc185e7 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Thu, 25 Jul 2024 22:06:02 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/help_1.jpg | Bin 0 -> 7194 bytes public/help_2.jpg | Bin 0 -> 4091 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/help_1.jpg create mode 100644 public/help_2.jpg diff --git a/public/help_1.jpg b/public/help_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8885b689758e78773aed511023798c5952f2a51e GIT binary patch literal 7194 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 LU?vaXeDvP{rttsE literal 0 HcmV?d00001 diff --git a/public/help_2.jpg b/public/help_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c0ebe3f11e7b496418564098041042dc7e36207 GIT binary patch literal 4091 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 literal 0 HcmV?d00001 From 2a3f3c8796ddc4f7207fb07b099bb32729e2f018 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Mon, 29 Jul 2024 21:32:09 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20?= =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 + public/11.svg | 42 ++++ public/22.svg | 18 ++ public/frame_off.svg | 20 ++ public/frame_on.svg | 18 ++ public/help_1.jpg | Bin 7194 -> 0 bytes public/help_2.jpg | Bin 4091 -> 0 bytes public/magic_off.svg | 33 +++ public/magic_on.svg | 17 ++ src/api.js | 23 +- .../AchiveDescription/AchiveDescription.js | 31 +++ .../AchiveDescription.module.css | 54 +++++ src/components/Cards/Cards.jsx | 212 ++++++++++-------- src/components/EndGameModal/EndGameModal.jsx | 24 +- src/components/Helps/Helps.jsx | 44 ++++ src/components/Helps/Helps.module.css | 22 ++ .../ItemWithDescription.js | 25 +++ .../ItemWithDescription.module.css | 37 +++ .../LeaderboardBlock/LeaderboardBlock.jsx | 73 +++++- src/context/Context.jsx | 15 +- 20 files changed, 590 insertions(+), 123 deletions(-) create mode 100644 public/11.svg create mode 100644 public/22.svg create mode 100644 public/frame_off.svg create mode 100644 public/frame_on.svg delete mode 100644 public/help_1.jpg delete mode 100644 public/help_2.jpg create mode 100644 public/magic_off.svg create mode 100644 public/magic_on.svg create mode 100644 src/components/AchiveDescription/AchiveDescription.js create mode 100644 src/components/AchiveDescription/AchiveDescription.module.css create mode 100644 src/components/Helps/Helps.jsx create mode 100644 src/components/Helps/Helps.module.css create mode 100644 src/components/ItemWithDescription/ItemWithDescription.js create mode 100644 src/components/ItemWithDescription/ItemWithDescription.module.css diff --git a/README.md b/README.md index 004cc963a..67e1636bb 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,8 @@ https://aleks3y1.github.io/react-memo/ - Ожидаемое время исполнение: 3 часа. - Затраченное время: 5 часа. + +## Домашняя работа №3 + +- Ожидаемое время исполнение: 10 часов. +- Затраченное время: 12 часов. diff --git a/public/11.svg b/public/11.svg new file mode 100644 index 000000000..b3cb1dfa4 --- /dev/null +++ b/public/11.svg @@ -0,0 +1,42 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/22.svg b/public/22.svg new file mode 100644 index 000000000..2cc4626dd --- /dev/null +++ b/public/22.svg @@ -0,0 +1,18 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + diff --git a/public/frame_off.svg b/public/frame_off.svg new file mode 100644 index 000000000..b49cb5c68 --- /dev/null +++ b/public/frame_off.svg @@ -0,0 +1,20 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + diff --git a/public/frame_on.svg b/public/frame_on.svg new file mode 100644 index 000000000..eb15a920b --- /dev/null +++ b/public/frame_on.svg @@ -0,0 +1,18 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + diff --git a/public/help_1.jpg b/public/help_1.jpg deleted file mode 100644 index 8885b689758e78773aed511023798c5952f2a51e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7194 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 LU?vaXeDvP{rttsE diff --git a/public/help_2.jpg b/public/help_2.jpg deleted file mode 100644 index 0c0ebe3f11e7b496418564098041042dc7e36207..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4091 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 diff --git a/public/magic_off.svg b/public/magic_off.svg new file mode 100644 index 000000000..c09c69b48 --- /dev/null +++ b/public/magic_off.svg @@ -0,0 +1,33 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/magic_on.svg b/public/magic_on.svg new file mode 100644 index 000000000..72e2de0ed --- /dev/null +++ b/public/magic_on.svg @@ -0,0 +1,17 @@ + + + Created with Pixso. + + + + + + + + + + + + + + diff --git a/src/api.js b/src/api.js index aa64340f9..66bf57f71 100644 --- a/src/api.js +++ b/src/api.js @@ -1,5 +1,5 @@ export const getLeaderboard = async () => { - const response = await fetch("https://wedev-api.sky.pro/api/leaderboard"); + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard"); if (!response.ok) { throw new Error("Ошибка получения список лидеров"); } @@ -7,20 +7,19 @@ export const getLeaderboard = async () => { return result; }; -export const addLeader = async (name, time) => { - const payload = JSON.stringify({ name, time }); - - const response = await fetch("https://wedev-api.sky.pro/api/leaderboard", { +export async function addLeader(name, time, achievements) { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { method: "POST", - body: payload, + body: JSON.stringify({ + name, + time, + achievements, + }), }); if (!response.ok) { - const errorText = await response.text(); - console.log("Ошибка:", errorText); - throw new Error("Ошибка добавление лидера"); + throw new Error("Ошибка при добавлении лидера"); } - const result = await response.json(); - return result; -}; + return await response.json(); +} diff --git a/src/components/AchiveDescription/AchiveDescription.js b/src/components/AchiveDescription/AchiveDescription.js new file mode 100644 index 000000000..8c3ee5569 --- /dev/null +++ b/src/components/AchiveDescription/AchiveDescription.js @@ -0,0 +1,31 @@ +import React, { useState } from "react"; +import styles from "./AchiveDescription.module.css"; + +const AchiveDescription = ({ children, description, style }) => { + const [isHovered, setIsHovered] = useState(false); + + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + return ( +
+ {isHovered &&
} +
+ {children} +
+ {description} +
+
+
+ ); +}; + +export default AchiveDescription; diff --git a/src/components/AchiveDescription/AchiveDescription.module.css b/src/components/AchiveDescription/AchiveDescription.module.css new file mode 100644 index 000000000..01a1fee10 --- /dev/null +++ b/src/components/AchiveDescription/AchiveDescription.module.css @@ -0,0 +1,54 @@ +.itemContainer { + position: absolute; + display: inline-block; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5; + pointer-events: none; +} + +.content { + position: relative; +} + +.description { + min-width: 174px; + height: auto; + border-radius: 12px; + display: block; + position: absolute; + top: -100px; + left: 100px; + transform: translateX(-50%); + padding: 10px; + border: 1px solid #c2f5ff; + background-color: #c2f5ff; + white-space: normal; + color: #004980; + z-index: 20; + visibility: visible; + opacity: 1; + transition: opacity 0.2s; + font-size: 18px; + font-weight: 400; +} + +.description::before { + content: ""; + width: 0; + height: 0; + border-left: 0 solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #c2f5ff; + position: absolute; + bottom: -10px; + left: 10%; + transform: translateX(-100%); + z-index: -1; +} diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index ec27241ef..fb28f000a 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -2,26 +2,20 @@ import { shuffle } from "lodash"; import { useEffect, useState } from "react"; import { generateDeck } from "../../utils/cards"; import styles from "./Cards.module.css"; -import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; -import { Button } from "../../components/Button/Button"; -import { Card } from "../../components/Card/Card"; +import { EndGameModal } from "../EndGameModal/EndGameModal"; +import { Card } from "../Card/Card"; import { AttemptsCounter } from "../AttemptsCounter/AttemptsCounter"; import { useCustomContext } from "../../hooks/useCustomContext"; +import { Helps } from "../Helps/Helps"; -// Игра закончилась const STATUS_LOST = "STATUS_LOST"; const STATUS_WON = "STATUS_WON"; -// Идет игра: карты закрыты, игрок может их открыть const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; -// Начало игры: игрок видит все карты в течении нескольких секунд const STATUS_PREVIEW = "STATUS_PREVIEW"; function getTimerValue(startDate, endDate) { if (!startDate && !endDate) { - return { - minutes: 0, - seconds: 0, - }; + return { minutes: 0, seconds: 0 }; } if (endDate === null) { @@ -31,36 +25,28 @@ function getTimerValue(startDate, endDate) { const diffInSeconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); const minutes = Math.floor(diffInSeconds / 60); const seconds = diffInSeconds % 60; - return { - minutes, - seconds, - }; + return { minutes, seconds }; } -/** - * Основной компонент игры, внутри него находится вся игровая механика и логика. - * pairsCount - сколько пар будет в игре - * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры - */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - const { attempts, startLife, handleAttemptsChange, hardGame } = useCustomContext(); - // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта + const { + attempts, + startLife, + handleAttemptsChange, + hardGame, + handleIsAlahomora, + isAlahomoraUsed, + isClairvoyanceUsed, + handleIsClairvoyance, + } = useCustomContext(); const [cards, setCards] = useState([]); - - // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); - - // Дата начала игры const [gameStartDate, setGameStartDate] = useState(null); - // Дата конца игры const [gameEndDate, setGameEndDate] = useState(null); const [playerLost, setPlayerLost] = useState(false); + const [isInLeaderboard] = useState(hardGame); - // Стейт для таймера, высчитывается в setInteval на основе gameStartDate и gameEndDate - const [timer, setTimer] = useState({ - seconds: 0, - minutes: 0, - }); + const [timer, setTimer] = useState({ seconds: 0, minutes: 0 }); useEffect(() => { if (attempts === 0) { @@ -69,7 +55,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { } }, [attempts]); - function finishGame(status = STATUS_LOST) { + function finishGame(status) { setGameEndDate(new Date()); setStatus(status); } @@ -87,119 +73,146 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); - handleAttemptsChange(startLife); // Сброс количества жизней при перезапуске игры + handleAttemptsChange(startLife); setPlayerLost(false); + handleIsAlahomora(false); + handleIsClairvoyance(false); } - /** - * Обработка основного действия в игре - открытие карты. - * После открытия карты игра может пепереходить в следующие состояния - * - "Игрок выиграл", если на поле открыты все карты - * - "Игрок проиграл", если на поле есть две открытые карты без пары - * - "Игра продолжается", если не случилось первых двух условий - */ const openCard = clickedCard => { - // Если карта уже открыта, то ничего не делаем - if (clickedCard.open) { - return; - } + if (clickedCard.open) return; - // Игровое поле после открытия кликнутой карты - const nextCards = cards.map(card => { - if (card.id !== clickedCard.id) { - return card; - } - return { - ...card, - open: true, - }; - }); + const nextCards = cards.map(card => (card.id !== clickedCard.id ? card : { ...card, open: true })); setCards(nextCards); const isPlayerWon = nextCards.every(card => card.open); - - // Победа - все карты на поле открыты if (isPlayerWon) { finishGame(STATUS_WON); return; } - // Открытые карты на игровом поле const openCards = nextCards.filter(card => card.open); - - // Ищем открытые карты, у которых нет пары среди других открытых - const openCardsWithoutPair = openCards.filter(card => { - const sameCards = openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank); - return sameCards.length < 2; - }); + const openCardsWithoutPair = openCards.filter( + card => openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank).length < 2, + ); if (openCardsWithoutPair.length >= 2 && attempts > 0) { handleAttemptsChange(attempts - 1); setTimeout(() => { - const closeCards = cards.map(card => { - const shouldClose = openCardsWithoutPair.some(openCard => openCard.id === card.id); - - if (shouldClose) { - return { - ...card, - open: false, - }; - } - - return card; - }); - + const closeCards = cards.map(card => + openCardsWithoutPair.some(openCard => openCard.id === card.id) ? { ...card, open: false } : card, + ); setCards(closeCards); - }, 1000); // Таймаут 1 секунда + }, 1000); } - // "Игрок проиграл", т.к на поле есть две открытые карты без пары if (playerLost) { finishGame(STATUS_LOST); handleAttemptsChange(startLife); return; } + }; + + const alahomora = () => { + if (isAlahomoraUsed) { + return; + } - // ... игра продолжается + const closedCards = cards.filter(card => !card.open); + if (closedCards.length > 0) { + const randomIndex = Math.floor(Math.random() * closedCards.length); + const randomCard = closedCards[randomIndex]; + const pairCard = closedCards.find( + card => card.suit === randomCard.suit && card.rank === randomCard.rank && card.id !== randomCard.id, + ); + + if (pairCard) { + const nextCards = cards.map(card => { + if (card.id === randomCard.id || card.id === pairCard.id) { + return { ...card, open: true }; + } + return card; + }); + + setCards(nextCards); + handleIsAlahomora(true); + const allCardsOpen = nextCards.every(card => card.open); + if (allCardsOpen) { + finishGame(STATUS_WON); + } + } + } + }; + + const clairvoyance = () => { + if (isClairvoyanceUsed) { + return; + } + + const initiallyOpenCards = cards.filter(card => card.open); + const pairsOpenedCards = initiallyOpenCards.filter(card => + initiallyOpenCards.some( + openCard => openCard.id !== card.id && openCard.suit === card.suit && openCard.rank === card.rank, + ), + ); + + const nextCards = cards.map(card => (card.open ? card : { ...card, open: true })); + setCards(nextCards); + handleIsClairvoyance(true); + + const savedGameEndDate = gameEndDate; + const savedGameStartDate = gameStartDate; + const pauseStartDate = new Date(); + setGameEndDate(pauseStartDate); + + setTimeout(() => { + const pauseEndDate = new Date(); + const pauseDuration = pauseEndDate - pauseStartDate; + setGameStartDate(new Date(savedGameStartDate.getTime() + pauseDuration)); + setGameEndDate(savedGameEndDate); + + const finalCards = nextCards.map(card => + pairsOpenedCards.some(openCard => openCard.id === card.id) ? card : { ...card, open: false }, + ); + setCards(finalCards); + }, 5000); }; + useEffect(() => { + if (status !== STATUS_IN_PROGRESS) return; + + const intervalId = setInterval(() => { + setTimer(getTimerValue(gameStartDate, gameEndDate)); + }, 300); + + return () => clearInterval(intervalId); + }, [gameStartDate, gameEndDate, status]); + const isGameEnded = status === STATUS_LOST || status === STATUS_WON; - // Игровой цикл useEffect(() => { - // В статусах кроме превью доп логики не требуется - if (status !== STATUS_PREVIEW) { - return; - } + if (status !== STATUS_PREVIEW) return; - // В статусе превью мы if (pairsCount > 36) { alert("Столько пар сделать невозможно"); return; } - setCards(() => { - return shuffle(generateDeck(pairsCount, 10)); - }); + setCards(() => shuffle(generateDeck(pairsCount, 10))); const timerId = setTimeout(() => { startGame(); }, previewSeconds * 1000); - return () => { - clearTimeout(timerId); - }; + return () => clearTimeout(timerId); }, [status, pairsCount, previewSeconds]); - // Обновляем значение таймера в интервале useEffect(() => { const intervalId = setInterval(() => { setTimer(getTimerValue(gameStartDate, gameEndDate)); }, 300); - return () => { - clearInterval(intervalId); - }; + return () => clearInterval(intervalId); }, [gameStartDate, gameEndDate]); return ( @@ -225,7 +238,9 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { )}
- {status === STATUS_IN_PROGRESS ? : null} + {status === STATUS_IN_PROGRESS ? ( + + ) : null}
@@ -241,7 +256,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
- {isGameEnded ? ( + {isGameEnded && (
- ) : null} + )} ); } diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 32a7129ee..63c4c9c3e 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -7,15 +7,21 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { addLeader, getLeaderboard } from "../../api"; +const ACHIEVEMENTS = { + HARD_MODE: 1, + NO_SUPERPOWERS: 2, +}; + export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, hardGame }) { const title = isWon ? "Вы победили!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emoji" : "dead emoji"; - const { handleLeaderboardChange } = useCustomContext(); + const { handleLeaderboardChange, isAlahomoraUsed, isClairvoyanceUsed } = useCustomContext(); const [isHardMode, setIsHardMode] = useState(false); const resultTime = gameDurationMinutes * 60 + gameDurationSeconds; const [isInLeaderboard, setIsInLeaderboard] = useState(false); const [name, setName] = useState(""); + const [achievements, setAchievements] = useState([]); const navigate = useNavigate(); useEffect(() => { @@ -31,7 +37,6 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, const leadersTimes = leaders.map(leader => leader.time); const isTopTen = leadersTimes.length < 10 || resultTime < Math.max(...leadersTimes); setIsInLeaderboard(isTopTen); - console.log(leadersTimes); }) .catch(error => { console.error("Ошибка:", error); @@ -39,10 +44,23 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, } }, [isWon, resultTime, handleLeaderboardChange, isHardMode]); + useEffect(() => { + const earnedAchievements = []; + if (isHardMode) { + earnedAchievements.push(ACHIEVEMENTS.HARD_MODE); + } + + if (!isAlahomoraUsed && !isClairvoyanceUsed && isWon) { + earnedAchievements.push(ACHIEVEMENTS.NO_SUPERPOWERS); + } + + setAchievements(earnedAchievements); + }, [isHardMode, isAlahomoraUsed, isClairvoyanceUsed, isWon]); + const handleSaveResult = async () => { const playerName = name.trim() || "Пользователь"; try { - const updatedLeaderboard = await addLeader(playerName, resultTime); + const updatedLeaderboard = await addLeader(playerName, resultTime, achievements); handleLeaderboardChange(updatedLeaderboard.leaders); navigate("/leaderboard"); } catch (error) { diff --git a/src/components/Helps/Helps.jsx b/src/components/Helps/Helps.jsx new file mode 100644 index 000000000..9ca9c404d --- /dev/null +++ b/src/components/Helps/Helps.jsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Button } from "../Button/Button"; +import styles from "./Helps.module.css"; +import ItemWithDescription from "../ItemWithDescription/ItemWithDescription"; + +export function Helps({ resetGame, alahomora, clairvoyance }) { + const descriptions = () => { + return ( + <> +
+

Алохомора

+

Открывается случайная пара карт.

+
+ + ); + }; + + const descriptionCard = () => { + return ( + <> +
+

Прозрение

+

+ На 5 секунд показываются все карты. Таймер длительности игры на это время останавливается. +

+
+ + ); + }; + + return ( + <> +
+ + открыть карты + + + открыть карты + +
+ + + ); +} diff --git a/src/components/Helps/Helps.module.css b/src/components/Helps/Helps.module.css new file mode 100644 index 000000000..5b79021b5 --- /dev/null +++ b/src/components/Helps/Helps.module.css @@ -0,0 +1,22 @@ +.helps_container { + display: flex; + gap: 15px; + cursor: pointer; +} + +.helps_title { + font-family: StratosSkyeng, serif; + font-size: 18px; + font-weight: 700; + line-height: 24px; + text-align: center; +} + +.helps_text { + font-family: StratosSkyeng, serif; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + margin: 10px 20px 20px 20px; +} diff --git a/src/components/ItemWithDescription/ItemWithDescription.js b/src/components/ItemWithDescription/ItemWithDescription.js new file mode 100644 index 000000000..4dbe01f46 --- /dev/null +++ b/src/components/ItemWithDescription/ItemWithDescription.js @@ -0,0 +1,25 @@ +import React, { useState } from "react"; +import styles from "./ItemWithDescription.module.css"; + +const ItemWithDescription = ({ children, description }) => { + const [isHovered, setIsHovered] = useState(false); + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + return ( +
+ {isHovered &&
} +
+ {children} + {isHovered &&
{description}
} +
+
+ ); +}; + +export default ItemWithDescription; diff --git a/src/components/ItemWithDescription/ItemWithDescription.module.css b/src/components/ItemWithDescription/ItemWithDescription.module.css new file mode 100644 index 000000000..103ead715 --- /dev/null +++ b/src/components/ItemWithDescription/ItemWithDescription.module.css @@ -0,0 +1,37 @@ +.itemContainer { + position: relative; + display: inline-block; +} + +.description { + display: block; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background-color: #c2f5ff; + padding: 10px; + border: 1px solid #ccc; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 15; + white-space: wrap; + width: 222px; + border-radius: 12px; + color: #004980; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0, 73, 128, 0.5); + z-index: 5; + pointer-events: none; +} + +.content { + position: relative; + z-index: 10; +} diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx index d2911178d..f5f25a0fd 100644 --- a/src/components/LeaderboardBlock/LeaderboardBlock.jsx +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -2,6 +2,24 @@ import { useCustomContext } from "../../hooks/useCustomContext"; import styles from "../../pages/Leaderboard/Leaderboard.module.css"; import { Button } from "../Button/Button"; import { useNavigate } from "react-router-dom"; +import AchiveDescription from "../AchiveDescription/AchiveDescription"; +import React from "react"; + +const ACHIEVEMENTS = { + HARD_MODE: 1, + NO_SUPERPOWERS: 2, +}; + +const getAchievementIcon = (achievements, id, type) => { + switch (id) { + case ACHIEVEMENTS.HARD_MODE: + return type === "on" ? "frame_on" : "frame_off"; + case ACHIEVEMENTS.NO_SUPERPOWERS: + return type === "on" ? "magic_on" : "magic_off"; + default: + return ""; + } +}; export function LeaderboardBlock() { const { leaderboard } = useCustomContext(); @@ -18,29 +36,66 @@ export function LeaderboardBlock() { return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; }; + const descriptions = () => { + return ( +
+ Игра пройден в сложном режиме +
+ ); + }; + + const descriptionCard = () => { + return ( +
+ Игра пройдена без супер-сил +
+ ); + }; + return (
-

Лидерборд

+ Лидерборд
  • -

    Позиция

    -

    Пользователь

    -

    -

    Время

    + Позиция + Пользователь + Достижения + Время
  • {sortedLeaderboard.map((leader, index) => (
  • -

    {index + 1}

    -

    {leader.name}

    -

    -

    {formatTime(leader.time)}

    + {index + 1} + {leader.name} + + + {leader.achievements.includes(ACHIEVEMENTS.HARD_MODE) + + + {leader.achievements.includes(ACHIEVEMENTS.NO_SUPERPOWERS) + + + {formatTime(leader.time)}
  • ))} diff --git a/src/context/Context.jsx b/src/context/Context.jsx index 796570b77..693842f6b 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -18,12 +18,21 @@ export const ContextProvider = ({ children }) => { }); const [leaderboard, setLeaderboard] = useState([]); + const [isAlahomoraUsed, setIsAlahomoraUsed] = useState(false); + const [isClairvoyanceUsed, setIsClairvoyanceUsed] = useState(false); + + const handleIsAlahomora = useCallback(result => { + setIsAlahomoraUsed(result); + }, []); + + const handleIsClairvoyance = useCallback(result => { + setIsClairvoyanceUsed(result); + }, []); const handleLeaderboardChange = useCallback(leaders => { setLeaderboard(leaders); }, []); - // Инициализация hardGame из localStorage const [hardGame, setHardGame] = useState(() => { const savedHardGame = localStorage.getItem("hardGame"); return savedHardGame ? Number(savedHardGame) : null; @@ -82,6 +91,10 @@ export const ContextProvider = ({ children }) => { handleLeaderboardChange, handleHardGameChange, hardGame, + isAlahomoraUsed, + handleIsAlahomora, + isClairvoyanceUsed, + handleIsClairvoyance, }} > {children} From f4871e4f2570fd162bf329a38e22590de0690a22 Mon Sep 17 00:00:00 2001 From: Aleksei Erofeev Date: Sun, 15 Sep 2024 21:28:06 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=BA=D0=B2?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=83.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...veDescription.js => AchiveDescription.jsx} | 0 src/components/EndGameModal/EndGameModal.jsx | 6 +- .../LeaderboardBlock/LeaderboardBlock.jsx | 75 ++++++++++++++----- src/pages/Leaderboard/Leaderboard.module.css | 5 ++ 4 files changed, 64 insertions(+), 22 deletions(-) rename src/components/AchiveDescription/{AchiveDescription.js => AchiveDescription.jsx} (100%) diff --git a/src/components/AchiveDescription/AchiveDescription.js b/src/components/AchiveDescription/AchiveDescription.jsx similarity index 100% rename from src/components/AchiveDescription/AchiveDescription.js rename to src/components/AchiveDescription/AchiveDescription.jsx diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 63c4c9c3e..67a342e6c 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -46,7 +46,9 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, useEffect(() => { const earnedAchievements = []; - if (isHardMode) { + const easyGame = localStorage.getItem("isEasyMode"); + + if (easyGame !== "true" && isHardMode) { earnedAchievements.push(ACHIEVEMENTS.HARD_MODE); } @@ -58,7 +60,7 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, }, [isHardMode, isAlahomoraUsed, isClairvoyanceUsed, isWon]); const handleSaveResult = async () => { - const playerName = name.trim() || "Пользователь"; + const playerName = name.trim() || ""; try { const updatedLeaderboard = await addLeader(playerName, resultTime, achievements); handleLeaderboardChange(updatedLeaderboard.leaders); diff --git a/src/components/LeaderboardBlock/LeaderboardBlock.jsx b/src/components/LeaderboardBlock/LeaderboardBlock.jsx index f5f25a0fd..b5b03f76d 100644 --- a/src/components/LeaderboardBlock/LeaderboardBlock.jsx +++ b/src/components/LeaderboardBlock/LeaderboardBlock.jsx @@ -25,6 +25,7 @@ export function LeaderboardBlock() { const { leaderboard } = useCustomContext(); const navigate = useNavigate(); const sortedLeaderboard = leaderboard.sort((a, b) => a.time - b.time).slice(0, 10); + console.log(sortedLeaderboard); const startGame = () => { navigate("/"); @@ -74,26 +75,60 @@ export function LeaderboardBlock() { {index + 1} {leader.name} - - {leader.achievements.includes(ACHIEVEMENTS.HARD_MODE) - - - {leader.achievements.includes(ACHIEVEMENTS.NO_SUPERPOWERS) - + {leader.achievements.includes(ACHIEVEMENTS.HARD_MODE) ? ( + + { + + ) : ( +
    + { +
    + )} + {leader.achievements.includes(ACHIEVEMENTS.NO_SUPERPOWERS) ? ( + + { + + ) : ( +
    + { +
    + )}
    {formatTime(leader.time)}
diff --git a/src/pages/Leaderboard/Leaderboard.module.css b/src/pages/Leaderboard/Leaderboard.module.css index 3f158e6d9..d739b856c 100644 --- a/src/pages/Leaderboard/Leaderboard.module.css +++ b/src/pages/Leaderboard/Leaderboard.module.css @@ -46,3 +46,8 @@ color: #999999; margin: 16px 20px 16px 20px; } + +.achive { + position: absolute; + display: inline-block; +}