I am doing a project that is somewhat clone of a powerpoint or canva, i am trying it go fullscreen using the js api but when ever the slide content changes it exits the fullscreen automatically.
import React, { useState, useEffect, useRef, useCallback } from "react";
import "./form-template.css";
import { useRecoilState } from "recoil";
import { slides } from "../../recoil/atom/slideDataAtom";
import "react-quill/dist/quill.snow.css";
import Toolbar from "../editor/Toolbar";
import ResizeableContent from "../resizeable_content/ResizeableContent";
import ImageUpload from "../editor/ImageUpload";
import ContextMenu from "../context-menu/ContextMenu";
const Multipage = ({
activeSlide,
aiResponse,
aiReset,
copySlide,
removeSlide,
slideShow,
}) => {
const [slide, setSlide] = useRecoilState(slides);
const [clickedIndex, setClickedIndex] = useState(null);
const editorRefs = useRef({});
const [showContextMenu, setShowContextMenu] = useState(false);
const [contextPosition, setContextPosition] = useState({ x: 0, y: 0 });
const [contextMenuItems, setContextMenuItems] = useState([]);
const [elementContextMenu, setElementContextMenu] = useState(false);
const slideContainerRef = useRef(null);
const newTextboxData = {
type: "TEXT",
size: { width: "50%", height: "11%" },
position: { x: 50, y: 100 },
content: "",
};
const manipulateBackgroudImage = (data) => {
console.log(data);
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
return {
...slide,
backgroundImage: data,
};
}
return slide;
});
return updatedSlide;
});
};
const manipulateSlideContentData = (contents, index) => {
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = slide.data.map((item, j) => {
if (j === index) {
return {
...item,
content: contents,
};
}
return item;
});
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
};
useEffect(() => {
const removeContextMenu = () => {
setContextMenuItems([]);
setShowContextMenu(false);
};
window.addEventListener("click", removeContextMenu);
return () => {
window.removeEventListener("click", removeContextMenu);
};
});
useEffect(() => {
if (aiResponse !== "") {
manipulateSlideContentData(aiResponse, clickedIndex);
}
aiReset("");
}, [aiResponse]);
const deleteElement = useCallback(() => {
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = slide.data.filter(
(item, j) => j !== clickedIndex
);
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
setElementContextMenu(false);
}, [setSlide, activeSlide, clickedIndex]);
const handleDeleteElement = useCallback(
(event) => {
if (event.key === "Delete") {
deleteElement();
}
},
[deleteElement]
);
useEffect(() => {
document.addEventListener("keydown", handleDeleteElement);
return () => {
document.removeEventListener("keydown", handleDeleteElement);
};
}, [handleDeleteElement]);
const handleAddTextbox = useCallback(() => {
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = [...slide.data, newTextboxData];
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
}, [setSlide, activeSlide, newTextboxData]);
const handleResizeStop = useCallback(
(e, direction, ref, delta, position, type) => {
const { width, height } = ref.style;
const { x, y } = position;
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = slide.data.map((item, j) => {
if (j === type) {
return {
...item,
size: { width, height },
position: { x, y },
};
}
return item;
});
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
},
[setSlide, activeSlide]
);
const handleDragStop = useCallback(
(e, d, type) => {
const { x, y } = d;
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = slide.data.map((item, j) => {
if (j === type) {
return {
...item,
position: { x, y },
};
}
return item;
});
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
},
[setSlide, activeSlide]
);
const slideContentChangeHandler = useCallback(
(e, index) => {
const content = e;
manipulateSlideContentData(content, index);
},
[setSlide, activeSlide]
);
const handleFocus = useCallback((editorId) => {
console.log("editor id: ", editorId);
setClickedIndex(editorId);
});
const handleAction = useCallback(
(action, param) => {
const editor = editorRefs.current[clickedIndex].getEditor();
editor.focus();
switch (action) {
case "bold":
editor.format("bold", !editor.getFormat().bold);
break;
case "italic":
editor.format("italic", !editor.getFormat().italic);
break;
case "underline":
editor.format("underline", !editor.getFormat().underline);
break;
case "align":
editor.format("align", param);
break;
case "font":
editor.format("font", param);
break;
case "size":
editor.format("size", param);
break;
}
},
[clickedIndex]
);
const addImage = (imageUrl) => {
console.log(imageUrl);
const newImageBoxData = {
type: "IMAGE",
size: { width: "50%", height: "50%" },
position: { x: 50, y: 100 },
content: imageUrl,
};
setSlide((prevData) => {
const updatedSlide = prevData.map((slide, i) => {
if (i === activeSlide) {
const updatedData = [...slide.data, newImageBoxData];
return {
...slide,
data: updatedData,
};
}
return slide;
});
return updatedSlide;
});
};
const handleGlobalContextMenu = (event) => {
if (
slide[activeSlide].data.length > 0 ||
slide[activeSlide]?.backgroundImage
) {
event.preventDefault();
const rect = event.target.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
if (
slide[activeSlide]?.backgroundImage &&
slide[activeSlide]?.data[clickedIndex]?.type !== "IMAGE"
) {
setContextMenuItems([
{
lable: "Remove background image",
onClick: removeBackgroundImage,
},
]);
} else {
setContextMenuItems([
{
lable: "Set as background image",
onClick: setBackgroundImage,
},
]);
}
setContextPosition({
x,
y,
});
setShowContextMenu(true);
console.log("context menu");
}
};
useEffect(() => {
document.addEventListener("contextmenu", handleGlobalContextMenu);
return () => {
document.removeEventListener("contextmenu", handleGlobalContextMenu);
};
}, [handleGlobalContextMenu]);
const setBackgroundImage = () => {
setShowContextMenu(false);
const src =
slide[activeSlide]?.data[clickedIndex]?.type === "IMAGE"
? slide[activeSlide].data[clickedIndex].content
: "";
manipulateBackgroudImage(src);
deleteElement();
setElementContextMenu(false);
};
const removeBackgroundImage = () => {
setShowContextMenu(false);
manipulateBackgroudImage("");
};
useEffect(() => {
const enterFullscreen = () => {
if (slideContainerRef.current) {
if (slideContainerRef.current.requestFullscreen) {
slideContainerRef.current.requestFullscreen();
} else if (slideContainerRef.current.mozRequestFullScreen) {
// Firefox
slideContainerRef.current.mozRequestFullScreen();
} else if (slideContainerRef.current.webkitRequestFullscreen) {
// Chrome, Safari and Opera
slideContainerRef.current.webkitRequestFullscreen();
} else if (slideContainerRef.current.msRequestFullscreen) {
// IE/Edge
slideContainerRef.current.msRequestFullscreen();
}
}
};
const exitFullscreen = () => {
if (document.mozCancelFullScreen) {
// Firefox
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
// Chrome, Safari and Opera
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
// IE/Edge
document.msExitFullscreen();
}
};
if (slideShow) {
enterFullscreen();
} else {
exitFullscreen();
}
}, [slideShow, activeSlide]); // Add activeSlide as a dependency
return (
<div
className="row w-100 pb-3 mx-auto"
onClick={() => {
setShowContextMenu(false);
}}
role="outter-container"
>
{slide.length > 0 && (
<div
className={`box col-12 d-flex flex-column justify-content-center align-content-center ${
slideShow ? "slideshow-mode" : ""
}`}
key={activeSlide}
>
{!slideShow && (
<Toolbar onAction={handleAction} addTextbox={handleAddTextbox} />
)}
<ImageUpload onUpload={addImage} />
<div
className={`form-template p-5 w-100 ${
slideShow ? "fullscreen" : ""
}`}
style={{
backgroundImage: `url(${slide[activeSlide]?.backgroundImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundColor: slide[activeSlide]?.backgroundColor,
}}
id="slide"
ref={slideContainerRef}
>
<div className="row w-100 h-100 position-relative" role="slide">
{showContextMenu && (
<ContextMenu
position={contextPosition}
onClose={() => setShowContextMenu(false)}
menuItems={contextMenuItems}
setBackgroundImage={setBackgroundImage}
/>
)}
{slide[activeSlide].data.length !== 0 ? (
slide[activeSlide].data.map((item, i) => {
return (
<ResizeableContent
key={i}
editorId={i}
type={item.type}
initialSize={item.size}
initialPosition={item.position}
onResizeStop={(e, direction, ref, delta, position) => {
handleResizeStop(e, direction, ref, delta, position, i);
}}
onDragStop={(e, d) => {
handleDragStop(e, d, i);
}}
className="col-12 rnd-container position-absolute"
onFocus={handleFocus}
value={item.content}
onChange={(e) => slideContentChangeHandler(e, i)}
ref={(el) => (editorRefs.current[i] = el)}
slideShow={slideShow}
/>
);
})
) : (
<></>
)}
</div>
</div>
{slide.length > 1 && (
<div className="bottom-props">
<span className="delete-icon-svg">
<img
onClick={removeSlide}
className="img-responsive cursor-pointer"
src="./../img/delete.svg"
alt=""
/>
</span>
<span className="copy-icon-svg pt-2">
<img
onClick={copySlide}
className="img-responsive cursor-pointer"
src="./../img/copy.svg"
alt=""
/>
</span>
</div>
)}
</div>
)}
</div>
);
};
export default Multipage;
**Problem Description
**
- Issue: When changing the slide content (e.g., adding or modifying text, images, or other elements), the application exits fullscreen mode automatically.
- Behavior Observed: The fullscreen mode is triggered correctly, but any state change that re-renders the slide causes it to exit fullscreen.