Skip to content

Commit

Permalink
feat: question creation module (#125)
Browse files Browse the repository at this point in the history
* install dnd package & add context provider

* remove column component

* make question element draggable

* add valid drop area

* preliminary text input component w/o styling

* page formatting

* cleanup

* render question elements

* add question state

* add on drop behaviour

* removing navbar

* making left panel fixed and right panel scrollable

* style text element component

* cursor behaviour

* move types

* Rename QuestionPage.tsx to CreateQuestionPage.tsx

* Rename CreateQuestionPage.tsx to QuestionPage.tsx

* moving page

* refactor

* increasing icon size

* fix multiline text issue

* fix window resizing issue

* error handling

---------

Co-authored-by: joyce-shi <j224shi@git.uwaterloo.ca>
  • Loading branch information
joyce-shi and joyce-shi committed Feb 24, 2023
1 parent 238d8a5 commit 04b65a1
Show file tree
Hide file tree
Showing 19 changed files with 395 additions and 111 deletions.
3 changes: 3 additions & 0 deletions frontend/package.json
Expand Up @@ -36,6 +36,8 @@
"jsonwebtoken": "^8.5.1",
"react": "^18.2.0",
"react-bootstrap": "^1.5.2",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.40.0",
"react-json-schema": "^1.2.2",
Expand All @@ -44,6 +46,7 @@
"react-scripts": "4.0.2",
"react-select-country-list": "^2.2.3",
"react-table": "^7.7.0",
"react-textarea-autosize": "^8.4.0",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
Expand Down
27 changes: 0 additions & 27 deletions frontend/src/assets/icons.tsx
Expand Up @@ -75,33 +75,6 @@ export const PlusOutlineIcon = (): React.ReactElement => (
</Icon>
);

export const ColumnIcon = (): React.ReactElement => (
<Icon viewBox="0 0 88 88" boxSize="5em">
<rect opacity="0.2" width="88" height="88" fill="#A1B4C7" />
<rect
x="45.5"
y="79.5"
width="67"
height="19"
transform="rotate(-90 45.5 79.5)"
fill="#C4C4C4"
fillOpacity="0.2"
stroke="black"
strokeDasharray="2 2"
/>
<rect
x="21.5"
y="79.5"
width="67"
height="19"
transform="rotate(-90 21.5 79.5)"
fill="#C4C4C4"
stroke="black"
strokeDasharray="2 2"
/>
</Icon>
);

export const QuestionIcon = (): React.ReactElement => (
<Icon viewBox="0 0 88 88" boxSize="5em">
<rect opacity="0.2" width="88" height="88" fill="#A1B4C7" />
Expand Down
29 changes: 0 additions & 29 deletions frontend/src/components/assessment-creation/CreateQuestionPage.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/components/common/QuestionCard.tsx
Expand Up @@ -12,7 +12,7 @@ import {
ListItem,
} from "@chakra-ui/react";
import QuestionTag from "./QuestionTag";
import QuestionType from "../../types/QuestionTypes";
import { QuestionType } from "../../types/QuestionTypes";
import {
EditOutlineIcon,
DeleteOutlineIcon,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/QuestionTag.tsx
Expand Up @@ -5,7 +5,7 @@ import {
MultipleChoiceTagIcon,
ShortAnswerTagIcon,
} from "../../assets/icons";
import QuestionType from "../../types/QuestionTypes";
import { QuestionType } from "../../types/QuestionTypes";

type QuestionTypeProps = {
type: QuestionType;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/ComponentLibrary.tsx
@@ -1,6 +1,6 @@
import React from "react";
import QuestionCard from "../common/QuestionCard";
import QuestionType from "../../types/QuestionTypes";
import { QuestionType } from "../../types/QuestionTypes";

const ComponentLibrary = (): React.ReactElement => {
return (
Expand Down
21 changes: 12 additions & 9 deletions frontend/src/components/pages/admin/AdminDashboard.tsx
Expand Up @@ -5,12 +5,13 @@ import { Switch, Route, Redirect } from "react-router-dom";
import Navbar from "../../common/Navbar";
import Page from "../../../types/PageTypes";
import * as Routes from "../../../constants/Routes";
import PrivateRoute from "../../auth/PrivateRoute";

import UsersPage from "./UsersPage";
import CreateQuestionPage from "../../assessment-creation/CreateQuestionPage";
import CreateAssessmentPage from "./CreateAssessmentPage";
import PrivateRoute from "../../auth/PrivateRoute";
import NotFound from "../NotFound";
import DisplayAssessmentsPage from "./DisplayAssessmentsPage";
import QuestionPage from "./QuestionPage";
import NotFound from "../NotFound";

const pages: Page[] = [
{ title: "Assessments", url: Routes.ASSESSMENTS },
Expand All @@ -19,6 +20,13 @@ const pages: Page[] = [

const AdminDashboard = (): React.ReactElement => {
return (
<Switch>
<PrivateRoute
exact
path={Routes.CREATE_QUESTION}
component={QuestionPage}
roles={["Admin"]}
/>
<VStack flex="1" align="left">
<Navbar pages={pages} />
<Box padding="1.5em 2em 0em 2em">
Expand All @@ -29,12 +37,6 @@ const AdminDashboard = (): React.ReactElement => {
component={UsersPage}
roles={["Admin"]}
/>
<PrivateRoute
exact
path={Routes.CREATE_QUESTION}
component={CreateQuestionPage}
roles={["Admin"]}
/>
<PrivateRoute
exact
path={Routes.ASSESSMENTS}
Expand All @@ -56,6 +58,7 @@ const AdminDashboard = (): React.ReactElement => {
</Switch>
</Box>
</VStack>
</Switch>
);
};

Expand Down
37 changes: 37 additions & 0 deletions frontend/src/components/pages/admin/QuestionPage.tsx
@@ -0,0 +1,37 @@
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { Flex } from "@chakra-ui/react";

import { HOME_PAGE } from "../../../constants/Routes";
import QuestionSidebar from "../../question-creation/QuestionSidebar";
import QuestionEditor from "../../question-creation/QuestionEditor";

import { QuestionElement } from "../../../types/QuestionTypes";

const QuestionPage = (): React.ReactElement => {
const [questionElements, setQuestionElements] = React.useState<
QuestionElement[]
>([]);

const addQuestionElement = (newQuestionElement: QuestionElement) => {
setQuestionElements((prevQuestionElements) => [
...prevQuestionElements,
newQuestionElement,
]);
};

return (
<DndProvider backend={HTML5Backend}>
<Flex minHeight="100vh">
<QuestionSidebar
addQuestionElement={addQuestionElement}
backPage={HOME_PAGE}
/>
<QuestionEditor questionElements={questionElements} />
</Flex>
</DndProvider>
);
};

export default QuestionPage;
6 changes: 6 additions & 0 deletions frontend/src/components/question-creation/HoverMessage.tsx
@@ -0,0 +1,6 @@
import React from "react";
import { Text } from "@chakra-ui/react";

const HoverMessage = (): React.ReactElement => <Text>You are hovering.</Text>;

export default HoverMessage;
67 changes: 67 additions & 0 deletions frontend/src/components/question-creation/QuestionEditor.tsx
@@ -0,0 +1,67 @@
import React from "react";
import { Text, Box, VStack } from "@chakra-ui/react";
import { useDrop } from "react-dnd";

import WelcomeMessage from "./WelcomeMessage";
import HoverMessage from "./HoverMessage";
import TextElement from "./question-elements/TextElement";

import { QuestionElement } from "../../types/QuestionTypes";
import { DragTypes } from "../../types/DragTypes";

interface QuestionEditorProps {
questionElements: QuestionElement[];
}

const renderQuestionElement = (
questionElement: QuestionElement,
index: number,
) => {
switch (questionElement) {
case QuestionElement.QUESTION:
return <Text key={index}>this is a question element.</Text>;
case QuestionElement.TEXT:
return <TextElement key={index} />;
case QuestionElement.IMAGE:
return <Text key={index}>this is an image element.</Text>;
case QuestionElement.MULTIPLE_CHOICE:
return <Text key={index}>this is a multiple choice element.</Text>;
case QuestionElement.SHORT_ANSWER:
return <Text key={index}>this is a short answer element.</Text>;
case QuestionElement.MULTI_SELECT:
return <Text key={index}>this is a multi select element.</Text>;
default:
return null;
}
};

const QuestionEditor = ({
questionElements,
}: QuestionEditorProps): React.ReactElement => {
const [{ canDrop, isOver }, drop] = useDrop(() => ({
accept: DragTypes.QUESTION_ELEMENT,
drop: () => ({ name: "Question Editor" }),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}));

const isHovering = canDrop && isOver;

return (
<Box flex="1" ref={drop} overflow="auto">
<VStack margin="3em 5em" align="left" color="grey.400">
{isHovering && <HoverMessage />}
{!isHovering && !questionElements.length && <WelcomeMessage />}
{!isHovering &&
questionElements.length &&
questionElements.map((questionElement, index) =>
renderQuestionElement(questionElement, index),
)}
</VStack>
</Box>
);
};

export default QuestionEditor;
30 changes: 30 additions & 0 deletions frontend/src/components/question-creation/QuestionElement.tsx
@@ -0,0 +1,30 @@
import React from "react";
import { Text, HStack, Box } from "@chakra-ui/react";
import { DeleteOutlineIcon, HamburgerMenuIcon } from "../../assets/icons";

export interface QuestionElementProps {
children: React.ReactNode;
error?: string;
}

const QuestionElement = ({
children,
error,
}: QuestionElementProps): React.ReactElement => {
return (
<>
<HStack spacing="6" fontSize="24px">
<Box color="grey.300">
<HamburgerMenuIcon />
</Box>
{children}
<Box color="grey.300" fontSize="24px">
<DeleteOutlineIcon />
</Box>
</HStack>
{error && <Text color="red.200">{error}</Text>}
</>
);
};

export default QuestionElement;

0 comments on commit 04b65a1

Please sign in to comment.