add widgets
This commit is contained in:
parent
2ee1a72259
commit
5f04a9d198
@ -1,22 +1,23 @@
|
|||||||
import { getQuizData } from "@/api/quizRelase";
|
import { getQuizData } from "@/api/quizRelase";
|
||||||
|
import { QuizViewContext, createQuizViewStore } from "@/stores/quizView";
|
||||||
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
|
import LoadingSkeleton from "@/ui_kit/LoadingSkeleton";
|
||||||
import { QuizDataContext } from "@contexts/QuizDataContext";
|
import { QuizDataContext } from "@contexts/QuizDataContext";
|
||||||
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
|
import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext";
|
||||||
import { QuizSettings } from "@model/settingsData";
|
import { QuizSettings } from "@model/settingsData";
|
||||||
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
|
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
|
||||||
|
import ScopedCssBaseline from "@mui/material/ScopedCssBaseline";
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
import { ruRU } from "@mui/x-date-pickers/locales";
|
||||||
import { handleComponentError } from "@utils/handleComponentError";
|
import { handleComponentError } from "@utils/handleComponentError";
|
||||||
import lightTheme from "@utils/themes/light";
|
import lightTheme from "@utils/themes/light";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { SnackbarProvider } from 'notistack';
|
import { SnackbarProvider } from "notistack";
|
||||||
import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react";
|
import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
|
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
|
||||||
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
||||||
import { QuizViewContext, createQuizViewStore } from "@/stores/quizView";
|
|
||||||
|
|
||||||
|
|
||||||
moment.locale("ru");
|
moment.locale("ru");
|
||||||
@ -28,9 +29,10 @@ type Props = {
|
|||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
changeFaviconAndTitle?: boolean;
|
changeFaviconAndTitle?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
disableGlobalCss?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function QuizAnswerer({ quizSettings, quizId, preview = false, changeFaviconAndTitle = true, className }: Props) {
|
function QuizAnswererInner({ quizSettings, quizId, preview = false, changeFaviconAndTitle = true, className, disableGlobalCss = false }: Props) {
|
||||||
const [quizViewStore] = useState(createQuizViewStore);
|
const [quizViewStore] = useState(createQuizViewStore);
|
||||||
const [rootContainerWidth, setRootContainerWidth] = useState<number>(() => window.innerWidth);
|
const [rootContainerWidth, setRootContainerWidth] = useState<number>(() => window.innerWidth);
|
||||||
const rootContainerRef = useRef<HTMLDivElement>(null);
|
const rootContainerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -58,63 +60,71 @@ export default function QuizAnswerer({ quizSettings, quizId, preview = false, ch
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) return (
|
if (isLoading) return <LoadingSkeleton />;
|
||||||
<ThemeProvider theme={lightTheme}>
|
if (error) return <ApologyPage error={error} />;
|
||||||
<LoadingSkeleton />
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
if (error) return (
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<ApologyPage error={error} />
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
quizSettings ??= data;
|
quizSettings ??= data;
|
||||||
if (!quizSettings) throw new Error("Quiz data is null");
|
if (!quizSettings) throw new Error("Quiz data is null");
|
||||||
|
|
||||||
if (quizSettings.questions.length === 0) return (
|
if (quizSettings.questions.length === 0) return <ApologyPage error={new Error("No questions found")} />;
|
||||||
<ThemeProvider theme={lightTheme}>
|
if (!quizId) return <ApologyPage error={new Error("No quiz id")} />;
|
||||||
<ApologyPage error={new Error("No questions found")} />
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
if(!quizId) return (
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<ApologyPage error={error} />
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const quizContainer = (
|
||||||
|
<Box
|
||||||
|
ref={rootContainerRef}
|
||||||
|
className={className}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={ApologyPage}
|
||||||
|
onError={handleComponentError}
|
||||||
|
>
|
||||||
|
<ViewPublicationPage />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QuizViewContext.Provider value={quizViewStore}>
|
<QuizViewContext.Provider value={quizViewStore}>
|
||||||
<RootContainerWidthContext.Provider value={rootContainerWidth}>
|
<RootContainerWidthContext.Provider value={rootContainerWidth}>
|
||||||
<QuizDataContext.Provider value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}>
|
<QuizDataContext.Provider value={{ ...quizSettings, quizId, preview, changeFaviconAndTitle }}>
|
||||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
{disableGlobalCss ? (
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ScopedCssBaseline
|
||||||
<SnackbarProvider
|
sx={{
|
||||||
preventDuplicate={true}
|
height: "100%",
|
||||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
width: "100%",
|
||||||
>
|
backgroundColor: "transparent",
|
||||||
<CssBaseline />
|
}}
|
||||||
<Box
|
>
|
||||||
ref={rootContainerRef}
|
{quizContainer}
|
||||||
className={className}
|
</ScopedCssBaseline>
|
||||||
sx={{
|
) : (
|
||||||
width: "100%",
|
<CssBaseline>
|
||||||
height: "100%",
|
{quizContainer}
|
||||||
}}
|
</CssBaseline>
|
||||||
>
|
)}
|
||||||
<ErrorBoundary
|
|
||||||
FallbackComponent={ApologyPage}
|
|
||||||
onError={handleComponentError}
|
|
||||||
>
|
|
||||||
<ViewPublicationPage />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</Box>
|
|
||||||
</SnackbarProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</LocalizationProvider>
|
|
||||||
</QuizDataContext.Provider>
|
</QuizDataContext.Provider>
|
||||||
</RootContainerWidthContext.Provider>
|
</RootContainerWidthContext.Provider>
|
||||||
</QuizViewContext.Provider>
|
</QuizViewContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function QuizAnswerer(props: Props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<SnackbarProvider
|
||||||
|
preventDuplicate={true}
|
||||||
|
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||||
|
>
|
||||||
|
<QuizAnswererInner {...props} />
|
||||||
|
</SnackbarProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</LocalizationProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ export const ApologyPage = ({ error }: Props) => {
|
|||||||
if (error.response?.data === "quiz is inactive") message = "Квиз не активирован";
|
if (error.response?.data === "quiz is inactive") message = "Квиз не активирован";
|
||||||
if (error.message === "No questions found") message = "Нет созданных вопросов";
|
if (error.message === "No questions found") message = "Нет созданных вопросов";
|
||||||
if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос";
|
if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос";
|
||||||
if (error.message === "No questions found") message = "Вопросы отсутствуют";
|
if (error.message === "No quiz id") message = "Отсутствует id квиза";
|
||||||
if (error.response?.data === "Invalid request data") message = "Такого квиза не существует";
|
if (error.response?.data === "Invalid request data") message = "Такого квиза не существует";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -6,10 +6,11 @@ let domain = "https://hbpn.link";
|
|||||||
const currentDomain = location.hostname;
|
const currentDomain = location.hostname;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentDomain === "s.hbpn.link" ||
|
currentDomain === "s.hbpn.link"
|
||||||
//Исключение - туризм. Он на стейджинговом квизе и на чужом для публикации домене
|
//Исключение - туризм. Он на стейджинговом квизе и на чужом для публикации домене
|
||||||
currentDomain === "tourism.pena.digital" ||
|
|| currentDomain === "tourism.pena.digital"
|
||||||
currentDomain.includes("localhost")
|
|| currentDomain.includes("localhost")
|
||||||
|
|| currentDomain.includes("127.0.0.1")
|
||||||
) domain = "https://s.hbpn.link";
|
) domain = "https://s.hbpn.link";
|
||||||
|
|
||||||
export { domain };
|
export { domain };
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import QuizAnswerer from "@/components/QuizAnswerer";
|
import QuizAnswerer from "@/components/QuizAnswerer";
|
||||||
import { Root, createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export * from "./widgets";
|
||||||
|
|
||||||
|
|
||||||
let root: Root | undefined = undefined;
|
// old widget
|
||||||
|
|
||||||
const widget = {
|
const widget = {
|
||||||
create({ selector, quizId, changeFaviconAndTitle = true }: {
|
create({ selector, quizId, changeFaviconAndTitle = true }: {
|
||||||
selector: string;
|
selector: string;
|
||||||
@ -13,13 +14,16 @@ const widget = {
|
|||||||
const element = document.getElementById(selector);
|
const element = document.getElementById(selector);
|
||||||
if (!element) throw new Error("Element for widget doesn't exist");
|
if (!element) throw new Error("Element for widget doesn't exist");
|
||||||
|
|
||||||
root = createRoot(element);
|
const root = createRoot(element);
|
||||||
|
|
||||||
root.render(<QuizAnswerer quizId={quizId} changeFaviconAndTitle={changeFaviconAndTitle} />);
|
root.render(
|
||||||
|
<QuizAnswerer
|
||||||
|
quizId={quizId}
|
||||||
|
changeFaviconAndTitle={changeFaviconAndTitle}
|
||||||
|
disableGlobalCss
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
unmount() {
|
|
||||||
if (root) root.unmount();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default widget;
|
export default widget;
|
||||||
|
35
src/widgets/QuizDialog.tsx
Normal file
35
src/widgets/QuizDialog.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import QuizAnswerer from "@/components/QuizAnswerer";
|
||||||
|
import { Dialog } from "@mui/material";
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open?: boolean;
|
||||||
|
quizId: string;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuizDialog({ open = true, quizId, onClose }: Props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
keepMounted
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
width: "calc(min(100%, max(70%, 700px)))",
|
||||||
|
height: "80%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
m: "16px",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QuizAnswerer
|
||||||
|
quizId={quizId}
|
||||||
|
changeFaviconAndTitle={false}
|
||||||
|
disableGlobalCss
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
29
src/widgets/banner/BannerWidget.tsx
Normal file
29
src/widgets/banner/BannerWidget.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
import QuizBanner from "./QuizBanner";
|
||||||
|
import { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export class BannerWidget {
|
||||||
|
root: Root | undefined;
|
||||||
|
element = document.createElement("div");
|
||||||
|
|
||||||
|
constructor({ quizId, position }: ComponentPropsWithoutRef<typeof QuizBanner>) {
|
||||||
|
this.element.style.setProperty("display", "none");
|
||||||
|
document.body.appendChild(this.element);
|
||||||
|
|
||||||
|
this.root = createRoot(this.element);
|
||||||
|
|
||||||
|
this.root.render(
|
||||||
|
<QuizBanner
|
||||||
|
quizId={quizId}
|
||||||
|
position={position}
|
||||||
|
onClose={() => this.destroy()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.root) this.root.unmount();
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
82
src/widgets/banner/QuizBanner.tsx
Normal file
82
src/widgets/banner/QuizBanner.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import lightTheme from "@/utils/themes/light";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import { Box, Button, IconButton, ThemeProvider } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import QuizDialog from "../QuizDialog";
|
||||||
|
|
||||||
|
|
||||||
|
const PADDING = 10;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
position: "topleft" | "topright" | "bottomleft" | "bottomright";
|
||||||
|
quizId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuizBanner({ quizId, position, onClose }: Props) {
|
||||||
|
const [isQuizDialogOpen, setIsQuizDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<Box
|
||||||
|
className="pena-quiz-widget-banner"
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
position: "fixed",
|
||||||
|
height: "70px",
|
||||||
|
width: `calc(min(calc(100% - ${PADDING * 2}px), max(500px, 70%)))`,
|
||||||
|
},
|
||||||
|
position === "topleft" && {
|
||||||
|
top: PADDING,
|
||||||
|
left: PADDING,
|
||||||
|
},
|
||||||
|
position === "topright" && {
|
||||||
|
top: PADDING,
|
||||||
|
right: PADDING,
|
||||||
|
},
|
||||||
|
position === "bottomleft" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
left: PADDING,
|
||||||
|
},
|
||||||
|
position === "bottomright" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
right: PADDING,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsQuizDialogOpen(p => !p)}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Пройти квиз
|
||||||
|
</Button>
|
||||||
|
<IconButton
|
||||||
|
onClick={onClose}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
p: 0,
|
||||||
|
width: "34px",
|
||||||
|
height: "34px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
backgroundColor: "#333647",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon sx={{ color: "#FFFFFF" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<QuizDialog
|
||||||
|
open={isQuizDialogOpen}
|
||||||
|
quizId={quizId}
|
||||||
|
onClose={() => setIsQuizDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
</ThemeProvider>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
31
src/widgets/button/ButtonWidget.tsx
Normal file
31
src/widgets/button/ButtonWidget.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
import OpenQuizButton from "./OpenQuizButton";
|
||||||
|
import { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export class ButtonWidget {
|
||||||
|
root: Root | undefined;
|
||||||
|
element = document.createElement("div");
|
||||||
|
|
||||||
|
constructor({ quizId, selector, fixedSide }: ComponentPropsWithoutRef<typeof OpenQuizButton>) {
|
||||||
|
if (!fixedSide && !selector) throw new Error("ButtonWidget: Either selector or fixedSide params must be provided");
|
||||||
|
|
||||||
|
this.element.style.setProperty("display", "none");
|
||||||
|
document.body.appendChild(this.element);
|
||||||
|
|
||||||
|
this.root = createRoot(this.element);
|
||||||
|
|
||||||
|
this.root.render(
|
||||||
|
<OpenQuizButton
|
||||||
|
selector={selector}
|
||||||
|
fixedSide={fixedSide}
|
||||||
|
quizId={quizId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.root) this.root.unmount();
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
55
src/widgets/button/OpenQuizButton.tsx
Normal file
55
src/widgets/button/OpenQuizButton.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import lightTheme from "@/utils/themes/light";
|
||||||
|
import { Button, ThemeProvider } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import QuizDialog from "../QuizDialog";
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
selector?: string;
|
||||||
|
fixedSide?: "left" | "right";
|
||||||
|
quizId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OpenQuizButton({ selector, quizId, fixedSide }: Props) {
|
||||||
|
const [isQuizDialogOpen, setIsQuizDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const portalContainer = !fixedSide && selector ? document.getElementById(selector)! : document.body;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
<Button
|
||||||
|
className="pena-quiz-widget-button"
|
||||||
|
onClick={() => setIsQuizDialogOpen(p => !p)}
|
||||||
|
variant="contained"
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
// generic styles
|
||||||
|
},
|
||||||
|
Boolean(fixedSide) && {
|
||||||
|
position: "fixed",
|
||||||
|
bottom: "50%",
|
||||||
|
},
|
||||||
|
fixedSide === "left" && {
|
||||||
|
left: 0,
|
||||||
|
transformOrigin: "left",
|
||||||
|
transform: "rotate(-90deg) translateY(50%) translateX(-50%)",
|
||||||
|
},
|
||||||
|
fixedSide === "right" && {
|
||||||
|
right: 0,
|
||||||
|
transformOrigin: "right",
|
||||||
|
transform: "rotate(-90deg) translateY(-50%) translateX(50%)",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Пройти квиз
|
||||||
|
</Button>
|
||||||
|
<QuizDialog
|
||||||
|
open={isQuizDialogOpen}
|
||||||
|
quizId={quizId}
|
||||||
|
onClose={() => setIsQuizDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
</ThemeProvider>,
|
||||||
|
portalContainer
|
||||||
|
);
|
||||||
|
}
|
29
src/widgets/container/ContainerWidget.tsx
Normal file
29
src/widgets/container/ContainerWidget.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import QuizAnswerer from "@/components/QuizAnswerer";
|
||||||
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
|
||||||
|
export class ContainerWidget {
|
||||||
|
root: Root | undefined;
|
||||||
|
|
||||||
|
constructor({ selector, quizId }: {
|
||||||
|
quizId: string;
|
||||||
|
selector: string;
|
||||||
|
}) {
|
||||||
|
const element = document.getElementById(selector);
|
||||||
|
if (!element) throw new Error("Element for widget doesn't exist");
|
||||||
|
|
||||||
|
this.root = createRoot(element);
|
||||||
|
|
||||||
|
this.root.render(
|
||||||
|
<QuizAnswerer
|
||||||
|
quizId={quizId}
|
||||||
|
changeFaviconAndTitle={false}
|
||||||
|
disableGlobalCss
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.root) this.root.unmount();
|
||||||
|
}
|
||||||
|
}
|
5
src/widgets/index.ts
Normal file
5
src/widgets/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./banner/BannerWidget";
|
||||||
|
export * from "./button/ButtonWidget";
|
||||||
|
export * from "./container/ContainerWidget";
|
||||||
|
export * from "./popup/PopupWidget";
|
||||||
|
export * from "./side/SideWidget";
|
30
src/widgets/popup/PopupWidget.tsx
Normal file
30
src/widgets/popup/PopupWidget.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
import QuizDialog from "../QuizDialog";
|
||||||
|
|
||||||
|
|
||||||
|
export class PopupWidget {
|
||||||
|
root: Root | undefined;
|
||||||
|
element: HTMLDivElement;
|
||||||
|
|
||||||
|
constructor({ quizId }: {
|
||||||
|
quizId: string;
|
||||||
|
}) {
|
||||||
|
this.element = document.createElement("div");
|
||||||
|
this.element.style.setProperty("display", "none");
|
||||||
|
document.body.appendChild(this.element);
|
||||||
|
|
||||||
|
this.root = createRoot(this.element);
|
||||||
|
|
||||||
|
this.root.render(
|
||||||
|
<QuizDialog
|
||||||
|
quizId={quizId}
|
||||||
|
onClose={() => this.destroy()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.root) this.root.unmount();
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
73
src/widgets/side/QuizSideButton.tsx
Normal file
73
src/widgets/side/QuizSideButton.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { QuizAnswerer } from "@/index";
|
||||||
|
import lightTheme from "@/utils/themes/light";
|
||||||
|
import { Box, Button, Grow, ThemeProvider } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
|
||||||
|
const PADDING = 10;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
quizId: string;
|
||||||
|
position: "left" | "right";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuizSideButton({ quizId, position }: Props) {
|
||||||
|
const [isQuizShown, setIsQuizShown] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<ThemeProvider theme={lightTheme}>
|
||||||
|
{isQuizShown ? (
|
||||||
|
<Grow in={true}>
|
||||||
|
<Box
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
position: "fixed",
|
||||||
|
height: `calc(min(calc(100% - ${PADDING * 2}px), 800px))`,
|
||||||
|
width: `calc(min(calc(100% - ${PADDING * 2}px), 600px))`,
|
||||||
|
},
|
||||||
|
position === "left" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
left: PADDING,
|
||||||
|
},
|
||||||
|
position === "right" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
right: PADDING,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<QuizAnswerer
|
||||||
|
quizId={quizId}
|
||||||
|
changeFaviconAndTitle={false}
|
||||||
|
disableGlobalCss
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Grow>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className="pena-quiz-widget-button"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setIsQuizShown(true)}
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
position: "fixed",
|
||||||
|
height: "70px",
|
||||||
|
width: `calc(min(calc(100% - ${PADDING * 2}px), 600px))`,
|
||||||
|
},
|
||||||
|
position === "left" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
left: PADDING,
|
||||||
|
},
|
||||||
|
position === "right" && {
|
||||||
|
bottom: PADDING,
|
||||||
|
right: PADDING,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Пройти квиз
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ThemeProvider>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
28
src/widgets/side/SideWidget.tsx
Normal file
28
src/widgets/side/SideWidget.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Root, createRoot } from "react-dom/client";
|
||||||
|
import QuizSideButton from "./QuizSideButton";
|
||||||
|
import { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export class SideWidget {
|
||||||
|
root: Root | undefined;
|
||||||
|
element = document.createElement("div");
|
||||||
|
|
||||||
|
constructor({ quizId, position }: ComponentPropsWithoutRef<typeof QuizSideButton>) {
|
||||||
|
this.element.style.setProperty("display", "none");
|
||||||
|
document.body.appendChild(this.element);
|
||||||
|
|
||||||
|
this.root = createRoot(this.element);
|
||||||
|
|
||||||
|
this.root.render(
|
||||||
|
<QuizSideButton
|
||||||
|
quizId={quizId}
|
||||||
|
position={position}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.root) this.root.unmount();
|
||||||
|
this.element.remove();
|
||||||
|
}
|
||||||
|
}
|
120
widget-test.html
Normal file
120
widget-test.html
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Quiz</title>
|
||||||
|
<style>
|
||||||
|
#widget-container {
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<!-- <div id="widget-container"></div> -->
|
||||||
|
<div id="button-container"></div>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||||
|
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qu</p>
|
||||||
|
<!-- <script type="module">
|
||||||
|
import { ContainerWidget } from "./widget/widget.js";
|
||||||
|
|
||||||
|
new ContainerWidget({
|
||||||
|
selector: "widget-container",
|
||||||
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
});
|
||||||
|
</script> -->
|
||||||
|
<!-- <script type="module">
|
||||||
|
import { PopupWidget } from "./widget/widget.js";
|
||||||
|
|
||||||
|
new PopupWidget({
|
||||||
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
});
|
||||||
|
</script> -->
|
||||||
|
<!-- <script type="module">
|
||||||
|
import { ButtonWidget } from "./widget/widget.js";
|
||||||
|
|
||||||
|
new ButtonWidget({
|
||||||
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
fixedSide: "right",
|
||||||
|
});
|
||||||
|
</script> -->
|
||||||
|
<!-- <script type="module">
|
||||||
|
import { BannerWidget } from "./widget/widget.js";
|
||||||
|
|
||||||
|
new BannerWidget({
|
||||||
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
position: "bottomright",
|
||||||
|
});
|
||||||
|
</script> -->
|
||||||
|
<script type="module">
|
||||||
|
import { SideWidget } from "./widget/widget.js";
|
||||||
|
|
||||||
|
new SideWidget({
|
||||||
|
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
|
||||||
|
position: "right",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user