feat: start page preview

This commit is contained in:
IlyaDoronin 2023-11-01 16:31:15 +03:00
parent 34a01a5c1c
commit 74a1fac8f0
8 changed files with 375 additions and 45 deletions

@ -0,0 +1,35 @@
import { Box, SxProps, Theme } from "@mui/material";
interface Props {
sx?: SxProps<Theme>;
}
export default function ResizeIcon({ sx }: Props) {
return (
<Box
sx={{
height: "20px",
width: "20px",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "nwse-resize",
pointerEvents: "auto",
...sx,
}}
>
<svg
width="20"
height="20"
viewBox="0 0 30 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.5 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM19.5 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10.5 16.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
fill="#4D4D4D"
/>
</svg>
</Box>
);
}

@ -1,3 +1,4 @@
import { ChangeEvent, useState } from "react";
import { import {
Box, Box,
Button, Button,
@ -12,7 +13,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { ChangeEvent, useState } from "react"; import { createPortal } from "react-dom";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import AlignLeftIcon from "@icons/AlignLeftIcon"; import AlignLeftIcon from "@icons/AlignLeftIcon";
import AlignRightIcon from "@icons/AlignRightIcon"; import AlignRightIcon from "@icons/AlignRightIcon";
@ -31,11 +32,12 @@ import { quizStore } from "@root/quizes";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import * as React from "react"; import * as React from "react";
import ModalSizeImage from "./ModalSizeImage"; import ModalSizeImage from "./ModalSizeImage";
import DropZone from "./dropZone"; import { DropZone } from "./dropZone";
import Extra from "./extra"; import Extra from "./extra";
import AlignCenterIcon from "@icons/AlignCenterIcon"; import AlignCenterIcon from "@icons/AlignCenterIcon";
import DropFav from "./dropfavicon"; import DropFav from "./dropfavicon";
import { createQuestion } from "@root/questions"; import { createQuestion } from "@root/questions";
import { StartPagePreview } from "@ui_kit/StartPagePreview";
const designTypes = [ const designTypes = [
[ [
@ -906,6 +908,7 @@ export default function StartPageSettings() {
Настроить вопросы Настроить вопросы
</Button> </Button>
</Box> </Box>
{createPortal(<StartPagePreview />, document.body)}
</> </>
); );
} }

@ -1,8 +1,19 @@
import { useState } from "react"; import { useState } from "react";
import { Box, ButtonBase, useTheme, Typography, SxProps, Theme } from "@mui/material"; import { useParams } from "react-router-dom";
import UploadIcon from "../../assets/icons/UploadIcon"; import {
Box,
ButtonBase,
useTheme,
Typography,
SxProps,
Theme,
} from "@mui/material";
import { SnackbarProvider, enqueueSnackbar } from "notistack"; import { SnackbarProvider, enqueueSnackbar } from "notistack";
import { quizStore } from "@root/quizes";
import UploadIcon from "@icons/UploadIcon";
interface Props { interface Props {
text?: string; text?: string;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
@ -11,21 +22,35 @@ interface Props {
} }
//Научи функцию принимать данные для валидации //Научи функцию принимать данные для валидации
export default ({ text, sx, heightImg, widthImg }: Props) => { export const DropZone = ({ text, sx, heightImg, widthImg }: Props) => {
const quizId = Number(useParams().quizId);
const { listQuizes, updateQuizesList } = quizStore();
const [ready, setReady] = useState(false);
const theme = useTheme(); const theme = useTheme();
const quiz = listQuizes[quizId];
console.log(quiz.config.startpage.background.desktop);
const imgHC = (imgInp: HTMLInputElement) => { const imgHC = (imgInp: HTMLInputElement) => {
const file = imgInp.files?.[0]; const file = imgInp.files?.[0];
if (file) { if (file) {
if (file.size < 5242880) { if (file.size < 5242880) {
setData(URL.createObjectURL(file)); updateQuizesList(quizId, {
config: {
...quiz.config,
startpage: {
...quiz.config.startpage,
background: {
...quiz.config.startpage.background,
desktop: URL.createObjectURL(file),
},
},
},
});
} else { } else {
enqueueSnackbar("Размер картинки слишком велик"); enqueueSnackbar("Размер картинки слишком велик");
} }
} }
}; };
const [data, setData] = useState("");
const [ready, setReady] = useState(false);
const dragenterHC = () => { const dragenterHC = () => {
setReady(true); setReady(true);
@ -41,7 +66,18 @@ export default ({ text, sx, heightImg, widthImg }: Props) => {
const file = event.dataTransfer.files[0]; const file = event.dataTransfer.files[0];
if (file.size < 5242880) { if (file.size < 5242880) {
setData(URL.createObjectURL(file)); updateQuizesList(quizId, {
config: {
...quiz.config,
startpage: {
...quiz.config.startpage,
background: {
...quiz.config.startpage.background,
desktop: URL.createObjectURL(file),
},
},
},
});
} else { } else {
enqueueSnackbar("Размер картинки слишком велик"); enqueueSnackbar("Размер картинки слишком велик");
} }
@ -53,7 +89,13 @@ export default ({ text, sx, heightImg, widthImg }: Props) => {
return ( return (
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}> <ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
<input onChange={(event) => imgHC(event.target)} hidden accept="image/*" multiple type="file" /> <input
onChange={(event) => imgHC(event.target)}
hidden
accept="image/*"
multiple
type="file"
/>
<Box <Box
onDragEnter={dragenterHC} onDragEnter={dragenterHC}
onDragExit={dragexitHC} onDragExit={dragexitHC}
@ -69,7 +111,7 @@ export default ({ text, sx, heightImg, widthImg }: Props) => {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`, border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
borderRadius: "8px", borderRadius: "8px",
opacity: data ? "0.5" : 1, opacity: quiz.config.startpage.background.desktop ? "0.5" : 1,
...sx, ...sx,
}} }}
> >
@ -87,19 +129,22 @@ export default ({ text, sx, heightImg, widthImg }: Props) => {
> >
{text} {text}
</Typography> </Typography>
<SnackbarProvider style={{ backgroundColor: theme.palette.brightPurple.main }} /> <SnackbarProvider
{data ? ( style={{ backgroundColor: theme.palette.brightPurple.main }}
/>
{quiz.config.startpage.background.desktop && (
<img <img
height={heightImg} height={heightImg}
width={widthImg} width={widthImg}
src={data} src={quiz.config.startpage.background.desktop}
style={{ style={{
position: "absolute", position: "absolute",
zIndex: "-1", zIndex: "-1",
objectFit: "revert-layer", objectFit: "revert-layer",
}} }}
alt="img"
/> />
) : null} )}
</Box> </Box>
</ButtonBase> </ButtonBase>
); );

@ -133,9 +133,9 @@ export const quizStore = create<QuizStore>()(
"position": "ltr", // ltr или rtl. отображение элементов поверх фона "position": "ltr", // ltr или rtl. отображение элементов поверх фона
"background": { "background": {
"type": "image", //image или video "type": "image", //image или video
"desktop": "hub.pena.digital/img/back/full.png", "desktop": "",
"mobile": "hub.pena.digital/img/back/mobile.png", "mobile": "",
"video":"hub.pena.digital/vid/back/vi1.mp4", "video":"",
"cycle": true //зацикливать видео или нет "cycle": true //зацикливать видео или нет
}, },
}, },

@ -5,7 +5,7 @@ import { useLayoutEffect, useRef } from "react";
import { Rnd } from "react-rnd"; import { Rnd } from "react-rnd";
import { useWindowSize } from "../../utils/hooks/useWindowSize"; import { useWindowSize } from "../../utils/hooks/useWindowSize";
import QuizPreviewLayout from "./QuizPreviewLayout"; import QuizPreviewLayout from "./QuizPreviewLayout";
import ResizeIcon from "./ResizeIcon"; import ResizeIcon from "@icons/ResizeIcon";
const DRAG_PARENT_MARGIN = 0; const DRAG_PARENT_MARGIN = 0;
const NAVBAR_HEIGHT = 0; const NAVBAR_HEIGHT = 0;

@ -1,26 +0,0 @@
import { Box, SxProps, Theme } from "@mui/material";
interface Props {
sx?: SxProps<Theme>;
}
export default function ResizeIcon({ sx }: Props) {
return (
<Box sx={{
height: "20px",
width: "20px",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "nwse-resize",
pointerEvents: "auto",
...sx,
}}>
<svg width="20" height="20" viewBox="0 0 30 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM19.5 8a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10.5 16.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" fill="#4D4D4D" />
</svg>
</Box>
);
}

@ -0,0 +1,139 @@
import {
Box,
Button,
Paper,
Typography,
useTheme,
useMediaQuery,
} from "@mui/material";
import { useParams } from "react-router-dom";
import { quizStore } from "@root/quizes";
export default function QuizPreviewLayout() {
const quizId = Number(useParams().quizId);
const { listQuizes } = quizStore();
const question = listQuizes[quizId];
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(630));
const isMediaFileExist =
(question.config.startpage.background.type === "image" &&
question.config.startpage.background.desktop) ||
(question.config.startpage.background.type === "video" &&
question.config.startpage.background.video);
return (
<Paper className="quiz-preview-draghandle" sx={{ height: "100%" }}>
<Box
sx={{
display: "flex",
flexDirection:
question.config.startpage.position === "ltr"
? "row"
: "row-reverse",
flexGrow: 1,
height: "100%",
"&::-webkit-scrollbar": { width: 0 },
}}
>
<Box
sx={{
width: isMediaFileExist && !isTablet ? "40%" : "100%",
padding: "16px",
display: "flex",
flexDirection: "column",
alignItems: isMediaFileExist && !isTablet ? "flex-start" : "center",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
{question.config.startpage.background.type === "image" &&
question.config.startpage.background.desktop && (
<img
src={question.config.startpage.background.desktop}
style={{
height: "30px",
maxWidth: "50px",
objectFit: "cover",
}}
alt=""
/>
)}
<Typography sx={{ fontSize: "12px" }}>
{question.config.info.orgname}
</Typography>
</Box>
<Box
sx={{
flexGrow: 1,
display: "flex",
gap: "10px",
flexDirection: "column",
justifyContent: "center",
}}
>
<Typography sx={{ fontWeight: "bold" }}>{question.name}</Typography>
<Typography sx={{ fontSize: "12px" }}>
{question.config.startpage.description}
</Typography>
<Box>
{question.config.startpage.button && (
<Button
variant="contained"
sx={{
fontSize: "16px",
padding: "10px 15px",
}}
>
{question.config.startpage.button}
</Button>
)}
</Box>
</Box>
<Box>
<Typography
sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}
>
{question.config.info.phonenumber}
</Typography>
<Typography sx={{ fontSize: "12px" }}>
{question.config.info.law}
</Typography>
</Box>
</Box>
{!isTablet && isMediaFileExist && (
<Box sx={{ width: "60%" }}>
{question.config.startpage.background.type === "image" &&
question.config.startpage.background.desktop && (
<img
src={question.config.startpage.background.desktop}
alt=""
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
)}
{question.config.startpage.background.type === "video" &&
question.config.startpage.background.video && (
<video
src={question.config.startpage.background.video}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
)}
</Box>
)}
</Box>
</Paper>
);
}

@ -0,0 +1,134 @@
import VisibilityIcon from "@mui/icons-material/Visibility";
import { Box, IconButton, useTheme, useMediaQuery } from "@mui/material";
import { toggleQuizPreview, useQuizPreviewStore } from "@root/quizPreview";
import { useLayoutEffect, useRef } from "react";
import { Rnd } from "react-rnd";
import QuizPreviewLayout from "./QuizPreviewLayout";
import ResizeIcon from "@icons/ResizeIcon";
const DRAG_PARENT_MARGIN = 0;
const NAVBAR_HEIGHT = 0;
const DRAG_PARENT_BOTTOM_MARGIN = 0;
interface RndPositionAndSize {
x: number;
y: number;
width: string;
height: string;
}
export const StartPagePreview = () => {
const isPreviewShown = useQuizPreviewStore((state) => state.isPreviewShown);
const rndParentRef = useRef<HTMLDivElement>(null);
const rndRef = useRef<Rnd | null>(null);
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(630));
const rndPositionAndSizeRef = useRef<RndPositionAndSize>({
x: 0,
y: 0,
width: isTablet ? "350" : "600",
height: "330",
});
const isFirstShowRef = useRef<boolean>(true);
useLayoutEffect(
function stickPreviewToBottomRight() {
const rnd = rndRef.current;
const rndSelfElement = rnd?.getSelfElement();
if (
!rnd ||
!rndSelfElement ||
!rndParentRef.current ||
!isFirstShowRef.current
)
return;
const rndParentRect = rndParentRef.current.getBoundingClientRect();
const rndRect = rndSelfElement.getBoundingClientRect();
const x = rndParentRect.width - rndRect.width;
const y = rndParentRect.height - rndRect.height;
rnd.updatePosition({ x, y });
rndPositionAndSizeRef.current.x = x;
rndPositionAndSizeRef.current.y = y;
isFirstShowRef.current = false;
},
[isPreviewShown]
);
return (
<Box
ref={rndParentRef}
sx={{
position: "fixed",
top: NAVBAR_HEIGHT + DRAG_PARENT_MARGIN,
left: DRAG_PARENT_MARGIN,
bottom: DRAG_PARENT_BOTTOM_MARGIN,
right: DRAG_PARENT_MARGIN,
pointerEvents: "none",
zIndex: 100,
}}
>
{isPreviewShown && (
<Rnd
minHeight={20}
minWidth={20}
bounds="parent"
ref={rndRef}
dragHandleClassName="quiz-preview-draghandle"
default={{
x: rndPositionAndSizeRef.current.x,
y: rndPositionAndSizeRef.current.y,
width: rndPositionAndSizeRef.current.width,
height: rndPositionAndSizeRef.current.height,
}}
onResizeStop={(e, direction, ref, delta, position) => {
rndPositionAndSizeRef.current.x = position.x;
rndPositionAndSizeRef.current.y = position.y;
rndPositionAndSizeRef.current.width = ref.style.width;
rndPositionAndSizeRef.current.height = ref.style.height;
}}
onDragStop={(e, d) => {
rndPositionAndSizeRef.current.x = d.x;
rndPositionAndSizeRef.current.y = d.y;
}}
onDragStart={(e, d) => {
e.preventDefault();
}}
enableResizing={{
topLeft: isPreviewShown,
}}
resizeHandleComponent={{
topLeft: <ResizeIcon />,
}}
resizeHandleStyles={{
topLeft: {
top: "-1px",
left: "-1px",
},
}}
style={{
overflow: "hidden",
pointerEvents: "auto",
borderRadius: "5px",
}}
>
<QuizPreviewLayout />
</Rnd>
)}
<IconButton
onClick={toggleQuizPreview}
sx={{
position: "absolute",
right: 0,
bottom: 0,
pointerEvents: "auto",
}}
>
<VisibilityIcon sx={{ height: "30px", width: "30px" }} />
</IconButton>
</Box>
);
};