From 7e231e771794cac168d38daf494f4a8a01723ede Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 14:30:04 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8B=AC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Modal/Modal.tsx | 66 ++++++++ frontend/src/components/Modal/ModalContext.ts | 13 ++ .../src/components/Modal/ModalProvider.tsx | 26 ++++ frontend/src/components/Modal/index.ts | 2 + frontend/src/index.css | 146 +++++++++--------- frontend/src/main.tsx | 5 +- frontend/src/pages/ContestPage.tsx | 17 +- 7 files changed, 201 insertions(+), 74 deletions(-) create mode 100644 frontend/src/components/Modal/Modal.tsx create mode 100644 frontend/src/components/Modal/ModalContext.ts create mode 100644 frontend/src/components/Modal/ModalProvider.tsx create mode 100644 frontend/src/components/Modal/index.ts diff --git a/frontend/src/components/Modal/Modal.tsx b/frontend/src/components/Modal/Modal.tsx new file mode 100644 index 0000000..0061614 --- /dev/null +++ b/frontend/src/components/Modal/Modal.tsx @@ -0,0 +1,66 @@ +import { css, cx } from '@style/css'; + +import type { HTMLAttributes, MouseEvent } from 'react'; +import { useContext, useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom'; + +import { ModalContext } from './ModalContext'; + +interface Props extends HTMLAttributes {} + +export function Modal({ children, ...props }: Props) { + const modal = useContext(ModalContext); + const $dialog = useRef(null); + + const handleClickBackdrop = (e: MouseEvent) => { + const $target = e.target as HTMLDialogElement; + + if ($target.nodeName !== 'DIALOG') return; + + modal.close(); + }; + + useEffect(() => { + if (modal.isOpen) { + $dialog.current?.showModal(); + } else { + $dialog.current?.close(); + } + }, [modal.isOpen]); + + return ReactDOM.createPortal( + +
{children}
+
, + document.body, + ); +} + +const style = css({ + borderRadius: '0.5rem', +}); + +const dialogStyle = css({ + position: 'fixed', + left: '50%', + top: '50%', + transform: 'translate(-50%,-50%)', + width: '500px', + height: '400px', + _backdrop: { + background: 'rgba(00,00,00,0.5)', + backdropFilter: 'blur(1rem)', + }, +}); + +const contentStyle = css({ + width: '100%', + height: '100%', +}); diff --git a/frontend/src/components/Modal/ModalContext.ts b/frontend/src/components/Modal/ModalContext.ts new file mode 100644 index 0000000..aacb2fd --- /dev/null +++ b/frontend/src/components/Modal/ModalContext.ts @@ -0,0 +1,13 @@ +import { createContext } from 'react'; + +interface ModalContextProps { + isOpen: boolean; + close: () => void; + open: () => void; +} + +export const ModalContext = createContext({ + isOpen: false, + close: () => {}, + open: () => {}, +}); diff --git a/frontend/src/components/Modal/ModalProvider.tsx b/frontend/src/components/Modal/ModalProvider.tsx new file mode 100644 index 0000000..7938c1c --- /dev/null +++ b/frontend/src/components/Modal/ModalProvider.tsx @@ -0,0 +1,26 @@ +import type { ReactNode } from 'react'; +import { useState } from 'react'; + +import { ModalContext } from './ModalContext'; + +export function ModalProvider({ children }: { children: ReactNode }) { + const [isOpen, setIsOpen] = useState(false); + const close = () => { + setIsOpen(false); + }; + const open = () => { + setIsOpen(true); + }; + + return ( + + {children} + + ); +} diff --git a/frontend/src/components/Modal/index.ts b/frontend/src/components/Modal/index.ts new file mode 100644 index 0000000..a84bf4d --- /dev/null +++ b/frontend/src/components/Modal/index.ts @@ -0,0 +1,2 @@ +export { Modal as default } from './Modal'; +export { ModalProvider } from './ModalProvider'; diff --git a/frontend/src/index.css b/frontend/src/index.css index b2b6068..6171fff 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,71 +1,75 @@ -@layer reset, base, tokens, recipes, utilities; - -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +@layer reset, base, tokens, recipes, utilities; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +html:has(dialog[open]) { + overflow: hidden; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f008af4..911886e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,12 +5,15 @@ import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; import AuthProvider from './components/Auth/AuthProvider'; +import { ModalProvider } from './components/Modal/ModalProvider'; import router from './router'; ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ); diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 2e3bb3c..8617e4d 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -1,10 +1,12 @@ import { css } from '@style/css'; -import { useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import Editor from '@/components/Editor/Editor'; +import Modal from '@/components/Modal'; +import { ModalContext } from '@/components/Modal/ModalContext'; import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; @@ -24,6 +26,7 @@ export default function ContestPage() { const { id } = useParams<{ id: string }>(); const competitionId: number = id ? parseInt(id, 10) : -1; const [currentProblemIndex, setCurrentProblemIndex] = useState(0); + const modal = useContext(ModalContext); const { simulationInputs, @@ -89,6 +92,10 @@ export default function ContestPage() { submitSolution(form); } + function handleOpenModal() { + modal.open(); + } + return (
@@ -118,8 +125,14 @@ export default function ContestPage() {
- + +
+ Hello World
); } From ff6eeff5276ea863a2f798fbf22728382f149798 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 15:54:34 +0900 Subject: [PATCH 02/10] =?UTF-8?q?refactor:=20Modal=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=8B=9C=20Modal.Provider=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/{ => Common}/Modal/Modal.tsx | 4 ++++ frontend/src/components/{ => Common}/Modal/ModalContext.ts | 0 .../src/components/{ => Common}/Modal/ModalProvider.tsx | 0 frontend/src/components/Common/Modal/index.ts | 1 + frontend/src/components/Common/index.ts | 1 + frontend/src/components/Modal/index.ts | 2 -- frontend/src/main.tsx | 6 +++--- frontend/src/pages/ContestPage.tsx | 4 ++-- 8 files changed, 11 insertions(+), 7 deletions(-) rename frontend/src/components/{ => Common}/Modal/Modal.tsx (92%) rename frontend/src/components/{ => Common}/Modal/ModalContext.ts (100%) rename frontend/src/components/{ => Common}/Modal/ModalProvider.tsx (100%) create mode 100644 frontend/src/components/Common/Modal/index.ts delete mode 100644 frontend/src/components/Modal/index.ts diff --git a/frontend/src/components/Modal/Modal.tsx b/frontend/src/components/Common/Modal/Modal.tsx similarity index 92% rename from frontend/src/components/Modal/Modal.tsx rename to frontend/src/components/Common/Modal/Modal.tsx index 0061614..fddc99e 100644 --- a/frontend/src/components/Modal/Modal.tsx +++ b/frontend/src/components/Common/Modal/Modal.tsx @@ -5,6 +5,7 @@ import { useContext, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; import { ModalContext } from './ModalContext'; +import { ModalProvider } from './ModalProvider'; interface Props extends HTMLAttributes {} @@ -43,6 +44,9 @@ export function Modal({ children, ...props }: Props) { ); } +Modal.Context = ModalContext; +Modal.Provider = ModalProvider; + const style = css({ borderRadius: '0.5rem', }); diff --git a/frontend/src/components/Modal/ModalContext.ts b/frontend/src/components/Common/Modal/ModalContext.ts similarity index 100% rename from frontend/src/components/Modal/ModalContext.ts rename to frontend/src/components/Common/Modal/ModalContext.ts diff --git a/frontend/src/components/Modal/ModalProvider.tsx b/frontend/src/components/Common/Modal/ModalProvider.tsx similarity index 100% rename from frontend/src/components/Modal/ModalProvider.tsx rename to frontend/src/components/Common/Modal/ModalProvider.tsx diff --git a/frontend/src/components/Common/Modal/index.ts b/frontend/src/components/Common/Modal/index.ts new file mode 100644 index 0000000..48f35c7 --- /dev/null +++ b/frontend/src/components/Common/Modal/index.ts @@ -0,0 +1 @@ +export { Modal as default } from './Modal'; diff --git a/frontend/src/components/Common/index.ts b/frontend/src/components/Common/index.ts index 6322cf3..8694f5b 100644 --- a/frontend/src/components/Common/index.ts +++ b/frontend/src/components/Common/index.ts @@ -1 +1,2 @@ export { Input } from './Input'; +export { default as Modal } from './Modal'; diff --git a/frontend/src/components/Modal/index.ts b/frontend/src/components/Modal/index.ts deleted file mode 100644 index a84bf4d..0000000 --- a/frontend/src/components/Modal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Modal as default } from './Modal'; -export { ModalProvider } from './ModalProvider'; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 911886e..d67a5ef 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,15 +5,15 @@ import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; import AuthProvider from './components/Auth/AuthProvider'; -import { ModalProvider } from './components/Modal/ModalProvider'; +import { Modal } from './components/Common'; import router from './router'; ReactDOM.createRoot(document.getElementById('root')!).render( - + - + , ); diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 8617e4d..fcf38ae 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -3,10 +3,10 @@ import { css } from '@style/css'; import { useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; +import Modal from '@/components/Common/Modal'; +import { ModalContext } from '@/components/Common/Modal/ModalContext'; import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import Editor from '@/components/Editor/Editor'; -import Modal from '@/components/Modal'; -import { ModalContext } from '@/components/Modal/ModalContext'; import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; From ccbdb44676bc01366e5c88cf4aba18f1ecad2868 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 15:57:35 +0900 Subject: [PATCH 03/10] =?UTF-8?q?update:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=85=EB=A0=A5=EC=9D=84=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ContestPage.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index fcf38ae..8d5c093 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -107,10 +107,6 @@ export default function ContestPage() {
- {isSimulating ? ( - Hello World + + + ); } From 4ac12974cf916fa52fee19806199f1473e862b42 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 16:41:25 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20Simulation=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=AA=A8=EB=8B=AC=EC=9D=84=20SimulationInputModal?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Common/Modal/Modal.tsx | 2 +- frontend/src/components/Common/Modal/index.ts | 3 +- frontend/src/components/Common/index.ts | 2 +- .../Simulation/SimulationInputModal.tsx | 29 +++++++++++++++++++ frontend/src/pages/ContestPage.tsx | 13 ++++----- 5 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/Simulation/SimulationInputModal.tsx diff --git a/frontend/src/components/Common/Modal/Modal.tsx b/frontend/src/components/Common/Modal/Modal.tsx index fddc99e..2b25214 100644 --- a/frontend/src/components/Common/Modal/Modal.tsx +++ b/frontend/src/components/Common/Modal/Modal.tsx @@ -7,7 +7,7 @@ import ReactDOM from 'react-dom'; import { ModalContext } from './ModalContext'; import { ModalProvider } from './ModalProvider'; -interface Props extends HTMLAttributes {} +export interface Props extends HTMLAttributes {} export function Modal({ children, ...props }: Props) { const modal = useContext(ModalContext); diff --git a/frontend/src/components/Common/Modal/index.ts b/frontend/src/components/Common/Modal/index.ts index 48f35c7..ef6a639 100644 --- a/frontend/src/components/Common/Modal/index.ts +++ b/frontend/src/components/Common/Modal/index.ts @@ -1 +1,2 @@ -export { Modal as default } from './Modal'; +export { Modal } from './Modal'; +export type { Props as ModalProps } from './Modal'; diff --git a/frontend/src/components/Common/index.ts b/frontend/src/components/Common/index.ts index 8694f5b..7aa1ae9 100644 --- a/frontend/src/components/Common/index.ts +++ b/frontend/src/components/Common/index.ts @@ -1,2 +1,2 @@ export { Input } from './Input'; -export { default as Modal } from './Modal'; +export * from './Modal'; diff --git a/frontend/src/components/Simulation/SimulationInputModal.tsx b/frontend/src/components/Simulation/SimulationInputModal.tsx new file mode 100644 index 0000000..0d33cb4 --- /dev/null +++ b/frontend/src/components/Simulation/SimulationInputModal.tsx @@ -0,0 +1,29 @@ +import { useContext } from 'react'; + +import type { SimulationInput } from '@/hooks/simulation'; + +import { Modal, type ModalProps } from '../Common'; +import { SimulationInputList } from './SimulationInputList'; + +interface Props extends ModalProps { + simulationInputs: SimulationInput[]; + onChangeInput: (id: number, newInput: string) => void; +} + +export function SimulationInputModal({ simulationInputs, onChangeInput, ...props }: Props) { + const modal = useContext(Modal.Context); + + const handleCloseModal = () => { + modal.close(); + }; + + return ( + + + + + ); +} diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 8d5c093..f21bfe8 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -3,12 +3,11 @@ import { css } from '@style/css'; import { useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; -import Modal from '@/components/Common/Modal'; import { ModalContext } from '@/components/Common/Modal/ModalContext'; import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import Editor from '@/components/Editor/Editor'; import ProblemViewer from '@/components/Problem/ProblemViewer'; -import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; +import { SimulationInputModal } from '@/components/Simulation/SimulationInputModal'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; import { SubmissionResult } from '@/components/Submission'; import { SITE } from '@/constants'; @@ -128,12 +127,10 @@ export default function ContestPage() { 테스트 케이스 추가하기 - - - + ); } From 0fb46c5607d20e59ac582f0c4dcd64ae60d0b695 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 16:48:30 +0900 Subject: [PATCH 05/10] =?UTF-8?q?update:=20=EB=AA=A8=EB=8B=AC=EC=9D=98=20?= =?UTF-8?q?=EB=B0=B1=EB=93=9C=EB=A1=AD=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20?= =?UTF-8?q?=EB=8B=AB=ED=9E=98=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Common/Modal/Modal.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Common/Modal/Modal.tsx b/frontend/src/components/Common/Modal/Modal.tsx index 2b25214..d3937be 100644 --- a/frontend/src/components/Common/Modal/Modal.tsx +++ b/frontend/src/components/Common/Modal/Modal.tsx @@ -7,9 +7,11 @@ import ReactDOM from 'react-dom'; import { ModalContext } from './ModalContext'; import { ModalProvider } from './ModalProvider'; -export interface Props extends HTMLAttributes {} +export interface Props extends HTMLAttributes { + onBackdropPressed?: () => void; +} -export function Modal({ children, ...props }: Props) { +export function Modal({ onBackdropPressed, children, ...props }: Props) { const modal = useContext(ModalContext); const $dialog = useRef(null); @@ -18,7 +20,9 @@ export function Modal({ children, ...props }: Props) { if ($target.nodeName !== 'DIALOG') return; - modal.close(); + if (onBackdropPressed instanceof Function) { + onBackdropPressed(); + } }; useEffect(() => { From 8497c60952020d80b3203dd33cfa9c5cc54aa936 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 17:28:26 +0900 Subject: [PATCH 06/10] =?UTF-8?q?update:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=A0=80=EC=9E=A5=EC=9D=84=20=EB=88=8C?= =?UTF-8?q?=EB=9F=AC=EC=95=BC=20=EC=A0=81=EC=9A=A9=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Simulation/SimulationInputModal.tsx | 27 +++++++++++++++---- .../src/hooks/simulation/useSimulations.ts | 9 +++---- frontend/src/pages/ContestPage.tsx | 10 +++---- .../src/utils/copy/__tests__/copy.spec.ts | 22 +++++++++++++++ frontend/src/utils/copy/index.ts | 3 +++ 5 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 frontend/src/utils/copy/__tests__/copy.spec.ts create mode 100644 frontend/src/utils/copy/index.ts diff --git a/frontend/src/components/Simulation/SimulationInputModal.tsx b/frontend/src/components/Simulation/SimulationInputModal.tsx index 0d33cb4..0a1a22a 100644 --- a/frontend/src/components/Simulation/SimulationInputModal.tsx +++ b/frontend/src/components/Simulation/SimulationInputModal.tsx @@ -1,29 +1,46 @@ -import { useContext } from 'react'; +import { useContext, useState } from 'react'; import type { SimulationInput } from '@/hooks/simulation'; +import { deepCopy } from '@/utils/copy'; import { Modal, type ModalProps } from '../Common'; import { SimulationInputList } from './SimulationInputList'; interface Props extends ModalProps { simulationInputs: SimulationInput[]; - onChangeInput: (id: number, newInput: string) => void; + onSave: (inputs: SimulationInput[]) => void; } -export function SimulationInputModal({ simulationInputs, onChangeInput, ...props }: Props) { +export function SimulationInputModal({ simulationInputs, onSave, ...props }: Props) { const modal = useContext(Modal.Context); + const [inputs, setInputs] = useState(deepCopy(simulationInputs)); const handleCloseModal = () => { + setInputs(simulationInputs); modal.close(); }; + const handleSave = () => { + onSave(deepCopy(inputs)); + modal.close(); + }; + + const handleChangeInput = (targetId: number, newParam: string) => { + const changedSimulation = inputs.find(({ id }) => id === targetId); + if (changedSimulation) { + changedSimulation.input = newParam; + } + setInputs([...inputs]); + }; + return ( + ); } diff --git a/frontend/src/hooks/simulation/useSimulations.ts b/frontend/src/hooks/simulation/useSimulations.ts index 60d6124..ea773aa 100644 --- a/frontend/src/hooks/simulation/useSimulations.ts +++ b/frontend/src/hooks/simulation/useSimulations.ts @@ -69,11 +69,8 @@ export const useSimulations = () => { }); } - function changeInput(targetId: number, newParam: string) { - const changedSimulation = simulationInputs.find(({ id }) => id === targetId); - if (changedSimulation) { - changedSimulation.input = newParam; - } + function changeInputs(simulationInputs: SimulationInput[]) { + console.log(simulationInputs); setSimulationInputs([...simulationInputs]); } @@ -87,7 +84,7 @@ export const useSimulations = () => { isSimulating, runSimulation, cancelSimulation, - changeInput, + changeInputs, }; }; diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index f21bfe8..a0f6e94 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -15,7 +15,7 @@ import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; import { useCompetitionProblem } from '@/hooks/problem'; import { useCompetitionProblemList } from '@/hooks/problem/useCompetitionProblemList'; -import { useSimulations } from '@/hooks/simulation'; +import { SimulationInput, useSimulations } from '@/hooks/simulation'; import { isNil } from '@/utils/type'; const RUN_SIMULATION = '테스트 실행'; @@ -32,7 +32,7 @@ export default function ContestPage() { simulationResults, isSimulating, runSimulation, - changeInput, + changeInputs, cancelSimulation, } = useSimulations(); @@ -68,8 +68,8 @@ export default function ContestPage() { cancelSimulation(); }; - const handleChangeInput = (id: number, newParam: string) => { - changeInput(id, newParam); + const handleSaveSimulationInputs = (simulationInputs: SimulationInput[]) => { + changeInputs(simulationInputs); }; const handleNextProblem = () => { @@ -129,7 +129,7 @@ export default function ContestPage() { ); diff --git a/frontend/src/utils/copy/__tests__/copy.spec.ts b/frontend/src/utils/copy/__tests__/copy.spec.ts new file mode 100644 index 0000000..babd088 --- /dev/null +++ b/frontend/src/utils/copy/__tests__/copy.spec.ts @@ -0,0 +1,22 @@ +import { deepCopy } from '../index'; +import { describe, expect, it } from 'vitest'; + +describe('deepCopy', () => { + it('deepCopy는 객체를 깊은 복사한다.', () => { + const obj = { + inner: { + a: 1, + }, + }; + expect(deepCopy(obj)).not.equal(obj); + expect(deepCopy(obj).inner).not.equal(obj.inner); + }); + + it('deepCopy는 배열을 깊은 복사한다.', () => { + const obj = { inner: { a: 1 } }; + const arr = [obj]; + expect(deepCopy(arr)).not.equal(arr); + expect(deepCopy(arr)[0]).not.equal(arr[0]); + expect(deepCopy(arr)[0].inner).not.equal(obj.inner); + }); +}); diff --git a/frontend/src/utils/copy/index.ts b/frontend/src/utils/copy/index.ts new file mode 100644 index 0000000..f016107 --- /dev/null +++ b/frontend/src/utils/copy/index.ts @@ -0,0 +1,3 @@ +export function deepCopy(value: T): T { + return JSON.parse(JSON.stringify(value)); +} From cb8c4b10aea9aaad525e2ab8123be6222e2ff123 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 17:29:36 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20=EB=94=94=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/simulation/useSimulations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/hooks/simulation/useSimulations.ts b/frontend/src/hooks/simulation/useSimulations.ts index ea773aa..552263b 100644 --- a/frontend/src/hooks/simulation/useSimulations.ts +++ b/frontend/src/hooks/simulation/useSimulations.ts @@ -70,7 +70,6 @@ export const useSimulations = () => { } function changeInputs(simulationInputs: SimulationInput[]) { - console.log(simulationInputs); setSimulationInputs([...simulationInputs]); } From c5bba50c2f2cf5277afa864c1ef428166d50f397 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 17:35:58 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20useSimulations=20->=20useSimu?= =?UTF-8?q?lation,=20=EC=82=AC=EC=9A=A9=20=EC=BD=94=EB=93=9C=20=EC=95=95?= =?UTF-8?q?=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/simulation/index.ts | 2 +- .../{useSimulations.ts => useSimulation.ts} | 58 +++++++++---------- frontend/src/pages/ContestPage.tsx | 23 +++----- 3 files changed, 37 insertions(+), 46 deletions(-) rename frontend/src/hooks/simulation/{useSimulations.ts => useSimulation.ts} (51%) diff --git a/frontend/src/hooks/simulation/index.ts b/frontend/src/hooks/simulation/index.ts index 8b89797..52b61a5 100644 --- a/frontend/src/hooks/simulation/index.ts +++ b/frontend/src/hooks/simulation/index.ts @@ -1,2 +1,2 @@ -export * from './useSimulations'; +export * from './useSimulation'; export * from './types'; diff --git a/frontend/src/hooks/simulation/useSimulations.ts b/frontend/src/hooks/simulation/useSimulation.ts similarity index 51% rename from frontend/src/hooks/simulation/useSimulations.ts rename to frontend/src/hooks/simulation/useSimulation.ts index 552263b..06915bd 100644 --- a/frontend/src/hooks/simulation/useSimulations.ts +++ b/frontend/src/hooks/simulation/useSimulation.ts @@ -4,54 +4,52 @@ import evaluator from '@/modules/evaluator'; import type { SimulationInput, SimulationResult } from './types'; -export const useSimulations = () => { - const [simulationInputs, setSimulationInputs] = useState([ +export const useSimulation = () => { + const [inputs, setInputs] = useState([ { id: 1, input: '' }, { id: 2, input: '' }, { id: 3, input: '' }, { id: 4, input: '' }, { id: 5, input: '' }, ]); - const [simulationResults, setSimulationResults] = useState([ + const [results, setResults] = useState([ { id: 1, isDone: true, input: '', output: '' }, { id: 2, isDone: true, input: '', output: '' }, { id: 3, isDone: true, input: '', output: '' }, { id: 4, isDone: true, input: '', output: '' }, { id: 5, isDone: true, input: '', output: '' }, ]); - const isSimulating = useMemo(() => { - return simulationResults.some((result) => !result.isDone); - }, [simulationResults]); + const isRunning = useMemo(() => { + return results.some((result) => !result.isDone); + }, [results]); useEffect(() => { - return evaluator.subscribe(({ result, error, task }) => { + return evaluator.subscribe(({ result: output, error, task }) => { if (!task) return; - setSimulationResults((simulations) => { - return simulations.map((simul) => { - if (simul.id !== task.clientId) return simul; + setResults((results) => { + return results.map((result) => { + if (result.id !== task.clientId) return result; if (error) { return { - ...simul, + ...result, isDone: true, output: `${error.name}: ${error.message} \n${error.stack}`, }; } return { - ...simul, + ...result, isDone: true, - output: result, + output, }; }); }); }); }, []); - function runSimulation(code: string) { - const tasks = simulationInputs.map(({ id, input }) => - evaluator.createEvalMessage(id, code, input), - ); + function run(code: string) { + const tasks = inputs.map(({ id, input }) => evaluator.createEvalMessage(id, code, input)); const isRequestSuccess = evaluator.evaluate(tasks); @@ -59,30 +57,30 @@ export const useSimulations = () => { return; } - setSimulationResults((simulResults) => { - return simulResults - .map((simul, index) => ({ - ...simul, - input: simulationInputs[index].input, + setResults((results) => { + return results + .map((result, index) => ({ + ...result, + input: inputs[index].input, })) .map(toEvaluatingState); }); } - function changeInputs(simulationInputs: SimulationInput[]) { - setSimulationInputs([...simulationInputs]); + function changeInputs(inputs: SimulationInput[]) { + setInputs([...inputs]); } - function cancelSimulation() { + function cancel() { evaluator.cancelEvaluation(); } return { - simulationInputs, - simulationResults, - isSimulating, - runSimulation, - cancelSimulation, + inputs, + results, + isRunning, + run, + cancel, changeInputs, }; }; diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index a0f6e94..1d72fed 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -15,7 +15,7 @@ import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; import { useCompetitionProblem } from '@/hooks/problem'; import { useCompetitionProblemList } from '@/hooks/problem/useCompetitionProblemList'; -import { SimulationInput, useSimulations } from '@/hooks/simulation'; +import { SimulationInput, useSimulation } from '@/hooks/simulation'; import { isNil } from '@/utils/type'; const RUN_SIMULATION = '테스트 실행'; @@ -27,14 +27,7 @@ export default function ContestPage() { const [currentProblemIndex, setCurrentProblemIndex] = useState(0); const modal = useContext(ModalContext); - const { - simulationInputs, - simulationResults, - isSimulating, - runSimulation, - changeInputs, - cancelSimulation, - } = useSimulations(); + const simulation = useSimulation(); const { socket, competition, submitSolution } = useCompetition(competitionId); const { problemList } = useCompetitionProblemList(competitionId); @@ -61,15 +54,15 @@ export default function ContestPage() { }; const handleSimulate = () => { - runSimulation(code); + simulation.run(code); }; const handleSimulationCancel = () => { - cancelSimulation(); + simulation.cancel(); }; const handleSaveSimulationInputs = (simulationInputs: SimulationInput[]) => { - changeInputs(simulationInputs); + simulation.changeInputs(simulationInputs); }; const handleNextProblem = () => { @@ -106,8 +99,8 @@ export default function ContestPage() {
- - {isSimulating ? ( + + {simulation.isRunning ? ( @@ -128,7 +121,7 @@ export default function ContestPage() { From 121ca30dc5f59e155404d50c86d71352ebf4d480 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 18:09:31 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20deepCopy=EC=97=90=20structure?= =?UTF-8?q?dClone=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/copy/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/copy/index.ts b/frontend/src/utils/copy/index.ts index f016107..0450899 100644 --- a/frontend/src/utils/copy/index.ts +++ b/frontend/src/utils/copy/index.ts @@ -1,3 +1,3 @@ export function deepCopy(value: T): T { - return JSON.parse(JSON.stringify(value)); + return structuredClone(value); } From 3cd7413504c222d544bd2df15e907b5f54dd70f3 Mon Sep 17 00:00:00 2001 From: dev2820 Date: Tue, 28 Nov 2023 18:15:29 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ContestPage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index ece415b..5352823 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -4,7 +4,6 @@ import { useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { ModalContext } from '@/components/Common/Modal/ModalContext'; -import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import CompetitionHeader from '@/components/Contest/CompetitionHeader'; import ContestProblemSelector from '@/components/Contest/ContestProblemSelector'; import Editor from '@/components/Editor/Editor'; @@ -85,7 +84,7 @@ export default function ContestPage() { function handleOpenModal() { modal.open(); } - + const problems = problemList.map((problem) => problem.id); return (