feat: drag&drop

This commit is contained in:
IlyaDoronin 2023-08-18 14:16:56 +03:00
parent 54cf5803f5
commit 54a06c4666
8 changed files with 297 additions and 170 deletions

@ -158,7 +158,6 @@ export default function QuestionsPageCard({
padding: "20px",
}}
>
<>{isExpanded && console.log(listQuestions[totalIndex])}</>
<FormControl fullWidth variant="standard" sx={{ p: 0 }}>
<TextField
fullWidth

@ -1,4 +1,4 @@
import * as React from 'react';
import * as React from "react";
import AnswerOptions from "./answerOptions/AnswerOptions";
import OptionsPicture from "./OptionsPicture/OptionsPicture";
@ -15,48 +15,48 @@ import {useParams} from "react-router-dom";
import { questionStore } from "@root/questions";
interface Props {
totalIndex: number,
totalIndex: number;
}
export default function SwitchQuestionsPage({ totalIndex }: Props) {
const params = Number(useParams().quizId);
const {listQuestions} = questionStore()
const switchState = listQuestions[totalIndex].type
const { listQuestions } = questionStore();
const switchState = listQuestions[totalIndex].type;
switch (switchState) {
case 'variant':
return (<AnswerOptions totalIndex={totalIndex}/>);
case "variant":
return <AnswerOptions totalIndex={totalIndex} />;
case 'images':
return (<OptionsPicture totalIndex={totalIndex}/>);
case "images":
return <OptionsPicture totalIndex={totalIndex} />;
case 'varimg':
return (<OptionsAndPicture totalIndex={totalIndex}/>);
case "varimg":
return <OptionsAndPicture totalIndex={totalIndex} />;
case 'emoji':
return (<Emoji totalIndex={totalIndex}/>);
case "emoji":
return <Emoji totalIndex={totalIndex} />;
case 'text':
return (<OwnTextField totalIndex={totalIndex}/>);
case "text":
return <OwnTextField totalIndex={totalIndex} />;
case 'select':
return (<DropDown totalIndex={totalIndex}/>);
case "select":
return <DropDown totalIndex={totalIndex} />;
case 'date':
return (<DataOptions totalIndex={totalIndex}/>);
case "date":
return <DataOptions totalIndex={totalIndex} />;
case 'number':
return (<SliderOptions totalIndex={totalIndex}/>);
case "number":
return <SliderOptions totalIndex={totalIndex} />;
case 'file':
return (<UploadFile totalIndex={totalIndex}/>);
case "file":
return <UploadFile totalIndex={totalIndex} />;
case 'page':
return (<PageOptions totalIndex={totalIndex}/>);
case "page":
return <PageOptions totalIndex={totalIndex} />;
case 'rating':
return (<RatingOptions totalIndex={totalIndex}/>);
case "rating":
return <RatingOptions totalIndex={totalIndex} />;
default:
return (<></>)
return <></>;
}
}

@ -0,0 +1,159 @@
import { useState } from "react";
import { Draggable } from "react-beautiful-dnd";
import {
TextField,
FormControl,
InputAdornment,
IconButton,
ListItem,
useTheme,
} from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions";
import PointsIcon from "@icons/questionsPage/PointsIcon";
import DeleteIcon from "@icons/questionsPage/deleteIcon";
import MessageIcon from "@icons/messagIcon";
import Popover from "@mui/material/Popover";
import TextareaAutosize from "@mui/base/TextareaAutosize";
import type { ChangeEvent, KeyboardEvent } from "react";
import type { Variants } from "@root/questions";
type AnswerItemProps = {
index: number;
totalIndex: number;
variants: Variants[];
variant: Variants;
};
export const AnswerItem = ({
index,
totalIndex,
variants,
variant,
}: AnswerItemProps) => {
const { listQuestions } = questionStore();
const theme = useTheme();
const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
};
const onChangeText = (event: ChangeEvent<HTMLInputElement>) => {
const answerNew = variants.slice();
answerNew[index].answer = event.target.value;
updateQuestionsList(totalIndex, {
content: { ...listQuestions[totalIndex].content, variants: answerNew },
});
};
const addNewAnswer = () => {
const answerNew = variants.slice();
answerNew.push({ answer: "", answerLong: "", hints: "" });
updateQuestionsList(totalIndex, {
content: { ...listQuestions[totalIndex].content, variants: answerNew },
});
};
const deleteAnswer = () => {
const answerNew = variants.slice();
answerNew.splice(index, 1);
updateQuestionsList(totalIndex, {
content: { ...listQuestions[totalIndex].content, variants: answerNew },
});
};
const changeAnswerHint = (event: ChangeEvent<HTMLTextAreaElement>) => {
const answerNew = variants.slice();
answerNew[index].hints = event.target.value;
updateQuestionsList(totalIndex, {
content: { ...listQuestions[totalIndex].content, variants: answerNew },
});
};
return (
<Draggable draggableId={String(index)} index={index}>
{(provided) => (
<ListItem ref={provided.innerRef} {...provided.draggableProps}>
<FormControl
key={index}
fullWidth
variant="standard"
sx={{ p: "0 0 20px 0" }}
>
<TextField
value={variant.answer}
fullWidth
autoFocus
placeholder={"Добавьте ответ"}
onChange={onChangeText}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) =>
event.code === "Enter" && addNewAnswer()
}
InputProps={{
startAdornment: (
<InputAdornment
{...provided.dragHandleProps}
position="start"
>
<PointsIcon />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-describedby="my-popover-id"
onClick={handleClick}
>
<MessageIcon />
</IconButton>
<Popover
id="my-popover-id"
open={isOpen}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
>
<TextareaAutosize
style={{ margin: "10px" }}
placeholder="Подсказка для этого ответа"
value={variant.hints}
onChange={changeAnswerHint}
/>
</Popover>
<IconButton onClick={deleteAnswer}>
<DeleteIcon color={theme.palette.grey2.main} />
</IconButton>
</InputAdornment>
),
}}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "10px",
background: "#ffffff",
},
}}
inputProps={{
sx: { fontSize: "18px", lineHeight: "21px", py: 0 },
}}
/>
</FormControl>
</ListItem>
)}
</Draggable>
);
};

@ -0,0 +1,23 @@
import { useState, useEffect } from "react";
import { Droppable } from "react-beautiful-dnd";
import type { DroppableProps } from "react-beautiful-dnd";
export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
setEnabled(false);
cancelAnimationFrame(animation);
};
}, []);
if (!enabled) {
return null;
}
return <Droppable {...props}>{children}</Droppable>;
};

@ -0,0 +1,11 @@
export const reorder = <T>(
list: T[],
startIndex: number,
endIndex: number
): T[] => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};

@ -0,0 +1,51 @@
import { Box } from "@mui/material";
import { DragDropContext } from "react-beautiful-dnd";
import { StrictModeDroppable } from "./StrictModeDroppable";
import { AnswerItem } from "./AnswerItem";
import { updateVariants } from "@root/questions";
import { reorder } from "./helper";
import type { DropResult } from "react-beautiful-dnd";
import type { Variants } from "@root/questions";
type AnswerDraggableListProps = {
variants: Variants[];
totalIndex: number;
};
export const AnswerDraggableList = ({
variants,
totalIndex,
}: AnswerDraggableListProps) => {
const onDragEnd = ({ destination, source }: DropResult) => {
if (destination) {
const newItems = reorder(variants, source.index, destination.index);
updateVariants(totalIndex, newItems);
}
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<StrictModeDroppable droppableId="droppable-list">
{(provided) => (
<Box ref={provided.innerRef} {...provided.droppableProps}>
{variants.map((variant, index) => (
<AnswerItem
key={variant.answer + index}
index={index}
totalIndex={totalIndex}
variants={variants}
variant={variant}
/>
))}
{provided.placeholder}
</Box>
)}
</StrictModeDroppable>
</DragDropContext>
);
};

@ -1,27 +1,11 @@
import {
Box,
Typography,
Link,
useTheme,
TextField,
FormControl,
InputAdornment,
IconButton,
} from "@mui/material";
import React, { useState } from "react";
import { useState } from "react";
import { Box, Typography, Link, useTheme } from "@mui/material";
import EnterIcon from "../../../assets/icons/questionsPage/enterIcon";
import SwitchAnswerOptions from "./switchAnswerOptions";
import { AnswerDraggableList } from "./AnswerDraggableList";
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
import QuestionsPageCard from "../DraggableList/QuestionPageCard";
import { useParams } from "react-router-dom";
import { Variants, questionStore, updateQuestionsList } from "@root/questions";
import PointsIcon from "@icons/questionsPage/PointsIcon";
import DeleteIcon from "@icons/questionsPage/deleteIcon";
import MessageIcon from "@icons/messagIcon";
import Popover from "@mui/material/Popover";
import TextareaAutosize from "@mui/base/TextareaAutosize";
import { questionStore, updateQuestionsList } from "@root/questions";
import type { KeyboardEvent } from "react";
// Импортируем интерфейс Varian
interface Props {
@ -29,33 +13,17 @@ interface Props {
}
export default function AnswerOptions({ totalIndex }: Props) {
const [switchState, setSwitchState] = useState("setting");
const { listQuestions } = questionStore();
const Answer = listQuestions[totalIndex].content.variants;
console.log(Answer);
const variants = listQuestions[totalIndex].content.variants;
const theme = useTheme();
const [switchState, setSwitchState] = React.useState("setting");
const SSHC = (data: string) => {
setSwitchState(data);
};
const [isOpen, setIsOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
null
);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
};
const id = "my-popover-id";
const addNewAnswer = () => {
const answerNew = Answer.slice(); // Create a shallow copy of the array
const answerNew = variants.slice(); // Create a shallow copy of the array
answerNew.push({ answer: "", answerLong: "", hints: "" });
updateQuestionsList(totalIndex, {
@ -66,7 +34,7 @@ export default function AnswerOptions({ totalIndex }: Props) {
return (
<>
<Box sx={{ padding: "0 20px 20px 20px" }}>
{Answer.length === 0 ? (
{variants.length === 0 ? (
<Typography
sx={{
padding: "0 0 33px 80px",
@ -79,99 +47,7 @@ export default function AnswerOptions({ totalIndex }: Props) {
Добавьте ответ
</Typography>
) : (
<Box>
{Answer.map((variant: Variants, index: number) => (
<FormControl
key={index}
fullWidth
variant="standard"
sx={{ p: "0 0 20px 0" }}
>
<TextField
value={variant.answer}
fullWidth
autoFocus
placeholder={"Добавьте ответ"}
onChange={(e) => {
const answerNew = Answer.slice(); // Create a shallow copy of the array
answerNew[index].answer = e.target.value;
let clonContent = listQuestions[totalIndex].content;
clonContent.variants = answerNew;
updateQuestionsList(totalIndex, { content: clonContent });
}}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) =>
event.code === "Enter" && addNewAnswer()
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PointsIcon />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton aria-describedby={id} onClick={handleClick}>
<MessageIcon />
</IconButton>
<Popover
id={id}
open={isOpen}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<TextareaAutosize
style={{ margin: "10px" }}
placeholder="Подсказка для этого ответа"
value={variant.hints}
onChange={(e) => {
const answerNew = Answer.slice(); // Create a shallow copy of the array
answerNew[index].hints = e.target.value;
let clonContent =
listQuestions[totalIndex].content;
clonContent.variants = answerNew;
updateQuestionsList(totalIndex, {
content: clonContent,
});
}}
/>
</Popover>
<IconButton
onClick={() => {
const answerNew = Answer.slice(); // Create a shallow copy of the array
answerNew.splice(index, 1);
let clonContent = listQuestions[totalIndex].content;
clonContent.variants = answerNew;
updateQuestionsList(totalIndex, {
content: clonContent,
});
}}
>
<DeleteIcon color={theme.palette.grey2.main} />
</IconButton>
</InputAdornment>
),
}}
sx={{
"& .MuiInputBase-root": {
height: "48px",
borderRadius: "10px",
},
}}
inputProps={{
sx: {
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
</FormControl>
))}
</Box>
<AnswerDraggableList variants={variants} totalIndex={totalIndex} />
)}
<Box

@ -56,6 +56,14 @@ export const updateQuestionsListDragAndDrop = (
questionStore.setState({ listQuestions: updatedQuestions });
};
export const updateVariants = (index: number, variants: Variants[]) => {
const listQuestions = [...questionStore.getState()["listQuestions"]];
listQuestions[index].content.variants = variants;
questionStore.setState({ listQuestions });
};
export const createQuestion = (id: number) => {
const idQ = getRandom(1000000, 10000000);
const newData = [...questionStore.getState()["listQuestions"]]; //пересоздание массива