add widget selector polling

split button widget class into fixed and normal
This commit is contained in:
nflnkr 2024-04-28 16:25:59 +03:00
parent b349e9d023
commit d1c70c264d
5 changed files with 128 additions and 39 deletions

@ -1,31 +1,70 @@
import { Root, createRoot } from "react-dom/client"; import { Root, createRoot } from "react-dom/client";
import OpenQuizButton from "./OpenQuizButton"; import OpenQuizButton from "./OpenQuizButton";
import { ComponentPropsWithoutRef } from "react"; import { createPortal } from "react-dom";
import { pollForSelector } from "../pollForSelector";
export class ButtonWidget { export class ButtonWidget {
root: Root | undefined; root: Root | undefined;
element = document.createElement("div"); element = document.createElement("div");
constructor({ quizId, selector, fixedSide }: ComponentPropsWithoutRef<typeof OpenQuizButton>) { constructor({ quizId, selector, selectorPollingTimeLimit = 60 }: {
if (!fixedSide && !selector) throw new Error("ButtonWidget: Either selector or fixedSide params must be provided"); quizId: string;
selector: string;
/**
* In seconds, null - polling disabled
*/
selectorPollingTimeLimit?: number | null;
}) {
const element = document.querySelector(selector);
if (element) {
this.root = createRoot(element);
this.root.render(<OpenQuizButton quizId={quizId} />);
this.element.style.setProperty("display", "none"); return;
document.body.appendChild(this.element); }
this.root = createRoot(this.element); if (!selectorPollingTimeLimit) {
console.error(`Не удалось найти элемент ${selector} для вставки виджета`);
return;
}
this.root.render( pollForSelector(selector, selectorPollingTimeLimit, (element) => {
<OpenQuizButton this.root = createRoot(element);
selector={selector} this.root.render(<OpenQuizButton quizId={quizId} />);
fixedSide={fixedSide} });
quizId={quizId}
/>
);
} }
destroy() { destroy() {
if (this.root) this.root.unmount(); if (this.root) this.root.unmount();
this.element.remove(); this.element.remove();
} }
} }
export class ButtonWidgetFixed {
root: Root | undefined;
element = document.createElement("div");
constructor({ quizId, side }: {
quizId: string;
side: "left" | "right";
}) {
this.element.style.setProperty("display", "none");
document.body.appendChild(this.element);
this.root = createRoot(this.element);
this.root.render(createPortal(
<OpenQuizButton
fixedSide={side}
quizId={quizId}
/>,
document.body
));
}
destroy() {
if (this.root) this.root.unmount();
this.element.remove();
}
}

@ -1,22 +1,18 @@
import lightTheme from "@/utils/themes/light"; import lightTheme from "@/utils/themes/light";
import { Button, ThemeProvider } from "@mui/material"; import { Button, ThemeProvider } from "@mui/material";
import { useState } from "react"; import { useState } from "react";
import { createPortal } from "react-dom";
import QuizDialog from "../QuizDialog"; import QuizDialog from "../QuizDialog";
interface Props { interface Props {
selector?: string;
fixedSide?: "left" | "right"; fixedSide?: "left" | "right";
quizId: string; quizId: string;
} }
export default function OpenQuizButton({ selector, quizId, fixedSide }: Props) { export default function OpenQuizButton({ quizId, fixedSide }: Props) {
const [isQuizDialogOpen, setIsQuizDialogOpen] = useState<boolean>(false); const [isQuizDialogOpen, setIsQuizDialogOpen] = useState<boolean>(false);
const portalContainer = !fixedSide && selector ? document.querySelector(selector)! : document.body; return (
return createPortal(
<ThemeProvider theme={lightTheme}> <ThemeProvider theme={lightTheme}>
<Button <Button
className="pena-quiz-widget-button" className="pena-quiz-widget-button"
@ -24,7 +20,7 @@ export default function OpenQuizButton({ selector, quizId, fixedSide }: Props) {
variant="contained" variant="contained"
sx={[ sx={[
{ {
// generic styles // normal styles
}, },
Boolean(fixedSide) && { Boolean(fixedSide) && {
position: "fixed", position: "fixed",
@ -49,7 +45,6 @@ export default function OpenQuizButton({ selector, quizId, fixedSide }: Props) {
quizId={quizId} quizId={quizId}
onClose={() => setIsQuizDialogOpen(false)} onClose={() => setIsQuizDialogOpen(false)}
/> />
</ThemeProvider>, </ThemeProvider>
portalContainer
); );
} }

@ -1,26 +1,48 @@
import QuizAnswerer from "@/components/QuizAnswerer"; import QuizAnswerer from "@/components/QuizAnswerer";
import { Root, createRoot } from "react-dom/client"; import { Root, createRoot } from "react-dom/client";
import { pollForSelector } from "../pollForSelector";
export class ContainerWidget { export class ContainerWidget {
root: Root | undefined; root: Root | undefined;
constructor({ selector, quizId }: { constructor({ selector, quizId, selectorPollingTimeLimit = 60 }: {
quizId: string; quizId: string;
selector: string; selector: string;
/**
* In seconds, null - polling disabled
*/
selectorPollingTimeLimit?: number | null;
}) { }) {
const element = document.querySelector(selector); const element = document.querySelector(selector);
if (!element) throw new Error("Element for widget doesn't exist"); if (element) {
this.root = createRoot(element);
this.root.render(
<QuizAnswerer
quizId={quizId}
changeFaviconAndTitle={false}
disableGlobalCss
/>
);
this.root = createRoot(element); return;
}
this.root.render( if (!selectorPollingTimeLimit) {
<QuizAnswerer console.error(`Не удалось найти элемент ${selector} для вставки виджета`);
quizId={quizId} return;
changeFaviconAndTitle={false} }
disableGlobalCss
/> pollForSelector(selector, selectorPollingTimeLimit, (element) => {
); this.root = createRoot(element);
this.root.render(
<QuizAnswerer
quizId={quizId}
changeFaviconAndTitle={false}
disableGlobalCss
/>
);
});
} }
destroy() { destroy() {

@ -0,0 +1,25 @@
const SELECTOR_POLLING_INTERVAL = 5000;
export function pollForSelector(
selector: string,
selectorPollingTimeLimit: number,
onSuccess: (element: Element) => void,
) {
const deadline = Date.now() + selectorPollingTimeLimit * 1000;
const interval = setInterval(() => {
const element = document.querySelector(selector);
if (Date.now() > deadline) {
clearInterval(interval);
console.error(`Не удалось найти элемент ${selector} для вставки виджета`);
return;
}
if (!element) {
return;
}
clearInterval(interval);
onSuccess(element);
}, SELECTOR_POLLING_INTERVAL);
}

@ -8,8 +8,8 @@
<title>Quiz</title> <title>Quiz</title>
<style> <style>
#widget-container { #widget-container {
width: 400px; width: 100%;
height: 300px; height: 500px;
} }
p { p {
font-size: x-large; font-size: x-large;
@ -80,7 +80,7 @@
import { ContainerWidget } from "./widget/widget.js"; import { ContainerWidget } from "./widget/widget.js";
new ContainerWidget({ new ContainerWidget({
selector: "widget-container", selector: "#widget-container",
quizId: "3c49550d-8c77-4788-bc2d-42586a261514", quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
}); });
</script> --> </script> -->
@ -96,9 +96,17 @@
new ButtonWidget({ new ButtonWidget({
quizId: "3c49550d-8c77-4788-bc2d-42586a261514", quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
fixedSide: "right", selector: "#button-container",
}); });
</script> --> </script> -->
<script type="module">
import { ButtonWidgetFixed } from "./widget/widget.js";
new ButtonWidgetFixed({
quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
side: "left",
});
</script>
<!-- <script type="module"> <!-- <script type="module">
import { BannerWidget } from "./widget/widget.js"; import { BannerWidget } from "./widget/widget.js";
@ -107,14 +115,14 @@
position: "bottomright", position: "bottomright",
}); });
</script> --> </script> -->
<script type="module"> <!-- <script type="module">
import { SideWidget } from "./widget/widget.js"; import { SideWidget } from "./widget/widget.js";
new SideWidget({ new SideWidget({
quizId: "3c49550d-8c77-4788-bc2d-42586a261514", quizId: "3c49550d-8c77-4788-bc2d-42586a261514",
position: "right", position: "right",
}); });
</script> </script> -->
</body> </body>
</html> </html>