Merge branch 'staging'
This commit is contained in:
commit
ab05007b85
2
CHANGELOG.md
Normal file
2
CHANGELOG.md
Normal file
@ -0,0 +1,2 @@
|
||||
1.0.1 Страница заявок корректно отображает мультиответ
|
||||
1.0.0 Добавлены фичи "мультиответ", "перенос строки в своём ответе", "свой ответ", "плейсхолдер своего ответа"
|
@ -3,9 +3,12 @@ include:
|
||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/service-discovery.gitlab-ci.yml"
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
- service-discovery
|
||||
build-app:
|
||||
extends: .build_template
|
||||
tags:
|
||||
@ -30,3 +33,6 @@ deploy-to-prod:
|
||||
tags:
|
||||
- front
|
||||
- prod
|
||||
|
||||
service-discovery:
|
||||
extends: .sd_artefacts_template
|
||||
|
@ -6,6 +6,8 @@ services:
|
||||
image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
networks:
|
||||
- marketplace_penahub_frontend
|
||||
labels:
|
||||
com.pena.domains: squiz.pena.digital
|
||||
hostname: squiz
|
||||
tty: true
|
||||
networks:
|
||||
|
@ -7,7 +7,7 @@
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.85",
|
||||
"@frontend/squzanswerer": "^1.0.55",
|
||||
"@frontend/squzanswerer": "^1.0.56",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
@ -29,7 +29,7 @@
|
||||
"cytoscape": "^3.26.0",
|
||||
"cytoscape-popper": "^2.0.0",
|
||||
"date-fns": "^3.0.6",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.4.5",
|
||||
"html-to-image": "^1.11.11",
|
||||
|
26
src/assets/icons/AmoTrash.tsx
Normal file
26
src/assets/icons/AmoTrash.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color?: string;
|
||||
height?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export default function AmoTrash({ color, height, width }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.4994 6H4.5" stroke="#FC2012" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.5 18.7492V8.74609H18.5V18.7492C18.5 20.1299 17.3807 21.2492 16 21.2492H8C6.61929 21.2492 5.5 20.1299 5.5 18.7492Z" fill="#FC2012" stroke="#F02B2B"/>
|
||||
<path d="M15.75 6V4.5C15.75 4.10218 15.592 3.72064 15.3107 3.43934C15.0294 3.15804 14.6478 3 14.25 3H9.75C9.35218 3 8.97064 3.15804 8.68934 3.43934C8.40804 3.72064 8.25 4.10218 8.25 4.5V6" stroke="#FC2012" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
166
src/model/CropModal/CropModal.ts
Normal file
166
src/model/CropModal/CropModal.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import type { PercentCrop } from "react-image-crop";
|
||||
|
||||
|
||||
export const workSpaceTypesList = {
|
||||
images: {
|
||||
desktop: {
|
||||
step: "desktop",
|
||||
ratio: {
|
||||
width: 317,
|
||||
height: 257
|
||||
}
|
||||
},
|
||||
tablet: {
|
||||
step: "tablet",
|
||||
ratio: {
|
||||
width: 455,
|
||||
height: 257
|
||||
}
|
||||
},
|
||||
mobile: {
|
||||
step: "mobile",
|
||||
ratio: {
|
||||
width: 160,
|
||||
height: 183
|
||||
}
|
||||
},
|
||||
},
|
||||
varimg: {
|
||||
desktop: {
|
||||
step: "desktop",
|
||||
ratio: {
|
||||
width: 450,
|
||||
height: 450
|
||||
}
|
||||
},
|
||||
mobile: {
|
||||
step: "mobile",
|
||||
ratio: {
|
||||
width: 335,
|
||||
height: 335,
|
||||
}
|
||||
},
|
||||
},
|
||||
text: {
|
||||
desktop: {
|
||||
step: "desktop",
|
||||
ratio: {
|
||||
width: 450,
|
||||
height: 450
|
||||
}
|
||||
},
|
||||
mobile: {
|
||||
step: "mobile",
|
||||
ratio: {
|
||||
width: 335,
|
||||
height: 335,
|
||||
}
|
||||
},
|
||||
},
|
||||
variant: {
|
||||
desktop: {
|
||||
step: "desktop",
|
||||
ratio: {
|
||||
width: 450,
|
||||
height: 450
|
||||
}
|
||||
},
|
||||
mobile: {
|
||||
step: "mobile",
|
||||
ratio: {
|
||||
width: 335,
|
||||
height: 335,
|
||||
}
|
||||
},
|
||||
},
|
||||
result: {
|
||||
desktop: {
|
||||
step: "desktop",
|
||||
ratio: {
|
||||
width: 700,
|
||||
height: 306
|
||||
}
|
||||
},
|
||||
mobile: {
|
||||
step: "mobile",
|
||||
ratio: {
|
||||
width: 335,
|
||||
height: 236
|
||||
}
|
||||
},
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type WorkSpaceTypesList = typeof workSpaceTypesList;
|
||||
export type Writable<T> = { -readonly [K in keyof T]: T[K] };
|
||||
|
||||
|
||||
export type CropOnOpenType = {
|
||||
open: boolean;
|
||||
originalImageUrl?:string;
|
||||
imageBlob?: Blob;
|
||||
editedUrlImagesList?: Record<Partial<ScreenStepsTypes>, string>;
|
||||
questionId: string;
|
||||
questionType: AcceptedQuestionTypes;
|
||||
quizId: string;
|
||||
variantId?: string;
|
||||
|
||||
selfClose: () => void;
|
||||
setPictureUploading: (is: boolean) => void;
|
||||
}
|
||||
|
||||
export type WorkSpaceTypes = WorkSpaceTypesList[AcceptedQuestionTypes];
|
||||
export interface CropModalProps {
|
||||
open: boolean;
|
||||
editedImages: Record<keyof WorkSpaceTypes, EditedImage>;
|
||||
workSpaceTypes: WorkSpaceTypes;
|
||||
originalImageUrl: string;
|
||||
|
||||
setEditedImages: (callback: (editedImages: Record<keyof WorkSpaceTypes, EditedImage>) => Record<keyof WorkSpaceTypes, EditedImage>) => void;
|
||||
onSaveImageClick: () => void;
|
||||
closeCropModal: CropOnCloseType;
|
||||
onDeleteClick: () => void;
|
||||
};
|
||||
|
||||
export type AcceptedQuestionTypes = keyof WorkSpaceTypesList; //"images" | "varimg" | "text" | "variant" | "result"
|
||||
|
||||
|
||||
export type CropOnCloseType = () => void;
|
||||
export type CropOnDeleteIamgeClick = (callback: () => void) => void;
|
||||
|
||||
export type EditedImages = Record<Partial<ScreenStepsTypes>, EditedImage>
|
||||
|
||||
export type EditedImage = {
|
||||
step: string,
|
||||
url: string,
|
||||
newRules: EditedImageNewRules
|
||||
}
|
||||
|
||||
export type WorkSpaceModel = {
|
||||
step: Partial<ScreenStepsTypes>,
|
||||
ratio: CropAspectRatio
|
||||
};
|
||||
|
||||
export type CropAspectRatio = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type ScreenStepsTypes = "desktop" | "tablet" | "mobile" | "small";
|
||||
|
||||
export type EditedImageNewRules = {
|
||||
crop: PercentCrop,
|
||||
darken: number,
|
||||
rotate: number,
|
||||
}
|
||||
export const DEFAULTCROPRULES = {
|
||||
crop: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
unit: "%" as "%",
|
||||
},
|
||||
rotate: 0,
|
||||
darken: 0,
|
||||
}
|
@ -30,6 +30,17 @@ export function createQuestionVariant(): QuestionVariant {
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
isOwn: false,
|
||||
originalImageUrl: "",
|
||||
};
|
||||
}
|
||||
export function createQuestionOwnVariant(): QuestionVariant {
|
||||
return {
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
isOwn: true,
|
||||
originalImageUrl: "",
|
||||
};
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import React from "react";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
import SettingIcon from "@/assets/icons/questionsPage/settingIcon";
|
||||
import Branching from "@/assets/icons/questionsPage/branching";
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import SupplementIcon from "../../assets/icons/ContactFormIcon/supplementIcon";
|
||||
import SupplementIcon from "@/assets/icons/ContactFormIcon/supplementIcon";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
setSwitchState: (data: string) => void;
|
||||
}
|
||||
|
||||
export default function ButtonSettingForms({ SSHC, switchState }: Props) {
|
||||
export default function ButtonSettingForms({ setSwitchState, switchState }: Props) {
|
||||
const theme = useTheme();
|
||||
const buttonSetting: { icon: JSX.Element; title: string; value: string }[] = [
|
||||
{
|
||||
@ -68,7 +68,7 @@ export default function ButtonSettingForms({ SSHC, switchState }: Props) {
|
||||
<MiniButtonSetting
|
||||
key={i}
|
||||
onClick={() => {
|
||||
SSHC(e.value);
|
||||
setSwitchState(e.value);
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
|
@ -17,19 +17,13 @@ import {
|
||||
import { incrementCurrentStep, updateQuiz } from "@root/quizes/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import React, { ReactNode, useRef, useState } from "react";
|
||||
import Info from "../../assets/icons/Info";
|
||||
import Info from "@/assets/icons/Info";
|
||||
import Trash from "@icons/trash";
|
||||
import { OneIcon } from "../../assets/icons/questionsPage/OneIcon";
|
||||
import AddPlus from "../../assets/icons/questionsPage/addPlus";
|
||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import ArrowLeft from "@/assets/icons/questionsPage/arrowLeft";
|
||||
import { decrementCurrentStep } from "@root/quizes/actions";
|
||||
import ButtonSettingForms from "./ButtonSettingForms";
|
||||
import DrawerNewField from "./DrawerParent";
|
||||
import WindowMessengers from "./Massengers/WindowMessengers";
|
||||
import WindowNewField from "./NewField/WindowNewField";
|
||||
import SwitchContactForm from "./switchContactForm";
|
||||
import GearIcon from "@icons/GearIcon";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import {
|
||||
FieldSettingsDrawerState,
|
||||
|
@ -11,16 +11,16 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import BrowserIcon from "../../assets/icons/BrowserIcon";
|
||||
import TiktokIcon from "../../assets/icons/tiktokIcon";
|
||||
import TelegramIcon from "../../assets/icons/telegramIcon";
|
||||
import QRIcon from "../../assets/icons/qrIcon";
|
||||
import BrowserIcon from "@/assets/icons/BrowserIcon";
|
||||
import TiktokIcon from "@/assets/icons/tiktokIcon";
|
||||
import TelegramIcon from "@/assets/icons/telegramIcon";
|
||||
import QRIcon from "@/assets/icons/qrIcon";
|
||||
import React from "react";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
import CopyIcon from "../../assets/icons/CopyIcon";
|
||||
import Qr from "../../assets/Qr.png";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||
import Qr from "@/assets/Qr.png";
|
||||
|
||||
export default function ButtonSocial() {
|
||||
const theme = useTheme();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { decrementCurrentStep } from "@root/quizes/actions";
|
||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import ArrowLeft from "@/assets/icons/questionsPage/arrowLeft";
|
||||
import QuizInstallationCard from "./QuizInstallationCard/QuizInstallationCard";
|
||||
import QuizLinkCard from "./QuizLinkCard";
|
||||
|
||||
|
2
src/pages/InstallQuiz/QuizInstallationCard/WidgetSetupByType/BannerWidgetSetup/BannerWidgetSetup.tsx
2
src/pages/InstallQuiz/QuizInstallationCard/WidgetSetupByType/BannerWidgetSetup/BannerWidgetSetup.tsx
@ -19,7 +19,7 @@ import PenaTextField from "@ui_kit/PenaTextField";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { ReactNode, useState } from "react";
|
||||
import widgetPreviewImage from "../../../../../assets/widget-preview.png";
|
||||
import widgetPreviewImage from "@/assets/widget-preview.png";
|
||||
import WidgetScript from "../../WidgetScript";
|
||||
import { createBannerWidgetScriptText } from "../../createWidgetScriptText";
|
||||
import { useWidgetUrl } from "../../useWidgetUrl";
|
||||
|
4
src/pages/InstallQuiz/QuizInstallationCard/WidgetSetupByType/ButtonWidgetSetup/ButtonWidgetSetup.tsx
4
src/pages/InstallQuiz/QuizInstallationCard/WidgetSetupByType/ButtonWidgetSetup/ButtonWidgetSetup.tsx
@ -21,8 +21,8 @@ import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import RunningStripe from "@ui_kit/RunningStripe";
|
||||
import { nanoid } from "nanoid";
|
||||
import { ReactNode, useState } from "react";
|
||||
import Dots from "../../../../../assets/dots.png";
|
||||
import widgetPreviewImage from "../../../../../assets/widget-preview.png";
|
||||
import Dots from "@/assets/dots.png";
|
||||
import widgetPreviewImage from "@/assets/widget-preview.png";
|
||||
import WidgetScript from "../../WidgetScript";
|
||||
import { createButtonWidgetScriptText } from "../../createWidgetScriptText";
|
||||
import { useWidgetUrl } from "../../useWidgetUrl";
|
||||
|
@ -5,7 +5,7 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import PenaTextField from "@ui_kit/PenaTextField";
|
||||
import { nanoid } from "nanoid";
|
||||
import { ReactNode, useState } from "react";
|
||||
import Dots from "../../../../assets/dots.png";
|
||||
import Dots from "@/assets/dots.png";
|
||||
import WidgetScript from "../WidgetScript";
|
||||
import { createContainerWidgetScriptText } from "../createWidgetScriptText";
|
||||
import { useWidgetUrl } from "../useWidgetUrl";
|
||||
|
@ -3,7 +3,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import PenaTextField from "@ui_kit/PenaTextField";
|
||||
import { ReactNode, useState } from "react";
|
||||
import Dots from "../../../../assets/dots.png";
|
||||
import Dots from "@/assets/dots.png";
|
||||
import WidgetScript from "../WidgetScript";
|
||||
import { createPopupWidgetScriptText } from "../createWidgetScriptText";
|
||||
import { useWidgetUrl } from "../useWidgetUrl";
|
||||
|
@ -5,7 +5,7 @@ import CircleColorPicker from "@ui_kit/CircleColorPicker";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import PenaTextField from "@ui_kit/PenaTextField";
|
||||
import { ReactNode, useState } from "react";
|
||||
import widgetPreviewImage from "../../../../../assets/widget-preview.png";
|
||||
import widgetPreviewImage from "@/assets/widget-preview.png";
|
||||
import WidgetScript from "../../WidgetScript";
|
||||
import { createSideWidgetScriptText } from "../../createWidgetScriptText";
|
||||
import { useWidgetUrl } from "../../useWidgetUrl";
|
||||
|
@ -15,9 +15,9 @@ import {
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||
import { FC, useState } from "react";
|
||||
import ArrowDown from "../../assets/icons/ArrowDownIcon";
|
||||
import CopyIcon from "../../assets/icons/CopyIcon";
|
||||
import LinkIcon from "../../assets/icons/LinkIcon";
|
||||
import ArrowDown from "@/assets/icons/ArrowDownIcon";
|
||||
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||
import LinkIcon from "@/assets/icons/LinkIcon";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
|
@ -103,9 +103,10 @@ export default function Component() {
|
||||
<Typography color={"#727074"} fontSize={"14px"}>
|
||||
ООО Пена © 2024
|
||||
</Typography>
|
||||
<TagsCloudBlue sx={{ mt: "10px" }} />
|
||||
<TagsCloudBlue sx={{mt: "10px"}}/>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import abstraction from "../../assets/quizMain.png";
|
||||
import abstraction from "@/assets/quizMain.png";
|
||||
import SectionStyled from "./SectionStyled";
|
||||
import { Link, redirect, useLocation, useNavigate } from "react-router-dom";
|
||||
import { setIsContactFormOpen } from "@root/contactForm";
|
||||
|
@ -4,7 +4,7 @@ import { Fade, Typography, Zoom, useMediaQuery, useTheme } from "@mui/material";
|
||||
import SectionStyled from "./SectionStyled";
|
||||
import Link from "@mui/material/Link";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Notebook from "../../assets/LandingPict/notebook";
|
||||
import Notebook from "@/assets/LandingPict/notebook";
|
||||
import BigBlock from "./images/bigblock.png";
|
||||
import businessTasks2 from "./images/businessTasks2.png";
|
||||
import businessTasks3 from "./images/businessTasks3.png";
|
||||
@ -15,7 +15,6 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
// import CalendarW from './image/calendar-W.svg'
|
||||
// import CalendarP from './image/calendar-P.svg'
|
||||
import CalendarIcon from "../../assets/LandingPict/calendarIcon";
|
||||
import { PieСhartIcon } from "@icons/PieСhartIcon";
|
||||
import { SegmentationIcon } from "@icons/SegmentationIcon";
|
||||
import { TestingIcon } from "@icons/TestingIcon";
|
||||
|
@ -3,7 +3,6 @@ import Box from "@mui/material/Box";
|
||||
import { SxProps, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import SectionStyled from "./SectionStyled";
|
||||
import Button from "@mui/material/Button";
|
||||
// import Desktop from '../../assets/LandingPict/Desktop.png';
|
||||
import Desktop1 from "./images/Frame 1171274552.png";
|
||||
import Desktop2 from "./images/Frame 1171274552-1.png";
|
||||
import Desktop3 from "./images/Frame 1171274552-2.png";
|
||||
|
@ -7,7 +7,7 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
// import Quotes from './image/quotes.svg';
|
||||
import { styled } from "@mui/material/styles";
|
||||
import TitleIcon from "../../assets/LandingPict/titleIcon";
|
||||
import TitleIcon from "@/assets/LandingPict/titleIcon";
|
||||
|
||||
const BoxCard = styled("div")(({ theme }) => ({
|
||||
[theme.breakpoints.down("md")]: {
|
||||
|
@ -10,11 +10,11 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions";
|
||||
import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField, updateQuestion } from "@root/questions/actions";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { memo, type ChangeEvent, type FC, type KeyboardEvent, type ReactNode } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import type { QuestionVariant } from "@frontend/squzanswerer";
|
||||
import type { QuestionVariant, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
@ -26,13 +26,21 @@ type AnswerItemProps = {
|
||||
disableKeyDown?: boolean;
|
||||
additionalContent?: ReactNode;
|
||||
additionalMobile?: ReactNode;
|
||||
isOwn: boolean;
|
||||
ownPlaceholder: string;
|
||||
};
|
||||
|
||||
const AnswerItem = memo<AnswerItemProps>(
|
||||
({ index, variant, questionId, largeCheck = false, additionalContent, additionalMobile, disableKeyDown }) => {
|
||||
({ index, variant, questionId, largeCheck = false, additionalContent, additionalMobile, disableKeyDown, isOwn, ownPlaceholder }) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const setOwnPlaceholder = (replText: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
question.content.ownPlaceholder = replText;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={String(index)}
|
||||
@ -55,19 +63,24 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
value={variant.answer}
|
||||
value={ isOwn ? ownPlaceholder : variant.answer}
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
placeholder={isOwn ? "Добавьте текст-подсказку для ввода “своего ответа”" : "Добавьте ответ"}
|
||||
multiline={largeCheck}
|
||||
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||
if (target.value.length <= 1000) {
|
||||
isOwn ?
|
||||
setOwnPlaceholder(target.value || " ")
|
||||
:
|
||||
setQuestionVariantField(questionId, variant.id, "answer", target.value || " ");
|
||||
} else {
|
||||
enqueueSnackbar("Превышена длина вводимого текста")
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (disableKeyDown) {
|
||||
enqueueSnackbar("100 максимальное количество вопросов");
|
||||
enqueueSnackbar("100 максимальное количество");
|
||||
} else if (event.code === "Enter" && !largeCheck) {
|
||||
addQuestionVariant(questionId);
|
||||
}
|
||||
@ -88,7 +101,13 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
onClick={() => deleteQuestionVariant(questionId, variant.id)}
|
||||
onClick={() => {
|
||||
isOwn ? updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.own = false;
|
||||
})
|
||||
:
|
||||
deleteQuestionVariant(questionId, variant.id)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
@ -104,7 +123,7 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
"& .MuiInputBase-root": {
|
||||
padding: additionalContent ? "5px 13px" : "13px",
|
||||
borderRadius: "10px",
|
||||
background: "#ffffff",
|
||||
background: isOwn ? "#F2F3F7" : "white",
|
||||
"& input.MuiInputBase-input": {
|
||||
height: "22px",
|
||||
},
|
||||
|
@ -14,6 +14,8 @@ type Props = Omit<
|
||||
originalImageUrl?: string | null | undefined,
|
||||
) => Promise<void>;
|
||||
openImageUploadModal: () => void;
|
||||
isOwn: boolean;
|
||||
ownPlaceholder: string;
|
||||
};
|
||||
|
||||
export default function ImageEditAnswerItem({
|
||||
@ -27,6 +29,8 @@ export default function ImageEditAnswerItem({
|
||||
pictureUploding,
|
||||
openCropModal,
|
||||
openImageUploadModal,
|
||||
isOwn,
|
||||
ownPlaceholder,
|
||||
}: Props) {
|
||||
const addOrEditImageButton = useMemo(() => {
|
||||
return (
|
||||
@ -105,6 +109,8 @@ export default function ImageEditAnswerItem({
|
||||
variant={variant}
|
||||
additionalContent={addOrEditImageButton}
|
||||
additionalMobile={addOrEditImageButtonMobile}
|
||||
isOwn={isOwn}
|
||||
ownPlaceholder={ownPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
background-image: url("../../../../../assets/icons/ArrowGear.svg");
|
||||
background-image: url("@/assets/icons/ArrowGear.svg");
|
||||
font-size: 0px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Box, Skeleton, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { Box, Skeleton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
|
||||
import { lazy, Suspense, useCallback } from "react";
|
||||
import { DraggableList } from "./DraggableList";
|
||||
import { DraggableList } from "../DraggableList";
|
||||
import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
|
||||
|
||||
const BranchingMap = lazy(() =>
|
||||
import("./Branching/BranchingMap").then((module) => ({ default: module.BranchingMap })),
|
||||
import("./BranchingMap").then((module) => ({ default: module.BranchingMap })),
|
||||
);
|
||||
interface Props {
|
||||
openBranchingPage: boolean;
|
||||
@ -51,6 +51,7 @@ export const QuestionSwitchWindowTool = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Typography fontSize="20px" mb="25px">Настройки ветвления вопросов</Typography>
|
||||
<BranchingMap />
|
||||
</Suspense>
|
||||
) : (
|
@ -1,145 +0,0 @@
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { clearRuleForAll } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import {
|
||||
cleardragQuestionContentId,
|
||||
setModalQuestionParentContentId,
|
||||
setModalQuestionTargetContentId,
|
||||
updateOpenedModalSettingsId,
|
||||
} from "@root/uiTools/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import type { Core } from "cytoscape";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import CytoscapeComponent from "react-cytoscapejs";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||||
import CsNodeButtons from "./CsNodeButtons";
|
||||
import { InfoBanner } from "./InfoBanner/InfoBanner";
|
||||
import { PositionControl } from "./PositionControl/PositionControl";
|
||||
import { ZoomControl } from "./ZoomControl/ZoomControl";
|
||||
import { addNode, layoutOptions, storeToNodes } from "./helper";
|
||||
import { useRemoveNode } from "./hooks/useRemoveNode";
|
||||
import "./style/styles.css";
|
||||
import { stylesheet } from "./style/stylesheet";
|
||||
|
||||
function CsComponent() {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||
const [isBannerVisible, setBannerVisible] = useState(true);
|
||||
const desireToOpenABranchingModal = useUiTools((state) => state.desireToOpenABranchingModal);
|
||||
const modalQuestionParentContentId = useUiTools((state) => state.modalQuestionParentContentId);
|
||||
const modalQuestionTargetContentId = useUiTools((state) => state.modalQuestionTargetContentId);
|
||||
const trashQuestions = useQuestionsStore((state) => state.questions);
|
||||
const cyRef = useRef<Core | null>(null);
|
||||
const { removeNode } = useRemoveNode({ cyRef });
|
||||
|
||||
const csElements = useMemo(() => {
|
||||
const questions = trashQuestions.filter(
|
||||
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||||
);
|
||||
|
||||
return storeToNodes(questions);
|
||||
}, [trashQuestions]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const cy = cyRef?.current;
|
||||
if (desireToOpenABranchingModal) {
|
||||
setTimeout(() => {
|
||||
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true);
|
||||
}, 250);
|
||||
} else {
|
||||
cy?.elements().data("eroticeyeblink", false);
|
||||
}
|
||||
}, [desireToOpenABranchingModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||||
if (!cyRef.current) return;
|
||||
|
||||
addNode({
|
||||
parentNodeContentId: modalQuestionParentContentId,
|
||||
targetNodeContentId: modalQuestionTargetContentId,
|
||||
});
|
||||
}
|
||||
setModalQuestionParentContentId("");
|
||||
setModalQuestionTargetContentId("");
|
||||
}, [modalQuestionTargetContentId]);
|
||||
|
||||
useEffect(function onMount() {
|
||||
updateOpenedModalSettingsId();
|
||||
document.addEventListener("pointerup", cleardragQuestionContentId);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("pointerup", cleardragQuestionContentId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
function rerunLayout() {
|
||||
cyRef.current?.layout(layoutOptions).run();
|
||||
cyRef.current?.fit(undefined, 70);
|
||||
},
|
||||
[csElements]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CsNodeButtons
|
||||
csElements={csElements}
|
||||
cyRef={cyRef}
|
||||
/>
|
||||
<Box sx={{ position: "relative" }}>
|
||||
{isBannerVisible && <InfoBanner setBannerVisible={setBannerVisible} />}
|
||||
<PositionControl cyRef={cyRef} />
|
||||
<ZoomControl cyRef={cyRef} />
|
||||
<CytoscapeComponent
|
||||
wheelSensitivity={0.1}
|
||||
elements={csElements}
|
||||
style={{
|
||||
height: isMobile ? "327px" : "481px",
|
||||
background: "#F2F3F7",
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
width: "100%",
|
||||
}}
|
||||
stylesheet={stylesheet}
|
||||
layout={layoutOptions}
|
||||
cy={(cy) => {
|
||||
cyRef.current = cy;
|
||||
}}
|
||||
autoungrabify={true}
|
||||
autounselectify={true}
|
||||
boxSelectionEnabled={false}
|
||||
/>
|
||||
</Box>
|
||||
<DeleteNodeModal removeNode={removeNode} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Clear() {
|
||||
const quiz = useCurrentQuiz();
|
||||
if (quiz) {
|
||||
updateRootContentId(quiz?.id, "");
|
||||
}
|
||||
clearRuleForAll();
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default withErrorBoundary(CsComponent, {
|
||||
fallback: <Clear />,
|
||||
onError: (error, info) => {
|
||||
enqueueSnackbar("Дерево порвалось");
|
||||
devlog(info);
|
||||
devlog(error);
|
||||
},
|
||||
});
|
@ -2,7 +2,7 @@ import { Box, Paper } from "@mui/material";
|
||||
import { createUntypedQuestion } from "@root/questions/actions";
|
||||
import { useState } from "react";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
|
||||
import { ReactComponent as PlusIcon } from "@/assets/icons/plus.svg";
|
||||
import type { UntypedQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
import SwitchQuestionsPage from "../SwitchQuestionsPage";
|
||||
import TypeQuestions from "../TypeQuestions";
|
||||
|
@ -42,6 +42,7 @@ import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { FC, memo, useRef, useState } from "react";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
@ -124,11 +125,19 @@ const QuestionPageCardTitle = memo<Props>(function ({
|
||||
id="questionTitle"
|
||||
value={title}
|
||||
placeholder={"Заголовок вопроса"}
|
||||
onChange={({ target }) => setTitle(target.value || " ")}
|
||||
onChange={({ target }) => {
|
||||
console.log(target.value.length)
|
||||
if (target.value.length > maxLengthTextField) {
|
||||
enqueueSnackbar("Превышена длина вводимого текста")
|
||||
} else {
|
||||
setTitle(target.value || " ")
|
||||
}
|
||||
|
||||
}}
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
inputProps={{
|
||||
maxLength: maxLengthTextField,
|
||||
// maxLength: maxLengthTextField,
|
||||
}}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useCallback, useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
|
||||
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsOptions";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
|
||||
import SwitchDropDown from "./switchDropDown";
|
||||
|
||||
import type { QuizQuestionSelect } from "@frontend/squzanswerer";
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function DropDown({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const {onClickAddAnAnswer} = useAddAnswer();
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -100,7 +100,7 @@ export default function DropDown({ question, openBranchingPage, setOpenBranching
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -2,11 +2,11 @@ import { Box, Link, Popover, Typography, useMediaQuery, useTheme } from "@mui/ma
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionEmoji } from "@frontend/squzanswerer";
|
||||
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsOptions";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
|
||||
import EmojiAnswerItem from "./EmojiAnswerItem/EmojiAnswerItem";
|
||||
import SwitchEmoji from "./switchEmoji";
|
||||
|
||||
@ -18,7 +18,7 @@ interface Props {
|
||||
|
||||
export default function Emoji({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState<string>("setting");
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const {onClickAddAnAnswer} = useAddAnswer();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null);
|
||||
const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
|
||||
@ -29,9 +29,12 @@ export default function Emoji({ question, openBranchingPage, setOpenBranchingPag
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
|
||||
<AnswerDraggableList
|
||||
questionId={question.id}
|
||||
variants={question.content.variants.map((variant, index) => (
|
||||
variants={question.content.variants
|
||||
.filter(variant => !variant.isOwn ? true : question.content.own && variant.isOwn)
|
||||
.map((variant, index) => (
|
||||
<EmojiAnswerItem
|
||||
key={variant.id}
|
||||
index={index}
|
||||
@ -42,9 +45,12 @@ export default function Emoji({ question, openBranchingPage, setOpenBranchingPag
|
||||
setAnchorElement={setAnchorElement}
|
||||
setOpen={setOpen}
|
||||
setSelectedVariant={setSelectedVariant}
|
||||
isOwn={Boolean(variant?.isOwn)}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorElement}
|
||||
@ -115,7 +121,7 @@ export default function Emoji({ question, openBranchingPage, setOpenBranchingPag
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -2,6 +2,7 @@ import { ComponentPropsWithoutRef, useMemo } from "react";
|
||||
import AnswerItem from "../../AnswerDraggableList/AnswerItem";
|
||||
import VariantAdornment from "./VariantAdornment";
|
||||
import VariantAdornmentMobile from "./VariantAdornmentMobile";
|
||||
import { updateQuestion } from "@/stores/questions/actions";
|
||||
|
||||
type Props = Omit<
|
||||
ComponentPropsWithoutRef<typeof AnswerItem>,
|
||||
@ -11,6 +12,8 @@ type Props = Omit<
|
||||
setAnchorElement: React.Dispatch<React.SetStateAction<HTMLDivElement | null>>;
|
||||
setSelectedVariant: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isOwn: boolean;
|
||||
ownPlaceholder: string;
|
||||
};
|
||||
|
||||
export default function EmojiAnswerItem({
|
||||
@ -23,7 +26,11 @@ export default function EmojiAnswerItem({
|
||||
setAnchorElement,
|
||||
setSelectedVariant,
|
||||
setOpen,
|
||||
isOwn,
|
||||
ownPlaceholder,
|
||||
}: Props) {
|
||||
|
||||
|
||||
const addOrEditImageButton = useMemo(() => {
|
||||
return (
|
||||
!isTablet && (
|
||||
@ -77,6 +84,8 @@ export default function EmojiAnswerItem({
|
||||
variant={variant}
|
||||
additionalContent={addOrEditImageButton}
|
||||
additionalMobile={addOrEditImageButtonMobile}
|
||||
isOwn={isOwn}
|
||||
ownPlaceholder={ownPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,23 +1,37 @@
|
||||
import type { QuizQuestionEmoji, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import type { QuestionType, QuizQuestionEmoji, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import { memo } from "react";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
|
||||
type SettingEmojiProps = {
|
||||
question: QuizQuestionEmoji;
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isMulti: boolean;
|
||||
isOwn: boolean;
|
||||
isLargeCheck?: boolean;
|
||||
};
|
||||
|
||||
const SettingEmoji = memo<SettingEmojiProps>(function ({ questionId, isRequired, isMulti, isOwn }) {
|
||||
const SettingEmoji = memo<SettingEmojiProps>(function ({ question, questionId, isRequired, isLargeCheck, isMulti, isOwn }) {
|
||||
const theme = useTheme();
|
||||
const {switchOwn} = useAddAnswer();
|
||||
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const setOwnPlaceholder = (replText: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.ownPlaceholder = replText;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -29,7 +43,7 @@ const SettingEmoji = memo<SettingEmojiProps>(function ({ questionId, isRequired,
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
{/* <Box
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
@ -50,6 +64,17 @@ const SettingEmoji = memo<SettingEmojiProps>(function ({ questionId, isRequired,
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
{/* <CustomCheckbox
|
||||
dataCy="checkbox-long-text-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Многострочный ответ"}
|
||||
checked={isLargeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-multiple-answers"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
@ -67,12 +92,34 @@ const SettingEmoji = memo<SettingEmojiProps>(function ({ questionId, isRequired,
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={isOwn}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.own = target.checked;
|
||||
});
|
||||
switchOwn({question, checked:target.checked})
|
||||
}}
|
||||
/>
|
||||
</Box> */}
|
||||
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Подсказка "своего ответа"
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
maxLength={60}
|
||||
placeholder={"мой ответ: три"}
|
||||
value={ownPlaceholder}
|
||||
onChange={({ target }) => setOwnPlaceholder(target.value)}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
|
@ -12,10 +12,13 @@ export default function SwitchEmoji({ switchState = "setting", question }: Props
|
||||
case "setting":
|
||||
return (
|
||||
<SettingEmoji
|
||||
question={question}
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isOwn={question.content.own}
|
||||
isMulti={question.content.multi}
|
||||
isLargeCheck={question.content.isLargeCheck}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
);
|
||||
case "help":
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
} from "@root/quizes/actions";
|
||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||
import { createPortal } from "react-dom";
|
||||
import AddAnswer from "../../../assets/icons/questionsPage/addAnswer";
|
||||
import ArrowLeft from "../../../assets/icons/questionsPage/arrowLeft";
|
||||
import AddAnswer from "@/assets/icons/questionsPage/addAnswer";
|
||||
import ArrowLeft from "@/assets/icons/questionsPage/arrowLeft";
|
||||
import { FormDraggableList } from "./FormDraggableList";
|
||||
import {
|
||||
collapseAllQuestions,
|
||||
|
@ -3,12 +3,12 @@ import { Box } from "@mui/material";
|
||||
import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton";
|
||||
import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions";
|
||||
|
||||
import Answer from "../../../assets/icons/questionsPage/answer";
|
||||
import Date from "../../../assets/icons/questionsPage/date";
|
||||
import Download from "../../../assets/icons/questionsPage/download";
|
||||
import DropDown from "../../../assets/icons/questionsPage/drop_down";
|
||||
import Input from "../../../assets/icons/questionsPage/input";
|
||||
import Slider from "../../../assets/icons/questionsPage/slider";
|
||||
import Answer from "@/assets/icons/questionsPage/answer";
|
||||
import Date from "@/assets/icons/questionsPage/date";
|
||||
import Download from "@/assets/icons/questionsPage/download";
|
||||
import DropDown from "@/assets/icons/questionsPage/drop_down";
|
||||
import Input from "@/assets/icons/questionsPage/input";
|
||||
import Slider from "@/assets/icons/questionsPage/slider";
|
||||
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import { createTypedQuestion } from "@root/questions/actions";
|
||||
|
@ -3,9 +3,9 @@ import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { useEffect, useState } from "react";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import InfoIcon from "@/assets/icons/InfoIcon";
|
||||
import type { QuizQuestionText } from "@frontend/squzanswerer";
|
||||
import ButtonsOptionsAndPict from "../QuestionOptions/ButtonsOptionsAndPict";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
|
||||
import SwitchTextField from "./switchTextField";
|
||||
|
||||
interface Props {
|
||||
@ -90,9 +90,9 @@ export default function OwnTextField({ question, openBranchingPage, setOpenBranc
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
|
@ -0,0 +1,240 @@
|
||||
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
copyQuestion,
|
||||
deleteQuestion,
|
||||
deleteQuestionWithTimeout,
|
||||
} from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
|
||||
import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { memo, useState } from "react";
|
||||
import { CopyIcon } from "@/assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "@/assets/icons/questionsPage/branching";
|
||||
import { DeleteIcon } from "@/assets/icons/questionsPage/deleteIcon";
|
||||
import ImgIcon from "@/assets/icons/questionsPage/imgIcon";
|
||||
import SettingIcon from "@/assets/icons/questionsPage/settingIcon";
|
||||
import { DeleteBranchingQuestionModal } from "./DeleteBranchingQuestionModal";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
setSwitchState: (data: string) => void;
|
||||
openBranchingPage: boolean;
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
questionId: string;
|
||||
questionType: QuestionType;
|
||||
questionContentId: string;
|
||||
questionHasParent: boolean;
|
||||
};
|
||||
|
||||
const IgnoreImage = ["images", "emoji", "number", "date", "select", "file", "rating"]
|
||||
|
||||
const ButtonsOptions = memo<Props>(function ({
|
||||
setSwitchState,
|
||||
switchState,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
questionId,
|
||||
questionType,
|
||||
questionContentId,
|
||||
questionHasParent,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
const isQuestionFirst = useQuestionsStore((state) => state.questions[0]?.id === questionId,);
|
||||
|
||||
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isIgnoreImage = !IgnoreImage.includes(questionType)
|
||||
|
||||
const [buttonHover, setButtonHover] = useState<string>("");
|
||||
const [openedReallyChangingModal, setOpenedReallyChangingModal] = useState<boolean>(false);
|
||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
background: "#f2f3f7",
|
||||
height: isMobile ? "92px" : "70px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||
display: "flex",
|
||||
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||
gap: "6px",
|
||||
maxWidth: isMobile ? "200px" : undefined,
|
||||
}}
|
||||
>
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => setButtonHover("setting")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
switchState === "setting" ? setSwitchState("") : setSwitchState("setting");
|
||||
}}
|
||||
sx={{
|
||||
maxWidth: "104px",
|
||||
minWidth: isIconMobile ? "30px" : "64px",
|
||||
height: "30px",
|
||||
backgroundColor:
|
||||
switchState === "setting"
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main,
|
||||
"&:hover": {
|
||||
color:
|
||||
switchState === "setting" ? theme.palette.grey3.main : null,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingIcon
|
||||
color={
|
||||
buttonHover === "setting"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "setting"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Настройки"}
|
||||
</MiniButtonSetting>
|
||||
{questionType !== "text" && (
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => setButtonHover("branching")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
setOpenBranchingPage(true);
|
||||
updateDesireToOpenABranchingModal(questionContentId);
|
||||
}}
|
||||
sx={{
|
||||
display: quiz.config.type === "form" ? "none" : "flex",
|
||||
height: "30px",
|
||||
maxWidth: "103px",
|
||||
minWidth: isIconMobile ? "30px" : "64px",
|
||||
backgroundColor:
|
||||
switchState === "branching"
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === "branching"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
"&:hover": {
|
||||
color:
|
||||
switchState === "branching" ? theme.palette.grey3.main : null,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Branching
|
||||
color={
|
||||
buttonHover === "branching"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "branching"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Ветвление"}
|
||||
</MiniButtonSetting>
|
||||
)}
|
||||
{isIgnoreImage &&
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => setButtonHover("image")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
switchState === "image" ? setSwitchState("") : setSwitchState("image");
|
||||
}}
|
||||
sx={{
|
||||
height: "30px",
|
||||
maxWidth: "123px",
|
||||
minWidth: isIconMobile ? "30px" : "64px",
|
||||
backgroundColor:
|
||||
switchState === "image"
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === "image" ? "#ffffff" : theme.palette.grey3.main,
|
||||
"&:hover": {
|
||||
color: switchState === "image" ? theme.palette.grey3.main : null,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ImgIcon
|
||||
color={
|
||||
buttonHover === "image"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "image"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Изображение"}
|
||||
</MiniButtonSetting>
|
||||
}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px" }}
|
||||
onClick={() => copyQuestion(questionId, quiz.backendId)}
|
||||
>
|
||||
<CopyIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
{(quiz?.config.type !== "form" || !isQuestionFirst) && (
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
if (questionType === null) {
|
||||
deleteQuestion(questionId);
|
||||
}
|
||||
if (questionHasParent) {
|
||||
setOpenDelete(true);
|
||||
} else {
|
||||
deleteQuestionWithTimeout(questionId, () =>
|
||||
DeleteFunction(questionId),
|
||||
);
|
||||
}
|
||||
}}
|
||||
data-cy="delete-question"
|
||||
>
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
<DeleteBranchingQuestionModal
|
||||
open={openDelete}
|
||||
onclose={() => setOpenDelete(false)}
|
||||
questionId={questionId}
|
||||
/>
|
||||
<ReallyChangingModal
|
||||
opened={openedReallyChangingModal}
|
||||
onClose={() => setOpenedReallyChangingModal(false)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
export default ButtonsOptions;
|
@ -0,0 +1,66 @@
|
||||
import { deleteQuestionWithTimeout } from "@/stores/questions/actions";
|
||||
import { DeleteFunction } from "@/utils/deleteFunc";
|
||||
import { Box, Button, Modal, Typography } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onclose: () => void;
|
||||
questionId: string;
|
||||
}
|
||||
|
||||
export const DeleteBranchingQuestionModal = ({
|
||||
open,
|
||||
onclose,
|
||||
questionId,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onclose}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
padding: "30px",
|
||||
borderRadius: "10px",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ textAlign: "center" }}
|
||||
>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={onclose}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(questionId, () => DeleteFunction(questionId));
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
184
src/pages/Questions/QuestionOptions/ButtonsLayout/lalala.tsx
Normal file
184
src/pages/Questions/QuestionOptions/ButtonsLayout/lalala.tsx
Normal file
@ -0,0 +1,184 @@
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import type { SxProps } from "@mui/material";
|
||||
import { Box, Button, IconButton, Modal, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { memo, useState } from "react";
|
||||
import { CopyIcon } from "@/assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "@/assets/icons/questionsPage/branching";
|
||||
import SettingIcon from "@/assets/icons/questionsPage/settingIcon";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import type { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { DeleteBranchingQuestionModal } from "./DeleteBranchingQuestionModal";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
setSwitchState: (data: string) => void;
|
||||
questionId: string;
|
||||
questionContentId: string;
|
||||
questionType: QuestionType;
|
||||
questionHasParent: boolean;
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
|
||||
export default memo<Props>(function ({
|
||||
setSwitchState,
|
||||
switchState,
|
||||
questionId,
|
||||
questionContentId,
|
||||
questionType,
|
||||
questionHasParent,
|
||||
setOpenBranchingPage,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
const quiz = useCurrentQuiz();
|
||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||
const isQuestionFirst = useQuestionsStore((state) => state.questions[0]?.id === questionId);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
const openedModal = () => {
|
||||
setOpenBranchingPage(true);
|
||||
updateDesireToOpenABranchingModal(questionContentId);
|
||||
};
|
||||
|
||||
const buttonSetting: {
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
value: string;
|
||||
myFunc?: any;
|
||||
}[] = [
|
||||
{
|
||||
icon: <SettingIcon color={switchState === "setting" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: <Branching color={switchState === "branching" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: (question: AnyTypedQuizQuestion) => {
|
||||
setOpenBranchingPage(true);
|
||||
updateDesireToOpenABranchingModal(question.content.id);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
background: "#f2f3f7",
|
||||
height: isMobile ? "92px" : "70px",
|
||||
}}
|
||||
>
|
||||
ButtonsOptions
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||
display: "flex",
|
||||
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
{buttonSetting.map(({ icon, title, value, myFunc }) => (
|
||||
<Box key={value}>
|
||||
{value === "branching" ? (
|
||||
["page", "text", "date", "number"].includes(questionType) ? null : (
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
openedModal();
|
||||
}}
|
||||
sx={{
|
||||
display: quiz.config.type === "form" ? "none" : "flex",
|
||||
backgroundColor: switchState === value ? theme.palette.brightPurple.main : "transparent",
|
||||
color: switchState === value ? "#ffffff" : theme.palette.grey3.main,
|
||||
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||
height: "30px",
|
||||
"&:hover": {
|
||||
color: theme.palette.grey3.main,
|
||||
"& path": { stroke: theme.palette.grey3.main },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{isWrappMiniButtonSetting ? null : title}
|
||||
</MiniButtonSetting>
|
||||
)
|
||||
) : (
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
setSwitchState(value);
|
||||
myFunc();
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: switchState === value ? theme.palette.brightPurple.main : "transparent",
|
||||
color: switchState === value ? "#ffffff" : theme.palette.grey3.main,
|
||||
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||
height: "30px",
|
||||
"&:hover": {
|
||||
color: theme.palette.grey3.main,
|
||||
"& path": { stroke: theme.palette.grey3.main },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{isWrappMiniButtonSetting ? null : title}
|
||||
</MiniButtonSetting>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => copyQuestion(questionId, quiz.backendId)}
|
||||
>
|
||||
<CopyIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
{(quiz?.config.type !== "form" || !isQuestionFirst) && (
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
if (questionType === null) {
|
||||
deleteQuestion(questionId);
|
||||
}
|
||||
if (questionHasParent) {
|
||||
setOpenDelete(true);
|
||||
} else {
|
||||
deleteQuestionWithTimeout(questionId, () => DeleteFunction(questionId));
|
||||
}
|
||||
}}
|
||||
data-cy="delete-question"
|
||||
>
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
)}
|
||||
<DeleteBranchingQuestionModal
|
||||
open={openDelete}
|
||||
onclose={() => setOpenDelete(false)}
|
||||
questionId={questionId}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
@ -8,15 +8,16 @@ import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { memo, useState } from "react";
|
||||
import { CopyIcon } from "../../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../../assets/icons/questionsPage/branching";
|
||||
import SettingIcon from "../../../assets/icons/questionsPage/settingIcon";
|
||||
import { CopyIcon } from "@/assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "@/assets/icons/questionsPage/branching";
|
||||
import SettingIcon from "@/assets/icons/questionsPage/settingIcon";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import type { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { DeleteBranchingQuestionModal } from "./ButtonsLayout/DeleteBranchingQuestionModal";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
setSwitchState: (data: string) => void;
|
||||
questionId: string;
|
||||
questionContentId: string;
|
||||
questionType: QuestionType;
|
||||
@ -25,8 +26,8 @@ interface Props {
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
const ButtonsOptions = memo<Props>(function ({
|
||||
SSHC,
|
||||
export default memo<Props>(function ({
|
||||
setSwitchState,
|
||||
switchState,
|
||||
questionId,
|
||||
questionContentId,
|
||||
@ -54,21 +55,21 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
value: string;
|
||||
myFunc?: any;
|
||||
}[] = [
|
||||
{
|
||||
icon: <SettingIcon color={switchState === "setting" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: <Branching color={switchState === "branching" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: (question: AnyTypedQuizQuestion) => {
|
||||
setOpenBranchingPage(true);
|
||||
updateDesireToOpenABranchingModal(question.content.id);
|
||||
{
|
||||
icon: <SettingIcon color={switchState === "setting" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
icon: <Branching color={switchState === "branching" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: (question: AnyTypedQuizQuestion) => {
|
||||
setOpenBranchingPage(true);
|
||||
updateDesireToOpenABranchingModal(question.content.id);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -81,6 +82,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
height: isMobile ? "92px" : "70px",
|
||||
}}
|
||||
>
|
||||
ButtonsOptions
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||
@ -118,7 +120,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
SSHC(value);
|
||||
setSwitchState(value);
|
||||
myFunc();
|
||||
}}
|
||||
sx={{
|
||||
@ -170,60 +172,12 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
)}
|
||||
<Modal
|
||||
<DeleteBranchingQuestionModal
|
||||
open={openDelete}
|
||||
onClose={() => setOpenDelete(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
padding: "30px",
|
||||
borderRadius: "10px",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ textAlign: "center" }}
|
||||
>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что
|
||||
хотите удалить вопрос?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => setOpenDelete(false)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(questionId, () => DeleteFunction(questionId));
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
onclose={() => setOpenDelete(false)}
|
||||
questionId={questionId}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
ButtonsOptions.displayName = "ButtonsOptions";
|
||||
|
||||
export default ButtonsOptions;
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import {
|
||||
Box,
|
||||
@ -20,25 +21,28 @@ import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
|
||||
import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { memo, useState } from "react";
|
||||
import { CopyIcon } from "../../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../../assets/icons/questionsPage/branching";
|
||||
import { DeleteIcon } from "../../../assets/icons/questionsPage/deleteIcon";
|
||||
import ImgIcon from "../../../assets/icons/questionsPage/imgIcon";
|
||||
import SettingIcon from "../../../assets/icons/questionsPage/settingIcon";
|
||||
import { CopyIcon } from "@/assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "@/assets/icons/questionsPage/branching";
|
||||
import { DeleteIcon } from "@/assets/icons/questionsPage/deleteIcon";
|
||||
import ImgIcon from "@/assets/icons/questionsPage/imgIcon";
|
||||
import SettingIcon from "@/assets/icons/questionsPage/settingIcon";
|
||||
import { DeleteBranchingQuestionModal } from "./ButtonsLayout/DeleteBranchingQuestionModal";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
setSwitchState: (data: string) => void;
|
||||
openBranchingPage: boolean;
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
questionId: string;
|
||||
questionType: QuestionType;
|
||||
questionContentId: string;
|
||||
questionHasParent: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
SSHC,
|
||||
const IgnoreImage = ["images", "emoji", "number", "date", "select", "file", "rating"]
|
||||
|
||||
const ButtonsOptions = memo<Props>(function ({
|
||||
setSwitchState,
|
||||
switchState,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
@ -47,17 +51,17 @@ const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
questionContentId,
|
||||
questionHasParent,
|
||||
}) {
|
||||
const [buttonHover, setButtonHover] = useState<string>("");
|
||||
const [openedReallyChangingModal, setOpenedReallyChangingModal] =
|
||||
useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
|
||||
const quiz = useCurrentQuiz();
|
||||
const isQuestionFirst = useQuestionsStore((state) => state.questions[0]?.id === questionId,);
|
||||
|
||||
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isIgnoreImage = !IgnoreImage.includes(questionType)
|
||||
|
||||
const [buttonHover, setButtonHover] = useState<string>("");
|
||||
const [openedReallyChangingModal, setOpenedReallyChangingModal] = useState<boolean>(false);
|
||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||
const isQuestionFirst = useQuestionsStore(
|
||||
(state) => state.questions[0]?.id === questionId,
|
||||
);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
@ -85,7 +89,7 @@ const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
onMouseEnter={() => setButtonHover("setting")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
SSHC("setting");
|
||||
switchState === "setting" ? setSwitchState("") : setSwitchState("setting");
|
||||
}}
|
||||
sx={{
|
||||
maxWidth: "104px",
|
||||
@ -153,38 +157,40 @@ const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
{isIconMobile ? null : "Ветвление"}
|
||||
</MiniButtonSetting>
|
||||
)}
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => setButtonHover("image")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
SSHC("image");
|
||||
}}
|
||||
sx={{
|
||||
height: "30px",
|
||||
maxWidth: "123px",
|
||||
minWidth: isIconMobile ? "30px" : "64px",
|
||||
backgroundColor:
|
||||
switchState === "image"
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === "image" ? "#ffffff" : theme.palette.grey3.main,
|
||||
"&:hover": {
|
||||
color: switchState === "image" ? theme.palette.grey3.main : null,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ImgIcon
|
||||
color={
|
||||
buttonHover === "image"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "image"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Изображение"}
|
||||
</MiniButtonSetting>
|
||||
{isIgnoreImage &&
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => setButtonHover("image")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
switchState === "image" ? setSwitchState("") : setSwitchState("image");
|
||||
}}
|
||||
sx={{
|
||||
height: "30px",
|
||||
maxWidth: "123px",
|
||||
minWidth: isIconMobile ? "30px" : "64px",
|
||||
backgroundColor:
|
||||
switchState === "image"
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === "image" ? "#ffffff" : theme.palette.grey3.main,
|
||||
"&:hover": {
|
||||
color: switchState === "image" ? theme.palette.grey3.main : null,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ImgIcon
|
||||
color={
|
||||
buttonHover === "image"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "image"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Изображение"}
|
||||
</MiniButtonSetting>
|
||||
}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@ -217,52 +223,12 @@ const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
)}
|
||||
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
padding: "30px",
|
||||
borderRadius: "10px",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||
потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => setOpenDelete(false)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(questionId, () =>
|
||||
DeleteFunction(questionId),
|
||||
);
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Box>
|
||||
<DeleteBranchingQuestionModal
|
||||
open={openDelete}
|
||||
onclose={() => setOpenDelete(false)}
|
||||
questionId={questionId}
|
||||
/>
|
||||
<ReallyChangingModal
|
||||
opened={openedReallyChangingModal}
|
||||
onClose={() => setOpenedReallyChangingModal(false)}
|
||||
@ -271,6 +237,4 @@ const ButtonsOptionsAndPict = memo<Props>(function ({
|
||||
);
|
||||
});
|
||||
|
||||
ButtonsOptionsAndPict.displayName = "ButtonsOptionsAndPict";
|
||||
|
||||
export default ButtonsOptionsAndPict;
|
||||
export default ButtonsOptions;
|
||||
|
@ -12,7 +12,7 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DataOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
export default function DateOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -64,7 +64,7 @@ export default function DataOptions({ question, openBranchingPage, setOpenBranch
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -6,11 +6,11 @@ import type { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
type SettingsDataProps = {
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isDateRange: boolean;
|
||||
isRange: boolean;
|
||||
isTime: boolean;
|
||||
};
|
||||
|
||||
export default function SettingsData({ questionId, isRequired, isDateRange, isTime }: SettingsDataProps) {
|
||||
export default function SettingsData({ questionId, isRequired, isRange, isTime }: SettingsDataProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
@ -28,7 +28,7 @@ export default function SettingsData({ questionId, isRequired, isDateRange, isTi
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
{/* <Box
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
@ -53,14 +53,14 @@ export default function SettingsData({ questionId, isRequired, isDateRange, isTi
|
||||
dataCy="checkbox-dateRange"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор диапазона дат"}
|
||||
checked={isDateRange}
|
||||
checked={isRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionDate>(questionId, (question) => {
|
||||
question.content.dateRange = target.checked;
|
||||
question.content.isRange = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
{/* <CustomCheckbox
|
||||
dataCy="checkbox-time"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор времени"}
|
||||
@ -70,8 +70,8 @@ export default function SettingsData({ questionId, isRequired, isDateRange, isTi
|
||||
question.content.time = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box> */}
|
||||
/> */}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
|
@ -14,7 +14,7 @@ export default function SwitchData({ switchState = "setting", question }: Props)
|
||||
<SettingData
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isDateRange={question.content.dateRange}
|
||||
isRange={question.content.isRange}
|
||||
isTime={question.content.time}
|
||||
/>
|
||||
);
|
||||
|
@ -0,0 +1,80 @@
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import InfoIcon from "@/assets/icons/InfoIcon";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import SwitchDate from "./switchDate";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionDate;
|
||||
openBranchingPage: boolean;
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DateOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "auto" : "100%",
|
||||
maxWidth: "493px",
|
||||
display: "flex",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "18px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "12px",
|
||||
mb: "20px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Пользователю будет предложено выбрать дату
|
||||
</Typography>
|
||||
{isMobile ? (
|
||||
<TooltipClickInfo title={"Выбор даты."} />
|
||||
) : (
|
||||
<Tooltip
|
||||
title="Выбор даты."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchDate
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
110
src/pages/Questions/QuestionOptions/DateOptions/settingDate.tsx
Normal file
110
src/pages/Questions/QuestionOptions/DateOptions/settingDate.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import type { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
|
||||
type SettingsDataProps = {
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isRange: boolean;
|
||||
isTime: boolean;
|
||||
};
|
||||
|
||||
export default function SettingsData({ questionId, isRequired, isRange, isTime }: SettingsDataProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : "row",
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-dateRange"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор диапазона дат"}
|
||||
checked={isRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionDate>(questionId, (question) => {
|
||||
question.content.isRange = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/* <CustomCheckbox
|
||||
dataCy="checkbox-time"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор времени"}
|
||||
checked={isTime}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionDate>(questionId, (question) => {
|
||||
question.content.time = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-optional-question"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!isRequired}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionDate>(questionId, (question) => {
|
||||
question.content.required = !target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../../helpQuestions";
|
||||
import SettingDate from "./settingDate";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
question: QuizQuestionDate;
|
||||
}
|
||||
|
||||
export default function SwitchData({ switchState = "setting", question }: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return (
|
||||
<SettingDate
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isRange={question.content.isRange}
|
||||
isTime={question.content.time}
|
||||
/>
|
||||
);
|
||||
case "help":
|
||||
return (
|
||||
<HelpQuestions
|
||||
questionId={question.id}
|
||||
hintText={question.content.hint.text}
|
||||
hintVideo={question.content.hint.video}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
@ -1,16 +1,22 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { addQuestionVariant, clearQuestionImages, uploadQuestionImage } from "@root/questions/actions";
|
||||
import {
|
||||
addQuestionVariant,
|
||||
clearQuestionImages,
|
||||
uploadQuestionImage,
|
||||
} from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionVarImg } from "@frontend/squzanswerer";
|
||||
import type { QuizQuestionVarImg } from "@frontend/squzanswerer/dist-package/model/questionTypes/varimg";
|
||||
import { useDisclosure } from "@/utils/useDisclosure";
|
||||
import { AnswerDraggableList } from "../../AnswerDraggableList";
|
||||
import ImageEditAnswerItem from "../../AnswerDraggableList/ImageEditAnswerItem";
|
||||
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import { UploadImageModal } from "../../UploadImage/UploadImageModal";
|
||||
import SwitchOptionsAndPict from "./switchOptionsAndPict";
|
||||
import { CropModalInit } from "@/ui_kit/Modal/CropModal";
|
||||
|
||||
import imge from "@/assets/card-1.png"
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVarImg;
|
||||
@ -18,33 +24,48 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function OptionsAndPicture({ question, setOpenBranchingPage }: Props) {
|
||||
export default function OptionsAndPicture({
|
||||
question,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const [openCropModal, setOpenCropModal] = useState(false);
|
||||
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
)
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
});
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
const url = await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
},
|
||||
);
|
||||
setOpenCropModal(true)
|
||||
|
||||
setPictureUploading(false);
|
||||
};
|
||||
@ -55,7 +76,9 @@ export default function OptionsAndPicture({ question, setOpenBranchingPage }: Pr
|
||||
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
@ -82,10 +105,12 @@ export default function OptionsAndPicture({ question, setOpenBranchingPage }: Pr
|
||||
largeCheck={question.content.largeCheck}
|
||||
variant={variant}
|
||||
isMobile={isMobile}
|
||||
openCropModal={openCropModal}
|
||||
openCropModal={() => {setOpenCropModal(true)}}
|
||||
openImageUploadModal={openImageUploadModal}
|
||||
pictureUploding={pictureUploding}
|
||||
setSelectedVariantId={setSelectedVariantId}
|
||||
isOwn={Boolean(variant?.isOwn)}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
@ -94,17 +119,16 @@ export default function OptionsAndPicture({ question, setOpenBranchingPage }: Pr
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
if (selectedVariantId) clearQuestionImages(question.id, selectedVariantId);
|
||||
}}
|
||||
cropAspectRatio={{ width: 300, height: 300 }}
|
||||
<CropModalInit
|
||||
originalImageUrl={variant?.originalImageUrl}
|
||||
editedUrlImagesList={variant?.editedUrlImagesList}
|
||||
questionId={question.id.toString()}
|
||||
questionType={question.type}
|
||||
quizId={quizQid}
|
||||
variantId={variant?.id}
|
||||
open={openCropModal}
|
||||
selfClose={() => setOpenCropModal(false)}
|
||||
setPictureUploading={setPictureUploading}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
@ -152,9 +176,9 @@ export default function OptionsAndPicture({ question, setOpenBranchingPage }: Pr
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
import type { QuizQuestionVarImg, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
@ -9,16 +10,23 @@ type SettingOptionsAndPictProps = {
|
||||
questionId: string;
|
||||
replText: string;
|
||||
isRequired: boolean;
|
||||
isLargeCheck: boolean;
|
||||
isOwn: boolean;
|
||||
ownPlaceholder?: boolean;
|
||||
isMulti?: boolean;
|
||||
question: QuizQuestionVarImg;
|
||||
};
|
||||
|
||||
const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ questionId, replText, isRequired, isOwn }) {
|
||||
const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ question, questionId, ownPlaceholder, isMulti, isLargeCheck, replText, isRequired, isOwn }) {
|
||||
const theme = useTheme();
|
||||
const { switchOwn } = useAddAnswer();
|
||||
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
||||
|
||||
|
||||
const setReplText = (replText: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
if (question.type !== "varimg") return;
|
||||
@ -39,7 +47,7 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ quest
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
display: "flex",
|
||||
@ -49,7 +57,7 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ quest
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{/* <Typography
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
@ -58,18 +66,27 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ quest
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography> */}
|
||||
{/* <CustomCheckbox
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-own-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={isOwn}
|
||||
handleChange={({ target }) => {
|
||||
switchOwn({ question, checked: target.checked })
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-long-text-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Многострочный ответ"}
|
||||
checked={isLargeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.own = target.checked;
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
/>
|
||||
{!isWrappColumn && (
|
||||
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
@ -113,6 +130,7 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ quest
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mt: isMobile ? "10px" : ""
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
|
@ -13,10 +13,13 @@ export default function SwitchOptionsAndPict({ switchState = "setting", question
|
||||
case "setting":
|
||||
return (
|
||||
<SettingOptionsAndPict
|
||||
question={question}
|
||||
questionId={question.id}
|
||||
replText={question.content.replText}
|
||||
isRequired={question.content.required}
|
||||
isOwn={question.content.own}
|
||||
isLargeCheck={question.content.largeCheck}
|
||||
isMulti={question.content.multi}
|
||||
/>
|
||||
);
|
||||
case "help":
|
||||
|
@ -1,70 +1,78 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { clearQuestionImages, uploadQuestionImage } from "@root/questions/actions";
|
||||
import {
|
||||
clearQuestionImages,
|
||||
uploadQuestionImage,
|
||||
} from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionImages } from "@frontend/squzanswerer";
|
||||
import { useAddAnswer } from "../../../../utils/hooks/useAddAnswer";
|
||||
import { useDisclosure } from "../../../../utils/useDisclosure";
|
||||
import { useMemo, useState } from "react";
|
||||
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionVarImg } from "@frontend/squzanswerer/dist-package/model/questionTypes/varimg";
|
||||
|
||||
//@/model/questionTypes/images";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
import { useDisclosure } from "@/utils/useDisclosure";
|
||||
import { AnswerDraggableList } from "../../AnswerDraggableList";
|
||||
import ImageEditAnswerItem from "../../AnswerDraggableList/ImageEditAnswerItem";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import { UploadImageModal } from "../../UploadImage/UploadImageModal";
|
||||
import SwitchAnswerOptionsPict from "./switchOptionsPict";
|
||||
|
||||
import imge from "@/assets/card-1.png"
|
||||
import { CropModalInit } from "@/ui_kit/Modal/CropModal";
|
||||
interface Props {
|
||||
question: QuizQuestionImages;
|
||||
question: QuizQuestionVarImg;
|
||||
openBranchingPage: boolean;
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function OptionsPicture({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
export default function OptionsPicture({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const {onClickAddAnAnswer} = useAddAnswer();
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [openCropModal, setOpenCropModal] = useState(false);
|
||||
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
)
|
||||
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
});
|
||||
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
const url = await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
},
|
||||
);
|
||||
|
||||
setOpenCropModal(true)
|
||||
|
||||
setPictureUploading(false);
|
||||
};
|
||||
|
||||
function handleCropModalSaveClick(imageBlob: Blob) {
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
@ -79,10 +87,12 @@ export default function OptionsPicture({ question, openBranchingPage, setOpenBra
|
||||
largeCheck={question.content.largeCheck}
|
||||
variant={variant}
|
||||
isMobile={isMobile}
|
||||
openCropModal={openCropModal}
|
||||
openCropModal={() => {setOpenCropModal(true)}}
|
||||
openImageUploadModal={openImageUploadModal}
|
||||
pictureUploding={pictureUploding}
|
||||
setSelectedVariantId={setSelectedVariantId}
|
||||
isOwn={Boolean(variant?.isOwn)}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
@ -91,17 +101,16 @@ export default function OptionsPicture({ question, openBranchingPage, setOpenBra
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
if (selectedVariantId) clearQuestionImages(question.id, selectedVariantId);
|
||||
}}
|
||||
cropAspectRatio={{ width: 452, height: 300 }}
|
||||
<CropModalInit
|
||||
originalImageUrl={variant?.originalImageUrl}
|
||||
editedUrlImagesList={variant?.editedUrlImagesList}
|
||||
questionId={question.id.toString()}
|
||||
questionType={question.type}
|
||||
quizId={quizQid}
|
||||
variantId={variant?.id}
|
||||
open={openCropModal}
|
||||
selfClose={() => setOpenCropModal(false)}
|
||||
setPictureUploading={setPictureUploading}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Link
|
||||
@ -137,7 +146,7 @@ export default function OptionsPicture({ question, openBranchingPage, setOpenBra
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -0,0 +1,319 @@
|
||||
import type { QuizQuestionImages, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import { memo } from "react";
|
||||
import FormatIcon1 from "@/assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "@/assets/icons/questionsPage/FormatIcon2";
|
||||
import ProportionsIcon11 from "@/assets/icons/questionsPage/ProportionsIcon11";
|
||||
import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12";
|
||||
import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
|
||||
type Proportion = "1:1" | "1:2" | "2:1";
|
||||
|
||||
type ProportionItem = {
|
||||
value: Proportion;
|
||||
icon: (props: { color: string }) => JSX.Element;
|
||||
};
|
||||
|
||||
const PROPORTIONS: ProportionItem[] = [
|
||||
{ value: "1:1", icon: ProportionsIcon11 },
|
||||
{ value: "1:2", icon: ProportionsIcon21 },
|
||||
{ value: "2:1", icon: ProportionsIcon12 },
|
||||
];
|
||||
|
||||
type Format = "carousel" | "masonry";
|
||||
|
||||
type FormatItem = {
|
||||
value: Format;
|
||||
icon: (props: { color: string }) => JSX.Element;
|
||||
};
|
||||
|
||||
const FORMATS: FormatItem[] = [
|
||||
{ value: "carousel", icon: FormatIcon2 },
|
||||
{ value: "masonry", icon: FormatIcon1 },
|
||||
];
|
||||
|
||||
type SettingOptionsPictProps = {
|
||||
question: QuizQuestionVariant;
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isMulti: boolean;
|
||||
isOwn: boolean;
|
||||
proportions: Proportion;
|
||||
format: Format;
|
||||
ownPlaceholder?: boolean;
|
||||
isLargeCheck?: boolean;
|
||||
};
|
||||
|
||||
const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
|
||||
question,
|
||||
questionId,
|
||||
isRequired,
|
||||
ownPlaceholder, isMulti, isLargeCheck,
|
||||
isOwn,
|
||||
proportions,
|
||||
format,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const setOwnPlaceholder = (replText: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.ownPlaceholder = replText;
|
||||
});
|
||||
};
|
||||
const {switchOwn} = useAddAnswer();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : null,
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{/* <Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Пропорции
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{PROPORTIONS.map((proportionItem, index) => (
|
||||
<SelectIconButton
|
||||
key={index}
|
||||
Icon={proportionItem.icon}
|
||||
isActive={proportionItem.value === proportions}
|
||||
onClick={() => {
|
||||
updateQuestion<QuizQuestionImages>(questionId, (question) => {
|
||||
if (question.type !== "images") return;
|
||||
question.content.xy = proportionItem.value;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
{/* <CustomCheckbox
|
||||
dataCy="checkbox-long-text-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Многострочный ответ"}
|
||||
checked={isLargeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-multiple-answers"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={isMulti}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.multi = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-own-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={isOwn}
|
||||
handleChange={({ target }) => {
|
||||
switchOwn({question, checked:target.checked})
|
||||
}}
|
||||
/>
|
||||
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Подсказка "своего ответа"
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
maxLength={60}
|
||||
placeholder={"мой ответ: три"}
|
||||
value={ownPlaceholder}
|
||||
onChange={({ target }) => setOwnPlaceholder(target.value)}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{/* <Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Формат
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{FORMATS.map((formatItem, index) => (
|
||||
<SelectIconButton
|
||||
key={index}
|
||||
Icon={formatItem.icon}
|
||||
isActive={formatItem.value === format}
|
||||
onClick={() => {
|
||||
updateQuestion<QuizQuestionImages>(questionId, (question) => {
|
||||
if (question.type !== "images") return;
|
||||
question.content.format = formatItem.value;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>Настройки вопросов</Typography>
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-optional-question"
|
||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!isRequired}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestion<QuizQuestionImages>(questionId, (question) => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.required = !target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
SettingOptionsPict.displayName = "SettingOptionsPict";
|
||||
|
||||
export default SettingOptionsPict;
|
||||
|
||||
interface Props {
|
||||
Icon: (props: { color: string }) => JSX.Element;
|
||||
isActive?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="outlined"
|
||||
startIcon={<Icon color={isActive ? theme.palette.navbarbg.main : theme.palette.brightPurple.main} />}
|
||||
sx={{
|
||||
backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc",
|
||||
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
color: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
|
||||
p: "7px",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
minWidth: 0,
|
||||
"& .MuiButton-startIcon": {
|
||||
mr: 0,
|
||||
ml: 0,
|
||||
},
|
||||
"&:hover": {
|
||||
border: "none",
|
||||
borderColor: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -3,11 +3,13 @@ import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import { memo } from "react";
|
||||
import FormatIcon1 from "../../../../assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "../../../../assets/icons/questionsPage/FormatIcon2";
|
||||
import ProportionsIcon11 from "../../../../assets/icons/questionsPage/ProportionsIcon11";
|
||||
import ProportionsIcon12 from "../../../../assets/icons/questionsPage/ProportionsIcon12";
|
||||
import ProportionsIcon21 from "../../../../assets/icons/questionsPage/ProportionsIcon21";
|
||||
import FormatIcon1 from "@/assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "@/assets/icons/questionsPage/FormatIcon2";
|
||||
import ProportionsIcon11 from "@/assets/icons/questionsPage/ProportionsIcon11";
|
||||
import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12";
|
||||
import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
|
||||
type Proportion = "1:1" | "1:2" | "2:1";
|
||||
|
||||
@ -34,19 +36,23 @@ const FORMATS: FormatItem[] = [
|
||||
{ value: "masonry", icon: FormatIcon1 },
|
||||
];
|
||||
|
||||
type SettingOpytionsPictProps = {
|
||||
type SettingOptionsPictProps = {
|
||||
question: QuizQuestionVariant;
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isMulti: boolean;
|
||||
isOwn: boolean;
|
||||
proportions: Proportion;
|
||||
format: Format;
|
||||
ownPlaceholder?: boolean;
|
||||
isLargeCheck?: boolean;
|
||||
};
|
||||
|
||||
const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
|
||||
question,
|
||||
questionId,
|
||||
isRequired,
|
||||
isMulti,
|
||||
ownPlaceholder, isMulti, isLargeCheck,
|
||||
isOwn,
|
||||
proportions,
|
||||
format,
|
||||
@ -55,6 +61,15 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const setOwnPlaceholder = (replText: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.ownPlaceholder = replText;
|
||||
});
|
||||
};
|
||||
const {switchOwn} = useAddAnswer();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -68,8 +83,8 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
{/* <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
<Box
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{/* <Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
@ -110,7 +125,7 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
@ -132,6 +147,17 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
{/* <CustomCheckbox
|
||||
dataCy="checkbox-long-text-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Многострочный ответ"}
|
||||
checked={isLargeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-multiple-answers"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
@ -149,13 +175,35 @@ const SettingOptionsPict = memo<SettingOpytionsPictProps>(function ({
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={isOwn}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.own = target.checked;
|
||||
});
|
||||
switchOwn({question, checked:target.checked})
|
||||
}}
|
||||
/>
|
||||
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Подсказка "своего ответа"
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
maxLength={60}
|
||||
placeholder={"мой ответ: три"}
|
||||
value={ownPlaceholder}
|
||||
onChange={({ target }) => setOwnPlaceholder(target.value)}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
</Box> */}
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{/* <Box
|
||||
sx={{
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { QuizQuestionImages } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../../helpQuestions";
|
||||
import SettingOpytionsPict from "./settingOpytionsPict";
|
||||
import SettingOptionsPict from "./settingOptionsPict";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
@ -11,13 +11,17 @@ export default function SwitchAnswerOptionsPict({ switchState = "setting", quest
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return (
|
||||
<SettingOpytionsPict
|
||||
<SettingOptionsPict
|
||||
question={question}
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isMulti={question.content.multi}
|
||||
isOwn={question.content.own}
|
||||
proportions={question.content.xy}
|
||||
format={question.content.format}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
isLargeCheck={question.content.isLargeCheck}
|
||||
|
||||
/>
|
||||
);
|
||||
case "help":
|
||||
|
@ -9,7 +9,7 @@ import LightbulbIcon from "@/assets/icons/questionsPage/lightbulbIcon";
|
||||
import LikeIcon from "@/assets/icons/questionsPage/likeIcon";
|
||||
import TropfyIcon from "@/assets/icons/questionsPage/tropfyIcon";
|
||||
import type { QuizQuestionRating } from "@frontend/squzanswerer";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import SwitchRating from "./switchRating";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
@ -289,7 +289,7 @@ export default function RatingOptions({ question, openBranchingPage, setOpenBran
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -4,7 +4,7 @@ import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import CustomNumberField from "@ui_kit/CustomNumberField";
|
||||
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import SwitchSlider from "./switchSlider";
|
||||
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
@ -209,7 +209,7 @@ export default function SliderOptions({ question, openBranchingPage, setOpenBran
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import AnswerItem from "../AnswerDraggableList/AnswerItem";
|
||||
import ButtonsOptionsAndPict from "../QuestionOptions/ButtonsOptionsAndPict";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
import { AnswerDraggableList } from "../../AnswerDraggableList";
|
||||
import AnswerItem from "../../AnswerDraggableList/AnswerItem";
|
||||
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
|
||||
import SwitchAnswerOptions from "./switchAnswerOptions";
|
||||
|
||||
interface Props {
|
||||
@ -15,7 +15,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function AnswerOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const {onClickAddAnAnswer} = useAddAnswer();
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -44,13 +44,17 @@ export default function AnswerOptions({ question, openBranchingPage, setOpenBran
|
||||
) : (
|
||||
<AnswerDraggableList
|
||||
questionId={question.id}
|
||||
variants={question.content.variants.map((variant, index) => (
|
||||
variants={question.content.variants
|
||||
.filter(variant => !variant.isOwn ? true : question.content.own && variant.isOwn)
|
||||
.map((variant, index) => (
|
||||
<AnswerItem
|
||||
key={variant.id}
|
||||
index={index}
|
||||
disableKeyDown={question.content.variants.length >= 100}
|
||||
questionId={question.id}
|
||||
variant={variant}
|
||||
isOwn={Boolean(variant.isOwn)}
|
||||
ownPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
@ -100,9 +104,9 @@ export default function AnswerOptions({ question, openBranchingPage, setOpenBran
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
@ -3,20 +3,25 @@ import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import type { QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { memo } from "react";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useAddAnswer } from "@/utils/hooks/useAddAnswer";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVariant;
|
||||
questionId: string;
|
||||
isRequired: boolean;
|
||||
isLargeCheck: boolean;
|
||||
isMulti: boolean;
|
||||
isOwn: boolean;
|
||||
ownPlaceholder?: string;
|
||||
}
|
||||
|
||||
const ResponseSettings = memo<Props>(function ({ questionId, isRequired, isLargeCheck, isMulti, isOwn }) {
|
||||
const ResponseSettings = memo<Props>(function ({question, questionId, ownPlaceholder, isRequired, isLargeCheck, isMulti, isOwn }) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const {switchOwn} = useAddAnswer();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -30,7 +35,7 @@ const ResponseSettings = memo<Props>(function ({ questionId, isRequired, isLarge
|
||||
pt: isTablet ? "5px" : "0px",
|
||||
}}
|
||||
>
|
||||
{/* <Box
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: "20px",
|
||||
@ -54,7 +59,7 @@ const ResponseSettings = memo<Props>(function ({ questionId, isRequired, isLarge
|
||||
<CustomCheckbox
|
||||
dataCy="checkbox-long-text-answer"
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Длинный текстовый ответ"}
|
||||
label={"Многострочный ответ"}
|
||||
checked={isLargeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
@ -79,12 +84,10 @@ const ResponseSettings = memo<Props>(function ({ questionId, isRequired, isLarge
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={isOwn}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(questionId, (question) => {
|
||||
question.content.own = target.checked;
|
||||
});
|
||||
switchOwn({question, checked:target.checked})
|
||||
}}
|
||||
/>
|
||||
</Box> */}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
@ -1,6 +1,6 @@
|
||||
import { QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import UploadImage from "../UploadImage";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import UploadImage from "../../UploadImage";
|
||||
import HelpQuestions from "../../helpQuestions";
|
||||
import ResponseSettings from "./responseSettings";
|
||||
|
||||
interface Props {
|
||||
@ -13,11 +13,13 @@ export default function SwitchAnswerOptions({ switchState = "setting", question
|
||||
case "setting":
|
||||
return (
|
||||
<ResponseSettings
|
||||
question={question}
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isLargeCheck={question.content.largeCheck}
|
||||
isMulti={question.content.multi}
|
||||
isOwn={question.content.own}
|
||||
isOwnPlaceholder={question.content.ownPlaceholder}
|
||||
/>
|
||||
);
|
||||
case "help":
|
@ -20,10 +20,10 @@ import { useUiTools } from "@root/uiTools/store";
|
||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import AddPlus from "../../assets/icons/questionsPage/addPlus";
|
||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import AddPlus from "@/assets/icons/questionsPage/addPlus";
|
||||
import ArrowLeft from "@/assets/icons/questionsPage/arrowLeft";
|
||||
import BranchingQuestions from "./Branching/BranchingModal/BranchingQuestionsModal";
|
||||
import { QuestionSwitchWindowTool } from "./QuestionSwitchWindowTool";
|
||||
import { QuestionSwitchWindowTool } from "./Branching/QuestionSwitchWindowTool";
|
||||
|
||||
interface Props {
|
||||
openBranchingPage: boolean;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import DataOptions from "./QuestionOptions/DataOptions/DataOptions";
|
||||
import DateOptions from "./QuestionOptions/DateOptions/DateOptions";
|
||||
import DropDown from "./DropDown/DropDown";
|
||||
import Emoji from "./Emoji/Emoji";
|
||||
import OptionsAndPicture from "./QuestionOptions/OptionsAndPicture/OptionsAndPicture";
|
||||
@ -8,7 +8,7 @@ import PageOptions from "./QuestionOptions/PageOptions/PageOptions";
|
||||
import RatingOptions from "./QuestionOptions/RatingOptions/RatingOptions";
|
||||
import SliderOptions from "./QuestionOptions/SliderOptions/SliderOptions";
|
||||
import UploadFile from "./UploadFile/UploadFile";
|
||||
import AnswerOptions from "./answerOptions/AnswerOptions";
|
||||
import AnswerOptions from "./QuestionOptions/answerOptions/AnswerOptions";
|
||||
import { notReachable } from "../../utils/notReachable";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
@ -76,7 +76,7 @@ export default function SwitchQuestionsPage({ question, openBranchingPage, setOp
|
||||
|
||||
case "date":
|
||||
return (
|
||||
<DataOptions
|
||||
<DateOptions
|
||||
question={question}
|
||||
openBranchingPage={openBranchingPage}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
|
@ -2,17 +2,17 @@ import { QuestionType } from "@model/question/question";
|
||||
import { Box } from "@mui/material";
|
||||
import { createTypedQuestion } from "@root/questions/actions";
|
||||
import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton";
|
||||
import Answer from "../../assets/icons/questionsPage/answer";
|
||||
import Date from "../../assets/icons/questionsPage/date";
|
||||
import Download from "../../assets/icons/questionsPage/download";
|
||||
import DropDown from "../../assets/icons/questionsPage/drop_down";
|
||||
import Emoji from "../../assets/icons/questionsPage/emoji";
|
||||
import Input from "../../assets/icons/questionsPage/input";
|
||||
import OptionsAndPict from "../../assets/icons/questionsPage/options_and_pict";
|
||||
import OptionsPict from "../../assets/icons/questionsPage/options_pict";
|
||||
import Page from "../../assets/icons/questionsPage/page";
|
||||
import RatingIcon from "../../assets/icons/questionsPage/rating";
|
||||
import Slider from "../../assets/icons/questionsPage/slider";
|
||||
import Answer from "@/assets/icons/questionsPage/answer";
|
||||
import Date from "@/assets/icons/questionsPage/date";
|
||||
import Download from "@/assets/icons/questionsPage/download";
|
||||
import DropDown from "@/assets/icons/questionsPage/drop_down";
|
||||
import Emoji from "@/assets/icons/questionsPage/emoji";
|
||||
import Input from "@/assets/icons/questionsPage/input";
|
||||
import OptionsAndPict from "@/assets/icons/questionsPage/options_and_pict";
|
||||
import OptionsPict from "@/assets/icons/questionsPage/options_pict";
|
||||
import Page from "@/assets/icons/questionsPage/page";
|
||||
import RatingIcon from "@/assets/icons/questionsPage/rating";
|
||||
import Slider from "@/assets/icons/questionsPage/slider";
|
||||
import type { UntypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||
|
||||
interface Props {
|
||||
|
@ -11,9 +11,9 @@ import {
|
||||
} from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import { useEffect, useState } from "react";
|
||||
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsOptions";
|
||||
import ArrowDown from "@/assets/icons/ArrowDownIcon";
|
||||
import InfoIcon from "@/assets/icons/InfoIcon";
|
||||
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
|
||||
import SwitchUpload from "./switchUpload";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { QuizQuestionFile, UploadFileType } from "@frontend/squzanswerer";
|
||||
@ -187,7 +187,7 @@ export default function UploadFile({ question, openBranchingPage, setOpenBranchi
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
setSwitchState={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
|
@ -8,9 +8,9 @@ import {
|
||||
InputAdornment,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import UploadIcon from "../../../assets/icons/UploadIcon";
|
||||
import SearchIcon from "../../../assets/icons/SearchIcon";
|
||||
import UnsplashIcon from "../../../assets/icons/Unsplash.svg";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import SearchIcon from "@/assets/icons/SearchIcon";
|
||||
import UnsplashIcon from "@/assets/icons/Unsplash.svg";
|
||||
import { useRef, useState, type DragEvent } from "react";
|
||||
|
||||
type ImageFormat = "jpg" | "jpeg" | "png" | "gif";
|
||||
|
@ -4,6 +4,7 @@ import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useState } from "react";
|
||||
import { DropZone } from "../../../pages/startPage/dropZone";
|
||||
import { CropModalInit } from "@/ui_kit/Modal/CropModal";
|
||||
|
||||
type UploadImageProps = {
|
||||
question: AnyTypedQuizQuestion;
|
||||
@ -15,6 +16,8 @@ type UploadImageProps = {
|
||||
|
||||
export default function UploadImage({ question, cropAspectRatio }: UploadImageProps) {
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [openCropModal, setOpenCropModal] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
@ -33,43 +36,53 @@ export default function UploadImage({ question, cropAspectRatio }: UploadImagePr
|
||||
Загрузить изображение
|
||||
</Typography>
|
||||
{pictureUploding ? (
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
sx={{ height: "120px", width: "300px" }}
|
||||
/>
|
||||
<Skeleton variant="rounded" sx={{ height: "120px", width: "300px" }} />
|
||||
) : (
|
||||
<DropZone
|
||||
text={"5 MB максимум"}
|
||||
sx={{ maxWidth: "300px", width: "100%" }}
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
imageUrl={question.content.back}
|
||||
imageUrl={question.content.originalBack}
|
||||
originalImageUrl={question.content.originalBack}
|
||||
onImageUploadClick={async (file) => {
|
||||
setPictureUploading(true);
|
||||
|
||||
await uploadQuestionImage(question.id, quiz.qid, file, (question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
});
|
||||
await uploadQuestionImage(
|
||||
question.id,
|
||||
quiz.qid,
|
||||
file,
|
||||
(question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
},
|
||||
);
|
||||
|
||||
setOpenCropModal(true)
|
||||
setPictureUploading(false);
|
||||
}}
|
||||
}
|
||||
}
|
||||
onDeleteClick={() => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
question.content.back = null;
|
||||
question.content.originalBack = null;
|
||||
if ("editedUrlImagesList" in question.content) question.content.editedUrlImagesList = null;
|
||||
});
|
||||
}}
|
||||
onImageSaveClick={async (file) => {
|
||||
setPictureUploading(true);
|
||||
|
||||
await uploadQuestionImage(question.id, quiz.qid, file, (question, url) => {
|
||||
question.content.back = url;
|
||||
});
|
||||
|
||||
setPictureUploading(false);
|
||||
onImageSavedClick={() => {
|
||||
setOpenCropModal(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CropModalInit
|
||||
originalImageUrl={question.content.originalBack}
|
||||
editedUrlImagesList={question.content?.editedUrlImagesList}
|
||||
questionId={question.id.toString()}
|
||||
questionType={question.type}
|
||||
quizId={quiz.qid}
|
||||
open={openCropModal}
|
||||
selfClose={() => setOpenCropModal(false)}
|
||||
setPictureUploading={setPictureUploading}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Box, Button, ButtonBase, Dialog, Typography, useTheme } from "@mui/mate
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import SelectableButton from "@ui_kit/SelectableButton";
|
||||
import { useState } from "react";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import type { DragEvent } from "react";
|
||||
|
||||
type BackgroundTypeModal = "linkVideo" | "ownVideo";
|
||||
|
@ -4,7 +4,7 @@ import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import SelectableButton from "@ui_kit/SelectableButton";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import { memo, useState } from "react";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import UploadVideoModal from "./UploadVideoModal";
|
||||
|
||||
type BackgroundType = "text" | "video";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { DateDefinition, TimeDefinition } from "./helper";
|
||||
import { CardAnswer } from "./CardAnswer";
|
||||
import { CardAnswer } from "./cardAnswers/CardAnswer";
|
||||
import { Result } from "@root/results/store";
|
||||
|
||||
interface AnswerListProps {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon";
|
||||
import { Box, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC, MouseEvent, useState } from "react";
|
||||
import { ContactIcon } from "./icons/ContactIcon";
|
||||
import { MessageIcon } from "./icons/MessageIcon";
|
||||
import { PhoneIcon } from "./icons/PhoneIcon";
|
||||
import { ContactIcon } from "../icons/ContactIcon";
|
||||
import { MessageIcon } from "../icons/MessageIcon";
|
||||
import { PhoneIcon } from "../icons/PhoneIcon";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
|
||||
import { deleteResult, obsolescenceResult } from "@root/results/actions";
|
||||
@ -12,10 +12,14 @@ import { useQuizStore } from "@root/quizes/store";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||
|
||||
import { DeleteModal } from "./DeleteModal";
|
||||
import { DeleteModal } from "../DeleteModal";
|
||||
|
||||
import type { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||
import { ListOfOptionsCardAnswer } from "./ListOfOptionsCardAnswer";
|
||||
import { ListOfImagesCardAnswer } from "./ListOfImagesCardAnswer";
|
||||
|
||||
import { timewebContentFile, timewebContent } from "./helper"
|
||||
|
||||
interface CardAnswerProps {
|
||||
isNew: boolean;
|
||||
@ -312,6 +316,26 @@ export const CardAnswer: FC<CardAnswerProps> = ({
|
||||
qid = quest[i].backendId
|
||||
}
|
||||
}
|
||||
if (answer?.Version !== undefined) {
|
||||
if (typeOuestion === "variant" || typeOuestion === "emoji") return (
|
||||
<ListOfOptionsCardAnswer
|
||||
title={titleQuestion || ""}
|
||||
answer={answer.content}
|
||||
id={id}
|
||||
/>
|
||||
)
|
||||
if (typeOuestion === "varimg" || typeOuestion === "images" && answer.content.includes("Image")) return (
|
||||
<ListOfImagesCardAnswer
|
||||
title={titleQuestion || ""}
|
||||
answer={answer.content}
|
||||
id={id}
|
||||
quizId={quiz?.id}
|
||||
questionId={qid}
|
||||
/>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={answer.id}
|
||||
@ -325,7 +349,7 @@ export const CardAnswer: FC<CardAnswerProps> = ({
|
||||
<Typography sx={{ fontSize: "18px", color: "#9A9AAF" }}>
|
||||
{id + 1}. {titleQuestion}.
|
||||
</Typography>
|
||||
{typeOuestion === "file" && (
|
||||
{typeOuestion === "file" && answer.content && (
|
||||
<Link
|
||||
download
|
||||
href={timewebContentFile(quiz?.qid, answer.content, qid)}
|
||||
@ -366,6 +390,7 @@ export const CardAnswer: FC<CardAnswerProps> = ({
|
||||
{!(typeOuestion === "file" || typeOuestion === "images" || typeOuestion === "varimg") && (
|
||||
<Typography sx={{ fontSize: "18px" }}>{answer.content}</Typography>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
@ -433,26 +458,3 @@ export const CardAnswer: FC<CardAnswerProps> = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function timewebContentFile(editQuizId: string, content: string, qid: string) {
|
||||
if (content.includes("<img")) {
|
||||
//Старая версия: контент лежит в теге
|
||||
//<img style="width:100%; max-width:250px; max-height:250px" src="https://s3.timeweb.cloud/3c580be9-30c7959dc17b/squizimages/03520c507b35e8/cq39gn7o73evdd30"/>
|
||||
return content.split("<")[1].split('src="')[1].split('"/>')[0]
|
||||
}
|
||||
//Новая версия: контент просто записан с указанием расширения файла
|
||||
return `https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizanswer/${editQuizId}/${qid}/${content}`
|
||||
}
|
||||
function timewebContent(editQuizId: string, content: string, qid: string) {
|
||||
if (content.includes("<img")) {
|
||||
//Старая версия: контент лежит в теге
|
||||
//<img style="width:100%; max-width:250px; max-height:250px" src="https://s3.timeweb.cloud/3c580be9-30c7959dc17b/squizimages/03520c507b35e8/cq39gn7o73evdd30"/>
|
||||
return content.split("<")[1].split('src="')[1].split('"/>')[0]
|
||||
}
|
||||
if (content.includes(`"Image"`)) {
|
||||
const data = JSON.parse(content)
|
||||
return data.Image
|
||||
}
|
||||
//Новая версия: контент просто записан с указанием расширения файла(устарело)
|
||||
return `https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/${editQuizId}/${content}`
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { splitUserText, timewebContent } from "./helper";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
id: number;
|
||||
answer: string;
|
||||
quizId: string;
|
||||
questionId: string;
|
||||
}
|
||||
|
||||
export const ListOfImagesCardAnswer = ({
|
||||
title,
|
||||
id,
|
||||
answer,
|
||||
quizId,
|
||||
questionId,
|
||||
}: Props) => {
|
||||
|
||||
return <Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
gap: "13px",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Typography sx={{ fontSize: "18px", color: "#9A9AAF" }}>
|
||||
{id + 1}. {title}.
|
||||
</Typography>
|
||||
<Box>
|
||||
{
|
||||
splitUserText(answer)
|
||||
.filter(text => text.length)
|
||||
.map(text => {
|
||||
const { Image, Description } = JSON.parse(text)
|
||||
return (<>
|
||||
<img
|
||||
width={40}
|
||||
height={40}
|
||||
src={timewebContent(quizId, Image, questionId)}
|
||||
/>
|
||||
<Typography sx={{ fontSize: "18px", wordBreak: "break-word" }}>{Description}</Typography>
|
||||
</>)
|
||||
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { splitUserText } from "./helper";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
id: number;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export const ListOfOptionsCardAnswer = ({
|
||||
title,
|
||||
id,
|
||||
answer
|
||||
}: Props) => {
|
||||
|
||||
return <Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "start",
|
||||
gap: "13px",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Typography sx={{ fontSize: "18px", color: "#9A9AAF" }}>
|
||||
{id + 1}. {title}.
|
||||
</Typography>
|
||||
<Box>
|
||||
{
|
||||
splitUserText(answer)
|
||||
.filter(text => text.length)
|
||||
.map(text => <Typography sx={{ fontSize: "18px", wordBreak: "break-word" }}>{text}</Typography>)
|
||||
}
|
||||
</Box>
|
||||
|
||||
|
||||
</Box>
|
||||
};
|
38
src/pages/QuizAnswersPage/cardAnswers/helper.ts
Normal file
38
src/pages/QuizAnswersPage/cardAnswers/helper.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export function splitUserText(input: string) {
|
||||
// Регулярное выражение для поиска текста в обратных кавычках
|
||||
const regex = /`([^`]*)`/g; // Изменено на ([^]*) для захвата пустых строк
|
||||
let result = [];
|
||||
let match;
|
||||
|
||||
// Найти все совпадения
|
||||
while ((match = regex.exec(input)) !== null) {
|
||||
// Добавляем найденный текст (включая пустые строки) в массив
|
||||
result.push(match[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function timewebContentFile(editQuizId: string, content: string, qid: string) {
|
||||
if (content.includes("<img")) {
|
||||
//Старая версия: контент лежит в теге
|
||||
//<img style="width:100%; max-width:250px; max-height:250px" src="https://s3.timeweb.cloud/3c580be9-30c7959dc17b/squizimages/03520c507b35e8/cq39gn7o73evdd30"/>
|
||||
return content.split("<")[1].split('src="')[1].split('"/>')[0]
|
||||
}
|
||||
//Новая версия: контент просто записан с указанием расширения файла
|
||||
return `https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizanswer/${editQuizId}/${qid}/${content}`
|
||||
}
|
||||
export function timewebContent(editQuizId: string, content: string, qid: string) {
|
||||
if (content.includes("<img")) {
|
||||
//Старая версия: контент лежит в теге
|
||||
//<img style="width:100%; max-width:250px; max-height:250px" src="https://s3.timeweb.cloud/3c580be9-30c7959dc17b/squizimages/03520c507b35e8/cq39gn7o73evdd30"/>
|
||||
return content.split("<")[1].split('src="')[1].split('"/>')[0]
|
||||
} else if (content.includes(`"Image"`)) {
|
||||
const data = JSON.parse(content)
|
||||
return data.Image
|
||||
} else {
|
||||
return content
|
||||
}
|
||||
//Новая версия: контент просто записан с указанием расширения файла(устарело)
|
||||
return `https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/${editQuizId}/${content}`
|
||||
}
|
@ -10,10 +10,10 @@ import StarIconPoints from "./StarIconsPoints";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
setSwitchState: (data: string) => void;
|
||||
}
|
||||
|
||||
export default function ButtonsOptionsForm({ SSHC, switchState }: Props) {
|
||||
export default function ButtonsOptionsForm({ setSwitchState, switchState }: Props) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(800));
|
||||
|
||||
@ -74,7 +74,7 @@ export default function ButtonsOptionsForm({ SSHC, switchState }: Props) {
|
||||
<MiniButtonSetting
|
||||
key={index}
|
||||
onClick={() => {
|
||||
SSHC(value);
|
||||
setSwitchState(value);
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
|
@ -24,7 +24,7 @@ export const DescriptionForm = () => {
|
||||
setPriceButtonsType(type);
|
||||
};
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
const setSwitchState = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
@ -188,7 +188,7 @@ export const DescriptionForm = () => {
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<ButtonsOptionsForm switchState={switchState} SSHC={SSHC} />
|
||||
<ButtonsOptionsForm switchState={switchState} setSwitchState={setSwitchState} />
|
||||
<SwitchResult switchState={switchState} totalIndex={0} />
|
||||
</Box>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
useMediaQuery,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import image from "../../assets/Rectangle 110.png";
|
||||
import image from "@/assets/Rectangle 110.png";
|
||||
|
||||
export const FirstEntry = () => {
|
||||
const theme = useTheme();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Box, Button, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuiz } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import image from "../../assets/Rectangle 110.png";
|
||||
import Info from "../../assets/icons/Info";
|
||||
import image from "@/assets/Rectangle 110.png";
|
||||
import Info from "@/assets/icons/Info";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
|
||||
export const Result = () => {
|
||||
|
@ -53,8 +53,8 @@ export default function AvailablePrivilege() {
|
||||
}
|
||||
const quizUnlimDays = getCramps(quizUnlimTime, userPrivileges?.quizUnlimTime?.created_at || "");
|
||||
const squizBadgeDays = getCramps(squizHideBadge, userPrivileges?.squizHideBadge?.created_at || "");
|
||||
|
||||
const currentDate = moment();
|
||||
console.log(userPrivileges)
|
||||
console.log(quizUnlimTime)
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
@ -145,15 +145,15 @@ export default function EditPage({
|
||||
{quizConfig && (
|
||||
<>
|
||||
<Stepper activeStep={currentStep} />
|
||||
<SwitchStepPages
|
||||
activeStep={currentStep}
|
||||
quizType={quizConfig.type}
|
||||
quizResults={quizConfig.results}
|
||||
quizStartPageType={quizConfig.startpageType}
|
||||
openBranchingPage={openBranchingPage}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
widthMain={widthMain}
|
||||
/>
|
||||
<SwitchStepPages
|
||||
activeStep={currentStep}
|
||||
quizType={quizConfig.type}
|
||||
quizResults={quizConfig.results}
|
||||
quizStartPageType={quizConfig.startpageType}
|
||||
openBranchingPage={openBranchingPage}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
widthMain={widthMain}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { wrap } from "module";
|
||||
|
||||
export default function ModalSizeImage() {
|
||||
const theme = useTheme();
|
||||
@ -25,27 +26,44 @@ export default function ModalSizeImage() {
|
||||
function createData(name: string, size: string) {
|
||||
return { name, size };
|
||||
}
|
||||
|
||||
|
||||
const rows = [
|
||||
createData("Прямая ссылка/домен", "1792х1509 px"),
|
||||
createData("Модальное окно на сайте", "1380х1300 px"),
|
||||
createData("Во ВКонтакте", "1166х1200 px"),
|
||||
createData("Версия для планшета", "767х220 px"),
|
||||
createData("Мобильная версия", "400х220 px"),
|
||||
createData("Картинка для дизайна Centered", "900х490 px"),
|
||||
createData("Картинка для дизайна Expanded", "1920х1080 px"),
|
||||
createData("Стартовая \"Centered\" (десктоп)", "844х306 px"),
|
||||
createData("Стартовая \"Centered\" (планшет гориз.)", "844х530 px"),
|
||||
createData("Стартовая \"Centered\" (планшет верт.)", "660х260 px"),
|
||||
createData("Логотип", "110 х 40 px"),
|
||||
createData("\"Варианты с картинками\" (десктоп)", "317х257 px"),
|
||||
createData("\"Варианты с картинками\" (планшет)", "455х257 px"),
|
||||
createData("\"Варианты с картинками\" (мобилка)", "160х183 px"),
|
||||
createData("\"Варианты и картинка\" (десктоп)", "450х450 px"),
|
||||
createData("\"Варианты и картинка\" (мобилка)", "335х335 px"),
|
||||
createData("\"Своё поле для ввода\" (десктоп)", "450х450 px"),
|
||||
createData("\"Своё поле для ввода\" (мобилка)", "335х335 px"),
|
||||
createData("\"Варианты\" (десктоп)", "450х450 px"),
|
||||
createData("\"Варианты\" (мобилка)", "335х335 px"),
|
||||
createData("Картинка для результата (десктоп)", "700х306 px"),
|
||||
createData("Картинка для результата (мобилка)", "335х236 px"),
|
||||
];
|
||||
// const rows = [
|
||||
// createData("Прямая ссылка/домен", "1792х1509 px"),
|
||||
// createData("Модальное окно на сайте", "1380х1300 px"),
|
||||
// createData("Во ВКонтакте", "1166х1200 px"),
|
||||
// createData("Версия для планшета", "767х220 px"),
|
||||
// createData("Мобильная версия", "400х220 px"),
|
||||
// createData("Картинка для дизайна Centered", "900х490 px"),
|
||||
// createData("Картинка для дизайна Expanded", "1920х1080 px"),
|
||||
// ];
|
||||
|
||||
const rows2 = [
|
||||
createData("Вертикальный вариант", "180х240 px"),
|
||||
createData("Квадратные", "240х240 px"),
|
||||
createData("Варианты и картинка", "380х307 px"),
|
||||
createData("Консультант", "140х140 px"),
|
||||
createData("Логотип", "107х37 px"),
|
||||
createData("Результаты", "1100х600 px"),
|
||||
createData("Бонус", "200х60 px"),
|
||||
createData('Картинка для формата вопроса "Страница"', "860х1250 px"),
|
||||
];
|
||||
// const rows2 = [
|
||||
// createData("Вертикальный вариант", "180х240 px"),
|
||||
// createData("Квадратные", "240х240 px"),
|
||||
// createData("Варианты и картинка", "380х307 px"),
|
||||
// createData("Консультант", "140х140 px"),
|
||||
// createData("Логотип", "107х37 px"),
|
||||
// createData("Результаты", "1100х600 px"),
|
||||
// createData("Бонус", "200х60 px"),
|
||||
// createData('Картинка для формата вопроса "Страница"', "860х1250 px"),
|
||||
// ];
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -84,81 +102,87 @@ export default function ModalSizeImage() {
|
||||
p: 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: "10px 9px 17px 20px",
|
||||
borderRadius: "12px 12px 0px 0px",
|
||||
}}
|
||||
>
|
||||
<Typography variant={"h5"}>Размеры картинок</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{ padding: "15px 20px 0px" }}>
|
||||
<Typography
|
||||
variant={"body2"}
|
||||
sx={{ color: theme.palette.grey2.main, fontWeight: 400 }}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: "10px 9px 17px 20px",
|
||||
borderRadius: "12px 12px 0px 0px",
|
||||
}}
|
||||
>
|
||||
Рекомендованный размер зависит от того, как вы будете чаще
|
||||
использовать quiz:
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ padding: "15px 40px 30px" }}>
|
||||
{rows.map(({ name, size }, index) => (
|
||||
<Box
|
||||
key={name || index}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
gap: "6px",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
paddingBottom: "5px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 18,
|
||||
left: 0,
|
||||
right: 0,
|
||||
borderBottom: "solid 1px #F2F3F7",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "block ruby",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
background: "white",
|
||||
}}
|
||||
>
|
||||
<Typography variant={"body2"} fontWeight={400}>
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "block ruby",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
background: "white",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ whiteSpace: "nowrap" }} variant={"body2"}>
|
||||
{size}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Typography variant={"h5"}>Размеры картинок</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box
|
||||
overflow="auto"
|
||||
height="auto"
|
||||
maxHeight="650px"
|
||||
>
|
||||
|
||||
<Box sx={{ padding: "15px 20px 0px" }}>
|
||||
<Typography
|
||||
variant={"body2"}
|
||||
sx={{ color: theme.palette.grey2.main, fontWeight: 400 }}
|
||||
>
|
||||
Рекомендованный размер зависит от того, как вы будете чаще
|
||||
использовать quiz:
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ padding: "15px 40px 30px" }}>
|
||||
{rows.map(({ name, size }, index) => (
|
||||
<Box
|
||||
key={name || index}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
gap: "6px",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
paddingBottom: "5px",
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 18,
|
||||
left: 0,
|
||||
right: 0,
|
||||
borderBottom: "solid 1px #F2F3F7",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "block ruby",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
background: "white",
|
||||
}}
|
||||
>
|
||||
<Typography variant={"body2"} fontWeight={400}>
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "block ruby",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
background: "white",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ whiteSpace: "nowrap" }} variant={"body2"}>
|
||||
{size}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{/* <Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: "20px",
|
||||
@ -214,7 +238,9 @@ export default function ModalSizeImage() {
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box> */}
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
|
@ -9,7 +9,7 @@ import LayoutStandartIcon from "@icons/LayoutStandartIcon";
|
||||
import { QuizStartpageType } from "@model/quizSettings";
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
import UploadIcon from "@/assets/icons/UploadIcon";
|
||||
import CustomizedSwitch from "@ui_kit/CustomSwitch";
|
||||
|
||||
import {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
@ -10,11 +10,13 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useState } from "react";
|
||||
import { UploadImageModal } from "../../pages/Questions/UploadImage/UploadImageModal";
|
||||
import { useDisclosure } from "../../utils/useDisclosure";
|
||||
import imge from "@/assets/card-1.png"
|
||||
import { CropModalInit } from "@/ui_kit/Modal/CropModal";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
|
||||
|
||||
const allowedFileTypes = ["image/png", "image/jpeg", "image/gif"];
|
||||
|
||||
@ -25,7 +27,7 @@ interface Props {
|
||||
imageUrl: string | null;
|
||||
originalImageUrl: string | null;
|
||||
onImageUploadClick: (image: Blob) => void;
|
||||
onImageSaveClick: (image: Blob) => void;
|
||||
onImageSavedClick?: () => void;
|
||||
onDeleteClick: () => void;
|
||||
cropAspectRatio?: {
|
||||
width: number;
|
||||
@ -41,22 +43,15 @@ export const DropZone = ({
|
||||
imageUrl,
|
||||
originalImageUrl,
|
||||
onImageUploadClick,
|
||||
onImageSaveClick,
|
||||
onImageSavedClick,
|
||||
onDeleteClick,
|
||||
cropAspectRatio,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
const [isDropReady, setIsDropReady] = useState<boolean>(false);
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
|
||||
useDisclosure();
|
||||
const {
|
||||
isCropModalOpen,
|
||||
openCropModal,
|
||||
closeCropModal,
|
||||
imageBlob,
|
||||
setCropModalImageBlob,
|
||||
} = useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
@ -68,7 +63,6 @@ export const DropZone = ({
|
||||
|
||||
onImageUploadClick?.(file);
|
||||
closeImageUploadModal();
|
||||
openCropModal(file);
|
||||
}
|
||||
|
||||
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
@ -102,18 +96,8 @@ export const DropZone = ({
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={onImageSaveClick}
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
/>
|
||||
<ButtonBase
|
||||
onClick={
|
||||
imageUrl ? () => openCropModal(imageUrl) : openImageUploadModal
|
||||
onClick={ () => onImageSavedClick &&imageUrl ? onImageSavedClick() : openImageUploadModal()
|
||||
}
|
||||
sx={{
|
||||
width: "100%",
|
||||
@ -152,24 +136,25 @@ export const DropZone = ({
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
||||
</ButtonBase>
|
||||
{imageUrl && (
|
||||
<IconButton
|
||||
onClick={onDeleteClick}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
color: theme.palette.orange.main,
|
||||
borderRadius: "8px",
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
...deleteIconSx,
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{imageUrl && (
|
||||
<IconButton
|
||||
onClick={onDeleteClick}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
color: theme.palette.orange.main,
|
||||
borderRadius: "8px",
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
...deleteIconSx,
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -64,7 +64,8 @@ export default function Extra() {
|
||||
Дополнительно
|
||||
</Link>
|
||||
</Box>
|
||||
{isExpanded && quiz && (
|
||||
|
||||
{/* {isExpanded && quiz && (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "transparent",
|
||||
@ -89,7 +90,7 @@ export default function Extra() {
|
||||
onChange={mutationOrgMetaHC}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
)} */}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CreationCard from "@ui_kit/CreationCard";
|
||||
import quizCreationImage1 from "../../assets/quiz-creation-1.png";
|
||||
import quizCreationImage2 from "../../assets/quiz-creation-2.png";
|
||||
import quizCreationImage1 from "@/assets/quiz-creation-1.png";
|
||||
import quizCreationImage2 from "@/assets/quiz-creation-2.png";
|
||||
import { setQuizType, updateQuiz } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import arrowLeftIcon from "../../assets/icons/arrow_left.svg";
|
||||
import arrowRightIcon from "../../assets/icons/arrow_right.svg";
|
||||
import arrowLeftIcon from "@/assets/icons/arrow_left.svg";
|
||||
import arrowRightIcon from "@/assets/icons/arrow_right.svg";
|
||||
import QuizgenegationName from "@utils/quizgenegationName";
|
||||
import { QuizType } from "@model/quizSettings";
|
||||
|
||||
|
@ -7,14 +7,14 @@ import {
|
||||
} from "@mui/material";
|
||||
import { setQuizStartpageType } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import cardImage1 from "../../assets/card-1.png";
|
||||
import cardImage2 from "../../assets/card-2.png";
|
||||
import cardImage3 from "../../assets/card-3.png";
|
||||
import cardImage1 from "@/assets/card-1.png";
|
||||
import cardImage2 from "@/assets/card-2.png";
|
||||
import cardImage3 from "@/assets/card-3.png";
|
||||
import CardWithImage from "./CardWithImage";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
|
||||
import arrowLeftIcon from "../../assets/icons/arrow_left.svg";
|
||||
import arrowRightIcon from "../../assets/icons/arrow_right.svg";
|
||||
import arrowLeftIcon from "@/assets/icons/arrow_left.svg";
|
||||
import arrowRightIcon from "@/assets/icons/arrow_right.svg";
|
||||
|
||||
export default function Steptwo() {
|
||||
const quiz = useCurrentQuiz();
|
||||
|
@ -4,11 +4,12 @@ import { devlog } from "@frontend/kitui";
|
||||
import { AnyTypedQuizQuestion, QuestionVariant } from "@frontend/squzanswerer";
|
||||
import { questionToEditQuestionRequest } from "@model/question/edit";
|
||||
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
||||
import { UntypedQuizQuestion, createQuestionVariant } from "@model/questionTypes/shared";
|
||||
import { UntypedQuizQuestion, createQuestionOwnVariant, createQuestionVariant } from "@model/questionTypes/shared";
|
||||
import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
|
||||
import { RequestQueue } from "../../utils/requestQueue";
|
||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||
@ -336,6 +337,20 @@ export const addQuestionVariant = (questionId: string) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
export const addQuestionOwnVariant = (questionId: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
switch (question.type) {
|
||||
case "variant":
|
||||
case "emoji":
|
||||
case "images":
|
||||
case "varimg":
|
||||
question.content.variants.push(createQuestionOwnVariant());
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Cannot add variant to question of type "${question.type}"`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteQuestionVariant = (questionId: string, variantId: string) => {
|
||||
updateQuestion(questionId, (question) => {
|
||||
@ -438,6 +453,7 @@ export const changeQuestionType = (questionId: string, type: QuestionType) => {
|
||||
question.content = JSON.parse(JSON.stringify(defaultQuestionByType[type].content));
|
||||
question.content.id = oldId;
|
||||
question.content.rule = oldRule;
|
||||
if ("editedUrlImagesList" in question.content) question.content.editedUrlImagesList = null;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Box, Button, Skeleton, useTheme, useMediaQuery } from "@mui/material";
|
||||
import Plus from "@icons/questionsPage/plus";
|
||||
import Image from "../assets/icons/questionsPage/image";
|
||||
import Image from "@/assets/icons/questionsPage/image";
|
||||
|
||||
import type { SxProps, Theme } from "@mui/material";
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
useContactFormStore,
|
||||
} from "../stores/contactForm";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import X from "../assets/icons/x";
|
||||
import X from "@/assets/icons/x";
|
||||
import PenaLogo from "../ui_kit/PenaLogo";
|
||||
|
||||
export default () => {
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
interface CustomTextFieldProps {
|
||||
placeholder: string;
|
||||
@ -71,6 +72,8 @@ export default function CustomTextField({
|
||||
if (onChange) {
|
||||
onChange(event);
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Превышена длина вводимого текста")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import EmojiPickerOriginal from "@emoji-mart/react";
|
||||
import { Box } from "@mui/material";
|
||||
import { I18n, Data } from "./emogiPickerUtils/MOCKdata"
|
||||
|
||||
type Emoji = {
|
||||
emoticons: string[];
|
||||
@ -22,6 +23,8 @@ export const EmojiPicker = ({ onEmojiSelect }: EmojiPickerProps) => (
|
||||
theme="light"
|
||||
locale="ru"
|
||||
exceptEmojis={ignoreEmojis}
|
||||
i18n={I18n}
|
||||
data={Data}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,45 +1,61 @@
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import { Box, Button, ButtonBase, Skeleton, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import { FC, useState } from "react";
|
||||
import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal";
|
||||
import { VideoElement } from "../pages/startPage/VideoElement";
|
||||
import { useCurrentQuiz } from "../stores/quizes/hooks";
|
||||
import { useDisclosure } from "../utils/useDisclosure";
|
||||
import { QuizQuestionPage, QuizQuestionResult } from "@frontend/squzanswerer";
|
||||
import UploadVideoModal from "@/pages/Questions/UploadVideoModal";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionPage | QuizQuestionResult;
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { UploadImageModal } from "@/pages/Questions/UploadImage/UploadImageModal";
|
||||
import { VideoElement } from "@/pages/startPage/VideoElement";
|
||||
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||
import { useDisclosure } from "@/utils/useDisclosure";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import imge from "@/assets/card-1.png"
|
||||
import { CropModalInit } from "./Modal/CropModal";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
interface Iprops {
|
||||
question: AnyTypedQuizQuestion;
|
||||
cropAspectRatio: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio }) => {
|
||||
export const MediaSelectionAndDisplay: FC<Iprops> = ({
|
||||
question,
|
||||
cropAspectRatio,
|
||||
}) => {
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [backgroundUploding, setBackgroundUploading] = useState<boolean>(false);
|
||||
const [openCropModal, setOpenCropModal] = useState(false);
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const theme = useTheme();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const [isVideoUploadDialogOpen, setIsVideoUploadDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
|
||||
useDisclosure();
|
||||
async function handleImageUpload(file: File) {
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
});
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
const url = await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
question.content.back = url;
|
||||
question.content.originalBack = url;
|
||||
},
|
||||
);
|
||||
setOpenCropModal(true)
|
||||
|
||||
setPictureUploading(false);
|
||||
}
|
||||
@ -95,11 +111,10 @@ export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio
|
||||
}}
|
||||
variant="text"
|
||||
onClick={() =>
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("useImage" in question.content)) return;
|
||||
|
||||
question.content.useImage = true;
|
||||
})
|
||||
updateQuestion(
|
||||
question.id,
|
||||
(question) => (question.content.useImage = true),
|
||||
)
|
||||
}
|
||||
>
|
||||
Изображение
|
||||
@ -124,32 +139,30 @@ export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio
|
||||
Видео
|
||||
</Button>
|
||||
</Box>
|
||||
<UploadImageModal
|
||||
isOpen={isImageUploadOpen}
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModal
|
||||
isOpen={isCropModalOpen}
|
||||
imageBlob={imageBlob}
|
||||
originalImageUrl={originalImageUrl}
|
||||
setCropModalImageBlob={setCropModalImageBlob}
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
question.content.back = null;
|
||||
question.content.originalBack = null;
|
||||
});
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
cropAspectRatio={cropAspectRatio}
|
||||
/>
|
||||
<UploadVideoModal
|
||||
open={isVideoUploadDialogOpen}
|
||||
onClose={() => setIsVideoUploadDialogOpen(false)}
|
||||
onUpload={handleVideoUpload}
|
||||
video={question.content.video}
|
||||
/>
|
||||
>
|
||||
<UploadImageModal
|
||||
isOpen={isImageUploadOpen}
|
||||
onClose={closeImageUploadModal}
|
||||
handleImageChange={handleImageUpload}
|
||||
/>
|
||||
<CropModalInit
|
||||
originalImageUrl={question.content.originalBack}
|
||||
editedUrlImagesList={question.content?.editedUrlImagesList}
|
||||
questionId={question.id}
|
||||
questionType={question.type}
|
||||
quizId={quizQid}
|
||||
open={openCropModal}
|
||||
selfClose={() => setOpenCropModal(false)}
|
||||
setPictureUploading={setPictureUploading}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{question.content.useImage && (
|
||||
<Box
|
||||
sx={{
|
||||
@ -161,14 +174,14 @@ export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio
|
||||
}}
|
||||
>
|
||||
<AddOrEditImageButton
|
||||
imageSrc={question.content.back ?? undefined}
|
||||
imageSrc={question.content.back}
|
||||
uploading={pictureUploding}
|
||||
onImageClick={() => {
|
||||
if (question.content.back) {
|
||||
return openCropModal(question.content.back, question.content.originalBack);
|
||||
setOpenCropModal(true)
|
||||
} else {
|
||||
openImageUploadModal();
|
||||
}
|
||||
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
openImageUploadModal();
|
||||
@ -221,6 +234,27 @@ export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio
|
||||
my: "20px",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
onChange={async (event) => {
|
||||
setBackgroundUploading(true);
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
question.content.video = url;
|
||||
},
|
||||
);
|
||||
}
|
||||
setBackgroundUploading(false);
|
||||
}}
|
||||
hidden
|
||||
accept=".mp4"
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<UploadBox
|
||||
icon={<UploadIcon />}
|
||||
sx={{
|
||||
@ -246,7 +280,8 @@ export const MediaSelectionAndDisplay: FC<Props> = ({ question, cropAspectRatio
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</Box >
|
||||
);
|
||||
};
|
||||
|
@ -1,438 +0,0 @@
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { ResetIcon } from "@icons/ResetIcon";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Slider,
|
||||
SxProps,
|
||||
Theme,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { FC, useCallback, useMemo, useRef, useState } from "react";
|
||||
import ReactCrop, {
|
||||
PercentCrop,
|
||||
centerCrop,
|
||||
convertToPixelCrop,
|
||||
makeAspectCrop,
|
||||
} from "react-image-crop";
|
||||
import "react-image-crop/dist/ReactCrop.css";
|
||||
import { isImageBlobAGifFile } from "../../utils/isImageBlobAGifFile";
|
||||
import {
|
||||
getModifiedImageBlob,
|
||||
getRotatedImageBlob,
|
||||
} from "./utils/imageManipulation";
|
||||
|
||||
const styleSlider: SxProps<Theme> = {
|
||||
color: "#7E2AEA",
|
||||
height: "12px",
|
||||
"& .MuiSlider-track": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
border: `1px solid #9A9AAF`,
|
||||
},
|
||||
"& .MuiSlider-thumb": {
|
||||
height: 26,
|
||||
width: 26,
|
||||
border: `6px solid #7E2AEA`,
|
||||
backgroundColor: "white",
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
imageBlob: Blob | null;
|
||||
originalImageUrl: string | null;
|
||||
setCropModalImageBlob: (imageBlob: Blob) => void;
|
||||
onClose: () => void;
|
||||
onSaveImageClick: (imageBlob: Blob) => void;
|
||||
onDeleteClick?: () => void;
|
||||
cropAspectRatio?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const CropModal: FC<Props> = ({
|
||||
isOpen,
|
||||
imageBlob,
|
||||
originalImageUrl,
|
||||
setCropModalImageBlob,
|
||||
onSaveImageClick,
|
||||
onDeleteClick,
|
||||
onClose,
|
||||
cropAspectRatio,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [percentCrop, setPercentCrop] = useState<PercentCrop | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [darken, setDarken] = useState(0);
|
||||
const [imageWidth, setImageWidth] = useState<number | null>(null);
|
||||
const [imageHeight, setImageHeight] = useState<number | null>(null);
|
||||
const cropImageElementRef = useRef<HTMLImageElement>(null);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||
|
||||
const imageUrl = useMemo(
|
||||
() => imageBlob && URL.createObjectURL(imageBlob),
|
||||
[imageBlob],
|
||||
);
|
||||
|
||||
function resetEditState() {
|
||||
setPercentCrop(undefined);
|
||||
setDarken(0);
|
||||
}
|
||||
|
||||
async function handleSaveModifiedImage() {
|
||||
if (!percentCrop || !imageWidth || !imageHeight) return;
|
||||
if (!cropImageElementRef.current) throw new Error("No image");
|
||||
|
||||
const width = cropImageElementRef.current.width;
|
||||
const height = cropImageElementRef.current.height;
|
||||
const pixelCrop = convertToPixelCrop(percentCrop, width, height);
|
||||
|
||||
try {
|
||||
const blob = await getModifiedImageBlob(
|
||||
cropImageElementRef.current,
|
||||
pixelCrop,
|
||||
darken,
|
||||
);
|
||||
|
||||
onSaveImageClick?.(blob);
|
||||
resetEditState();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
devlog("getCroppedImageBlob error", error);
|
||||
enqueueSnackbar("Не удалось изменить изображение");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRotateClick() {
|
||||
if (!cropImageElementRef.current) throw new Error("No image");
|
||||
|
||||
try {
|
||||
const blob = await getRotatedImageBlob(cropImageElementRef.current);
|
||||
|
||||
setCropModalImageBlob(blob);
|
||||
setPercentCrop(undefined);
|
||||
} catch (error) {
|
||||
devlog("getRotatedImageBlob error", error);
|
||||
enqueueSnackbar("Не удалось изменить изображение");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveOriginalImage() {
|
||||
if (!originalImageUrl) return;
|
||||
|
||||
const response = await fetch(originalImageUrl);
|
||||
const blob = await response.blob();
|
||||
|
||||
onSaveImageClick?.(blob);
|
||||
resetEditState();
|
||||
onClose();
|
||||
}
|
||||
|
||||
function handleSizeChange(value: number) {
|
||||
setPercentCrop((prev) => {
|
||||
if (!imageWidth || !imageHeight) return;
|
||||
|
||||
const crop = makeAspectCrop(
|
||||
{
|
||||
unit: "%",
|
||||
width: value,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
cropAspectRatio ? cropAspectRatio.width / cropAspectRatio.height : 1,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
);
|
||||
|
||||
if (!prev || prev.height === 0 || prev.width === 0) {
|
||||
return centerCrop(crop, imageWidth, imageHeight);
|
||||
}
|
||||
|
||||
crop.x = Math.min(
|
||||
100 - crop.width,
|
||||
Math.max(0, prev.x + (prev.width - crop.width) / 2),
|
||||
);
|
||||
crop.y = Math.min(
|
||||
100 - crop.height,
|
||||
Math.max(0, prev.y + (prev.height - crop.height) / 2),
|
||||
);
|
||||
|
||||
return crop;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
resetEditState();
|
||||
onClose();
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
padding: "20px",
|
||||
borderRadius: "8px",
|
||||
width: isMobile ? "343px" : "620px",
|
||||
height: isMobile ? "80vh" : undefined,
|
||||
display: isMobile ? "flex" : undefined,
|
||||
flexDirection: isMobile ? "column" : undefined,
|
||||
justifyContent: isMobile ? "flex-start" : undefined,
|
||||
overflow: isMobile ? "auto" : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "320px",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{imageUrl && (
|
||||
<ReactCrop
|
||||
crop={percentCrop}
|
||||
onChange={(_, percentCrop) => setPercentCrop(percentCrop)}
|
||||
minWidth={5}
|
||||
minHeight={5}
|
||||
locked
|
||||
aspect={
|
||||
cropAspectRatio
|
||||
? cropAspectRatio.width / cropAspectRatio.height
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<img
|
||||
onLoad={(e) => {
|
||||
setImageWidth(e.currentTarget.naturalWidth);
|
||||
setImageHeight(e.currentTarget.naturalHeight);
|
||||
|
||||
if (cropImageElementRef.current) {
|
||||
setPercentCrop(
|
||||
getInitialCrop(
|
||||
cropImageElementRef.current.width,
|
||||
cropImageElementRef.current.height,
|
||||
cropAspectRatio
|
||||
? cropAspectRatio.width / cropAspectRatio.height
|
||||
: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
ref={cropImageElementRef}
|
||||
alt="Crop me"
|
||||
src={imageUrl}
|
||||
style={{
|
||||
filter: `brightness(${100 - darken}%)`,
|
||||
maxWidth: "100%",
|
||||
maxHeight: "320px",
|
||||
display: "block",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</ReactCrop>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "40px",
|
||||
display: isMobile ? "block" : "flex",
|
||||
alignItems: "end",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={handleRotateClick}>
|
||||
<ResetIcon />
|
||||
</IconButton>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Размер
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={[
|
||||
styleSlider,
|
||||
{
|
||||
width: isMobile ? undefined : "200px",
|
||||
},
|
||||
]}
|
||||
value={percentCrop?.width ?? 1}
|
||||
min={1}
|
||||
max={100}
|
||||
step={0.1}
|
||||
onChange={(_, newValue) => {
|
||||
if (typeof newValue === "number") handleSizeChange(newValue);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Затемнение
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={[
|
||||
styleSlider,
|
||||
{
|
||||
width: isMobile ? undefined : "200px",
|
||||
},
|
||||
]}
|
||||
value={darken}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onChange={(_, newValue) => setDarken(newValue as number)}
|
||||
/>
|
||||
</Box>
|
||||
{onDeleteClick && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onDeleteClick?.();
|
||||
onClose();
|
||||
}}
|
||||
sx={{
|
||||
height: "48px",
|
||||
width: "48px",
|
||||
p: 0,
|
||||
color: theme.palette.orange.main,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "40px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
flexWrap: isMobile ? "wrap" : undefined,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={handleSaveOriginalImage}
|
||||
disableRipple
|
||||
sx={{
|
||||
height: "48px",
|
||||
color: "#7E2AEA",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #7E2AEA",
|
||||
px: "20px",
|
||||
}}
|
||||
>
|
||||
Сохранить оригинал
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSaveModifiedImage}
|
||||
disableRipple
|
||||
variant="contained"
|
||||
sx={{
|
||||
height: "48px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #7E2AEA",
|
||||
marginRight: "10px",
|
||||
px: "20px",
|
||||
ml: "auto",
|
||||
}}
|
||||
>
|
||||
Сохранить редактированное
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export function useCropModalState(initialOpenState = false) {
|
||||
const [isCropModalOpen, setOpened] = useState(initialOpenState);
|
||||
const [imageBlob, setCropModalImageBlob] = useState<Blob | null>(null);
|
||||
const [originalImageUrl, setOriginalImageUrl] = useState<string | null>(null);
|
||||
|
||||
const closeCropModal = useCallback(() => {
|
||||
setOpened(false);
|
||||
setCropModalImageBlob(null);
|
||||
setOriginalImageUrl(null);
|
||||
}, []);
|
||||
|
||||
const openCropModal = useCallback(
|
||||
async (
|
||||
image: Blob | string,
|
||||
originalImageUrl: string | null | undefined = null,
|
||||
) => {
|
||||
if (typeof image === "string") {
|
||||
const response = await fetch(image);
|
||||
image = await response.blob();
|
||||
}
|
||||
|
||||
const isGif = await isImageBlobAGifFile(image);
|
||||
if (isGif) return;
|
||||
|
||||
setCropModalImageBlob(image);
|
||||
setOriginalImageUrl(originalImageUrl);
|
||||
setOpened(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
isCropModalOpen,
|
||||
openCropModal,
|
||||
closeCropModal,
|
||||
imageBlob,
|
||||
setCropModalImageBlob,
|
||||
originalImageUrl,
|
||||
} as const;
|
||||
}
|
||||
|
||||
function getInitialCrop(
|
||||
imageWidth: number,
|
||||
imageHeight: number,
|
||||
aspectRatio: number,
|
||||
): PercentCrop {
|
||||
const imageAspectRatio = imageWidth / imageHeight;
|
||||
|
||||
return centerCrop(
|
||||
{
|
||||
width:
|
||||
imageAspectRatio < aspectRatio
|
||||
? 100
|
||||
: (100 * aspectRatio) / imageAspectRatio,
|
||||
height:
|
||||
imageAspectRatio < aspectRatio
|
||||
? (100 * imageAspectRatio) / aspectRatio
|
||||
: 100,
|
||||
unit: "%",
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
);
|
||||
}
|
53
src/ui_kit/Modal/CropModal/AlertModalDeleteImage.tsx
Normal file
53
src/ui_kit/Modal/CropModal/AlertModalDeleteImage.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { Box, Button, Modal, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
interface Props {
|
||||
open: boolean;
|
||||
cancelDelete: () => void;
|
||||
deleteImage: () => void;
|
||||
}
|
||||
export default ({
|
||||
open,
|
||||
cancelDelete,
|
||||
deleteImage,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(450));
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={cancelDelete}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
borderRadius: "12px",
|
||||
width: isMobile ? "200px" : "350px",
|
||||
height: "350px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
// height: isMobile ? "91px" : "70px",
|
||||
backgroundColor: "#F2F3F7",
|
||||
padding: isMobile ? "25px 20px 24px 20px" : "25px 43px 24px 20px",
|
||||
borderRadius: "8px 8px 0px 0px",
|
||||
color: "#9A9AAF",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21.33px"
|
||||
}}>
|
||||
Вы уверены, что хотите удалить всю картинку и каждую настройку?
|
||||
</Typography>
|
||||
<Button sx={{margin: "25px"}} variant="contained" onClick={cancelDelete}>нет</Button>
|
||||
<Button sx={{margin: "25px"}} variant="outlined" onClick={deleteImage}>да</Button>
|
||||
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
328
src/ui_kit/Modal/CropModal/CropGeneral.tsx
Normal file
328
src/ui_kit/Modal/CropModal/CropGeneral.tsx
Normal file
@ -0,0 +1,328 @@
|
||||
import { FC, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import ReactCrop, {
|
||||
PercentCrop,
|
||||
centerCrop,
|
||||
convertToPixelCrop,
|
||||
makeAspectCrop,
|
||||
type Crop
|
||||
} from "react-image-crop";
|
||||
import {
|
||||
getModifiedImageBlob,
|
||||
getRotatedImageBlob,
|
||||
} from "./utils/imageManipulation";
|
||||
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Slider,
|
||||
SxProps,
|
||||
Theme,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { ResetIcon } from "@icons/ResetIcon";
|
||||
import "react-image-crop/dist/ReactCrop.css";
|
||||
import { EditedImagesChangeType } from "./CropModal";
|
||||
import { CropAspectRatio, DEFAULTCROPRULES, EditedImage } from "@/model/CropModal/CropModal";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
|
||||
const styleSlider: SxProps<Theme> = {
|
||||
color: "#7E2AEA",
|
||||
height: "10px",
|
||||
p: "18px 0",
|
||||
"& .MuiSlider-track": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
border: `1px solid #9A9AAF`,
|
||||
},
|
||||
"& .MuiSlider-thumb": {
|
||||
height: 24,
|
||||
width: 24,
|
||||
border: `6px solid #7E2AEA`,
|
||||
backgroundColor: "white",
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
editedImage: EditedImage;
|
||||
cropAspectRatio: CropAspectRatio;
|
||||
editedImagesChange: EditedImagesChangeType;
|
||||
};
|
||||
|
||||
export const CropGeneral: FC<Props> = ({
|
||||
editedImage,
|
||||
cropAspectRatio,
|
||||
editedImagesChange,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||
|
||||
const { crop, darken } = editedImage.newRules;
|
||||
|
||||
const cropImageElementRef = useRef<HTMLImageElement>(null);
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number | null>(null);
|
||||
const [imageHeight, setImageHeight] = useState<number | null>(null);
|
||||
|
||||
async function handleRotateClick() {
|
||||
|
||||
if (cropImageElementRef.current !== null) {
|
||||
try {
|
||||
const blob = await getRotatedImageBlob(cropImageElementRef.current);
|
||||
|
||||
editedImagesChange((old) => {
|
||||
const newRotate = old.newRules.rotate + 90;
|
||||
|
||||
return {
|
||||
newRules: {
|
||||
...old.newRules,
|
||||
rotate: newRotate > 360 ? 0 : newRotate
|
||||
},
|
||||
url: (URL.createObjectURL(blob))
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
devlog("getRotatedImageBlob error", error);
|
||||
enqueueSnackbar("Не удалось изменить изображение");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function handleSizeChange(value: number) {
|
||||
editedImagesChange((old) => {
|
||||
if (!imageWidth || !imageHeight) return old;
|
||||
|
||||
const crop = makeAspectCrop(
|
||||
{
|
||||
unit: "%",
|
||||
width: value,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
cropAspectRatio ? cropAspectRatio.width / cropAspectRatio.height : 1,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
);
|
||||
|
||||
//Хз зачем это было нужно, как будет работать - перетещу
|
||||
|
||||
// if (!old.newRules.crop || old.newRules.crop.height === 0 || old.newRules.crop.width === 0) {
|
||||
// return centerCrop(crop, imageWidth, imageHeight);
|
||||
// }
|
||||
|
||||
crop.x = Math.min(
|
||||
100 - crop.width,
|
||||
Math.max(0, old.newRules.crop.x + (old.newRules.crop.width - crop.width) / 2),
|
||||
);
|
||||
crop.y = Math.min(
|
||||
100 - crop.height,
|
||||
Math.max(0, old.newRules.crop.y + (old.newRules.crop.height - crop.height) / 2),
|
||||
);
|
||||
|
||||
return {
|
||||
newRules: {
|
||||
...old.newRules,
|
||||
crop
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const calcCrop = () => {
|
||||
if (cropImageElementRef.current) {
|
||||
editedImagesChange((old) => ({
|
||||
newRules: {
|
||||
...old.newRules,
|
||||
crop: getInitialCrop(
|
||||
cropImageElementRef.current?.width,
|
||||
cropImageElementRef.current?.height,
|
||||
cropAspectRatio
|
||||
? cropAspectRatio.width / cropAspectRatio.height
|
||||
: 2,
|
||||
)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!crop.width && !crop.height) { calcCrop() }
|
||||
}, [crop])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
height: "320px",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0 20px",
|
||||
marginTop: isMobile ? "19px" : "20px",
|
||||
}}
|
||||
>
|
||||
<ReactCrop
|
||||
crop={crop}
|
||||
onChange={(_, percentCrop) => editedImagesChange((old) => {
|
||||
return ({
|
||||
newRules: {
|
||||
...old.newRules,
|
||||
crop: percentCrop
|
||||
}
|
||||
})
|
||||
}
|
||||
)}
|
||||
|
||||
minWidth={5}
|
||||
minHeight={5}
|
||||
locked
|
||||
aspect={
|
||||
cropAspectRatio
|
||||
? cropAspectRatio.width / cropAspectRatio.height
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<img
|
||||
id="imgid"
|
||||
onLoad={(e) => {
|
||||
setImageWidth(e.currentTarget.naturalWidth);
|
||||
setImageHeight(e.currentTarget.naturalHeight);
|
||||
|
||||
calcCrop()
|
||||
}}
|
||||
ref={cropImageElementRef}
|
||||
alt="Crop me"
|
||||
src={editedImage.url}
|
||||
style={{
|
||||
filter: `brightness(${100 - editedImage.newRules.darken}%)`,
|
||||
maxWidth: "100%",
|
||||
height: "320px",
|
||||
maxHeight: "320px",
|
||||
display: "block",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
crossOrigin = 'anonymous'
|
||||
/>
|
||||
</ReactCrop>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: isMobile ? "20px" : "48px",
|
||||
display: "flex",
|
||||
alignItems: "end",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 20px",
|
||||
flexDirection: isMobile ? "column" : "",
|
||||
|
||||
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={handleRotateClick}
|
||||
sx={{
|
||||
mb: "11px",
|
||||
p: "0",
|
||||
|
||||
}}
|
||||
>
|
||||
<ResetIcon />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isMobile ? "column" : "",
|
||||
width: isMobile ? "100%" : "auto",
|
||||
gap: "24px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Размер
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={[
|
||||
styleSlider,
|
||||
{
|
||||
width: isMobile ? undefined : "248px",
|
||||
},
|
||||
]}
|
||||
value={crop.width}
|
||||
min={1}
|
||||
max={100}
|
||||
step={0.1}
|
||||
onChange={(_, newValue) => {
|
||||
if (typeof newValue === "number") handleSizeChange(newValue);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px", ml: "-1px", }}>
|
||||
Затемнение
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={[
|
||||
styleSlider,
|
||||
{
|
||||
|
||||
width: isMobile ? undefined : "248px",
|
||||
},
|
||||
]}
|
||||
value={darken}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onChange={(_, newValue) => editedImagesChange((old) => ({
|
||||
newRules: {
|
||||
...old.newRules,
|
||||
darken: newValue as number
|
||||
}
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
)
|
||||
};
|
||||
|
||||
function getInitialCrop(
|
||||
imageWidth: number | null | undefined,
|
||||
imageHeight: number | null | undefined,
|
||||
aspectRatio: number,
|
||||
): PercentCrop {
|
||||
if (!imageHeight || !imageWidth) return DEFAULTCROPRULES.crop
|
||||
const imageAspectRatio = imageWidth / imageHeight;
|
||||
|
||||
return centerCrop(
|
||||
{
|
||||
width:
|
||||
imageAspectRatio < aspectRatio
|
||||
? 100
|
||||
: (100 * aspectRatio) / imageAspectRatio,
|
||||
height:
|
||||
imageAspectRatio < aspectRatio
|
||||
? (100 * imageAspectRatio) / aspectRatio
|
||||
: 100,
|
||||
unit: "%",
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
);
|
||||
}
|
122
src/ui_kit/Modal/CropModal/CropModal.tsx
Normal file
122
src/ui_kit/Modal/CropModal/CropModal.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { FC, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import WorkSpace from "./WorkSpace";
|
||||
import { NavigationPanel } from "./NavigationPanel";
|
||||
import {
|
||||
Box,
|
||||
Modal,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
DEFAULTCROPRULES,
|
||||
type CropModalProps,
|
||||
type EditedImage,
|
||||
type ScreenStepsTypes
|
||||
} from "@/model/CropModal/CropModal";
|
||||
|
||||
const PriorityOfSteps = ["desktop", "tablet", "mobile", "small"];
|
||||
|
||||
export type EditedImagesChangeType = (changed: (old: EditedImage) => Partial<EditedImage>) => void;
|
||||
|
||||
export const CropModal: FC<CropModalProps> = ({
|
||||
open,
|
||||
editedImages,
|
||||
workSpaceTypes,
|
||||
originalImageUrl,
|
||||
|
||||
setEditedImages,
|
||||
onSaveImageClick,
|
||||
closeCropModal,
|
||||
onDeleteClick,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||
|
||||
const currentStepName: ScreenStepsTypes = useMemo(() => {
|
||||
const mainSteps = Object.keys(workSpaceTypes);
|
||||
const POS = PriorityOfSteps.filter(POS => mainSteps.find(e => POS === e))
|
||||
return POS[currentStep] as ScreenStepsTypes
|
||||
}, [currentStep])
|
||||
const nextStepName: ScreenStepsTypes = useMemo(() => {
|
||||
const mainSteps = Object.keys(workSpaceTypes);
|
||||
const POS = PriorityOfSteps.filter(POS => mainSteps.find(e => POS === e))
|
||||
return POS[currentStep+1] as ScreenStepsTypes
|
||||
}, [currentStep])
|
||||
|
||||
const editedImagesChange: EditedImagesChangeType = (changed) => {
|
||||
setEditedImages(old => {
|
||||
const newData = { ...old };
|
||||
newData[currentStepName] = { ...old[currentStepName], ...changed(old[currentStepName]) };
|
||||
return newData;
|
||||
})
|
||||
}
|
||||
|
||||
const resetImage = () => {
|
||||
editedImagesChange(() => ({
|
||||
url: originalImageUrl,
|
||||
newRules: DEFAULTCROPRULES,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={closeCropModal}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
borderRadius: "12px",
|
||||
width: isMobile ? "343px" : "620px",
|
||||
height: isMobile ? "80vh" : undefined,
|
||||
display: isMobile ? "flex" : undefined,
|
||||
flexDirection: isMobile ? "column" : undefined,
|
||||
justifyContent: isMobile ? "flex-start" : undefined,
|
||||
overflow: isMobile ? "auto" : undefined,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
// height: isMobile ? "91px" : "70px",
|
||||
backgroundColor: "#F2F3F7",
|
||||
padding: isMobile ? "25px 20px 24px 20px" : "25px 43px 24px 20px",
|
||||
borderRadius: "8px 8px 0px 0px",
|
||||
color: "#9A9AAF",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21.33px"
|
||||
}}>
|
||||
Настройте вариант отображения картинки на разных девайсах
|
||||
</Typography>
|
||||
<WorkSpace
|
||||
//Информация о изменяемой сейчас картинке
|
||||
editedImage={editedImages[currentStepName]}
|
||||
//По каким правилам меняем
|
||||
cropAspectRatio={workSpaceTypes[currentStepName].ratio}
|
||||
currentStep={currentStep}
|
||||
currentStepName={currentStepName}
|
||||
|
||||
editedImagesChange={editedImagesChange}
|
||||
onDeleteClick={onDeleteClick}
|
||||
/>
|
||||
<NavigationPanel
|
||||
nextStepName={nextStepName}
|
||||
currentStep={currentStep}
|
||||
setCurrentStep={setCurrentStep}
|
||||
totalSteps={Object.keys(workSpaceTypes).length}
|
||||
onSaveImageClick={onSaveImageClick}
|
||||
resetImage={resetImage}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import {Box} from "@mui/material";
|
||||
|
||||
export default function DevaceDesktopIcon() {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: "#EEE4FC"
|
||||
}}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="17" width="20" height="3" rx="1" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||
<rect x="3" y="5" width="18" height="12" rx="1" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||
<path d="M14 5.5L10 5.5" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import {Box} from "@mui/material";
|
||||
|
||||
export default function DevaceMobileIcon() {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: "#EEE4FC"
|
||||
}}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="6" y="2" width="12" height="20" rx="3" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||
<path d="M14 2.5L10 2.5" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user