Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Tutorial 및 Block Tab Menu #33

Merged
merged 4 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/assets/tutorial/01.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/tutorial/02.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/assets/tutorial/03.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/assets/tutorial/04.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/assets/tutorial/05.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 52 additions & 7 deletions src/components/BlockContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,92 @@
import { useState, useEffect } from "react";

import useStore from "../store";

import InputBlock from "./common/InputBlock";
import MethodBlock from "./common/MethodBlock";

import { fetchBlocks } from "../services/blocks";

import {
Content,
Tab,
TabList,
BlockList,
InputBlockList,
MethodBlockList,
} from "../style/BlockContainerStyle";
import { Header, Section, Content } from "../style/CommonStyle";
import { Header, Section } from "../style/CommonStyle";

const BlockContainer = () => {
const { handleBlockDragStart, handleBlockDragOver, handleDeleteBlock } =
useStore();
const [inputBlocks, setInputBlocks] = useState([]);
const [methodBlocks, setMethodBlocks] = useState([]);
const [activeTab, setActiveTab] = useState("All");
const [actions, setActions] = useState(["All"]);

useEffect(() => {
const renderBlocks = async () => {
try {
const blocks = await fetchBlocks();

setInputBlocks(blocks.inputBlocks);
setMethodBlocks(blocks.methodBlocks);

const allActions = new Set(["All"]);

const updateAction = (allBlocks) => {
allBlocks.forEach((block) =>
block.actions.forEach((action) => allActions.add(action)),
);
};

[blocks.inputBlocks, blocks.methodBlocks].forEach(updateAction);

setActions([...allActions]);
} catch (error) {
console.error("Fetch Error");
}
};
Comment on lines 25 to 46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드의 가독성과 함수 재사용성을 고려했을 때 useEffect 외부로 분리해주는 것도 좋을 것 같아요! 물론, 아직은 다른 곳에서 renderBlocks가 사용되고있지는 않지만, 별도로 분리한다면 코드의 가독성이 향상될 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다. 기연님 의견 반영하여 전체적인 로직을 리팩토링할 때, useEffect를 외부로 분리하는 작업을 이슈로 생성하여 추후에 한 번에 진행하겠습니다! #36

renderBlocks();
}, []);

const filterBlocksByAction = (blocks, action) => {
return blocks.filter((block) => block.actions.includes(action));
};

const filterBlocks = (action) => {
if (action === "All") {
return { inputBlocks, methodBlocks };
}

return {
inputBlocks: filterBlocksByAction(inputBlocks, action),
methodBlocks: filterBlocksByAction(methodBlocks, action),
};
};

const {
inputBlocks: filteredInputBlocks,
methodBlocks: filteredMethodBlocks,
} = filterBlocks(activeTab);

return (
<Section onDragOver={handleBlockDragOver} onDrop={handleDeleteBlock}>
<Header>
<h2>Block Container</h2>
</Header>
<Content>
<TabList>
{actions.map((action) => (
<Tab
key={action}
data-active={activeTab === action}
onClick={() => setActiveTab(action)}
>
{action}
</Tab>
))}
</TabList>
<BlockList>
<InputBlockList>
{inputBlocks.map((inputBlock) => (
{filteredInputBlocks.map((inputBlock) => (
<InputBlock
key={inputBlock._id}
parameter={inputBlock.parameter}
Expand All @@ -50,7 +95,7 @@ const BlockContainer = () => {
))}
</InputBlockList>
<MethodBlockList>
{methodBlocks.map((methodBlock) => (
{filteredMethodBlocks.map((methodBlock) => (
<MethodBlock
key={methodBlock._id}
method={methodBlock.method}
Expand Down
67 changes: 67 additions & 0 deletions src/components/common/Tutorial.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from "react";

import Button from "./Button";

import {
ModalBackground,
ModalContainer,
ModalContent,
} from "../../style/ModalStyle";
import { Header, ButtonContainer } from "../../style/CommonStyle";
import { ImageContainer, ContentContainer } from "../../style/TutorialStyle";

const TutorialModal = ({ title, tutorials, onClose }) => {
const [currentIndex, setCurrentIndex] = useState(0);

const handleNextButton = () => {
if (currentIndex < tutorials.length - 1) {
setCurrentIndex(currentIndex + 1);
} else {
onClose();
}
};

const handlePreviousButton = () => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};

const currentTutorial = tutorials[currentIndex];

return (
<ModalBackground>
<ModalContainer>
<Header>
<h2>{title}</h2>
</Header>
<ModalContent>
<ImageContainer>
<img
src={currentTutorial.image}
alt={`Tutorial step ${currentIndex + 1}`}
/>
</ImageContainer>
<ContentContainer>
{currentTutorial.content.split(".").join(".\n")}
</ContentContainer>
<ButtonContainer>
<Button
type="text"
text="prev"
handleClick={handlePreviousButton}
disabled={currentIndex === 0}
/>
<Button
type="text"
text={currentIndex === tutorials.length - 1 ? "start" : "next"}
handleClick={handleNextButton}
/>
</ButtonContainer>
</ModalContent>
</ModalContainer>
</ModalBackground>
);
};

export default TutorialModal;
39 changes: 39 additions & 0 deletions src/pages/Main.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
import { useState } from "react";

import useStore from "../store";

import BlockContainer from "../components/BlockContainer";
import BlockDashboard from "../components/BlockDashboard";
import TestCodeDashboard from "../components/TestCodeDashboard";
import Tutorial from "../components/common/Tutorial";

import firstStep from "../assets/tutorial/01.svg";
import secondStep from "../assets/tutorial/02.svg";
import thirdStep from "../assets/tutorial/03.svg";
import fourthStep from "../assets/tutorial/04.svg";
import fifthStep from "../assets/tutorial/05.svg";

const Main = () => {
const { handleKeyDown } = useStore();
const [showTutorial, setShowTutorial] = useState(true);

const tutorials = [
{
image: firstStep,
content:
"왼쪽의 회색 블록에는 사용자가 원하는 값을 입력할 수 있습니다.오른쪽의 남색 블록으로 사용자가 원하는 동작을 선택할 수 있습니다.",
},
{
image: secondStep,
content: "Block Dashboard로 블록을 드래그할 수 있습니다.",
},
{
image: thirdStep,
content:
"하나의 라인 블록에는 최소 하나의 남색 블록이 포함되어야 합니다.",
},
{ image: fourthStep, content: "reset 버튼을 누르면 블록이 초기화됩니다." },
{
image: fifthStep,
content: "create 버튼을 누르면 테스트 코드가 생성됩니다.",
},
];

return (
<main onKeyDown={handleKeyDown} tabIndex="0">
<BlockContainer />
<BlockDashboard />
<TestCodeDashboard />
{showTutorial && (
<Tutorial
title="Tutorial"
tutorials={tutorials}
onClose={() => setShowTutorial(false)}
/>
)}
</main>
);
};
Expand Down
51 changes: 49 additions & 2 deletions src/style/BlockContainerStyle.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,56 @@
import styled from "styled-components";

const Content = styled.div`
display: flex;
flex-direction: column;
align-items: center;
position: relative;
height: 100vh;
overflow: scroll;
overflow-x: auto;
`;

const TabList = styled.nav`
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
height: 100%;
padding: 2rem 0.5rem;
background-color: transparent;
gap: 2rem;

@media (max-width: 1120px) {
position: static;
top: unset;
left: unset;
}
`;

const Tab = styled.button`
border: none;
background: ${({ theme }) => theme.color.lightGrayColor};
color: ${({ theme, "data-active": active }) =>
active ? theme.color.whiteColor : theme.color.blackColor};
font-size: ${({ theme }) => theme.fontSize.xsmall};
text-align: left;
cursor: pointer;

&:hover {
background-color: ${({ theme }) => theme.color.grayColor};
}

&:focus {
outline: none;
}
`;

const BlockList = styled.ul`
display: flex;
flex-direction: column;
gap: 2rem;
margin: 4rem;
padding: 2rem;
`;

const InputBlockList = styled.ul`
Expand All @@ -18,4 +65,4 @@ const MethodBlockList = styled.ul`
gap: 1rem;
`;

export { BlockList, InputBlockList, MethodBlockList };
export { Content, Tab, TabList, BlockList, InputBlockList, MethodBlockList };
20 changes: 20 additions & 0 deletions src/style/TutorialStyle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from "styled-components";

const ImageContainer = styled.div`
display: flex;
gap: 4rem;
width: 50rem;
height: 20rem;

img {
width: 50rem;
}
`;

const ContentContainer = styled.pre`
display: flex;
line-height: 2rem;
font-weight: bold;
`;

export { ImageContainer, ContentContainer };