diff --git a/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx b/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx index d852d803..05a6b638 100644 --- a/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx @@ -2,7 +2,6 @@ import React from "react"; import { useDispatch } from "react-redux"; import { addNewSlide } from "@/store/slices/presentationGeneration"; import { Loader2 } from "lucide-react"; -// import { useGroupLayoutLoader } from '@/app/layout-preview/hooks/useGroupLayoutLoader'; import { useLayout, FullDataInfo } from "../context/LayoutContext"; import { v4 as uuidv4 } from "uuid"; import { Trash2 } from 'lucide-react'; diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts index 5194c150..7ab1d4cb 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts +++ b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts @@ -14,20 +14,7 @@ export const useSlideEdit = ( const [slideHtml, setSlideHtml] = useState(""); const slideContentRef = useRef(null); - // Load Tailwind CSS dynamically for slide content - useEffect(() => { - if (slide.processed && slide.html) { - const existingScript = document.querySelector( - 'script[src*="tailwindcss.com"]' - ); - if (!existingScript) { - const script = document.createElement("script"); - script.src = "https://cdn.tailwindcss.com"; - script.async = true; - document.head.appendChild(script); - } - } - }, [slide.processed, slide.html]); + // Set up canvas when entering edit mode useEffect(() => { diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx index 338ebd4d..6becf5ac 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useEffect } from "react"; import FontManager from "./components/FontManager"; import Header from "../dashboard/components/Header"; import { useLayout } from "../context/LayoutContext"; @@ -67,6 +67,18 @@ const CustomTemplatePage = () => { ) ); }; + useEffect(() => { + const existingScript = document.querySelector( + 'script[src*="tailwindcss.com"]' + ); + if (!existingScript) { + const script = document.createElement("script"); + script.src = "https://cdn.tailwindcss.com"; + script.async = true; + document.head.appendChild(script); + } + }, []); + // Loading state if (isRequiredKeyLoading) { diff --git a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx index 0f35df8b..922c9f87 100644 --- a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx @@ -9,17 +9,28 @@ import { AlertCircle } from "lucide-react"; import { useGroupLayouts } from "../hooks/useGroupLayouts"; import { setPresentationData } from "@/store/slices/presentationGeneration"; import { DashboardApi } from "../services/api/dashboard"; +import { useLayout } from "../context/LayoutContext"; +import { useFontLoader } from "../hooks/useFontLoader"; const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { const { renderSlideContent, loading } = useGroupLayouts(); const [contentLoading, setContentLoading] = useState(true); + const {getCustomTemplateFonts} = useLayout() const dispatch = useDispatch(); const { presentationData } = useSelector( (state: RootState) => state.presentationGeneration ); const [error, setError] = useState(false); + useEffect(() => { + if(!loading && presentationData?.slides && presentationData?.slides.length > 0){ + const presentation_id = presentationData?.slides[0].layout.split(":")[0].split("custom-")[1]; + const fonts = getCustomTemplateFonts(presentation_id); + + useFontLoader(fonts || []); + } + }, [presentationData,loading]); useEffect(() => { if (presentationData?.slides[0].layout.includes("custom")) { const existingScript = document.querySelector( @@ -51,6 +62,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { setContentLoading(false); } }; + // Regular view return (
diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/backup.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/backup.tsx deleted file mode 100644 index db76f920..00000000 --- a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/backup.tsx +++ /dev/null @@ -1,400 +0,0 @@ - "use client"; - - import React, { useEffect, useState, useRef } from "react"; - import { useParams, useRouter } from "next/navigation"; - // import { useGroupLayoutLoader } from '../hooks/useGroupLayoutLoader' - import LoadingStates from "../components/LoadingStates"; - import { Card } from "@/components/ui/card"; - import { Button } from "@/components/ui/button"; - import { ArrowLeft, Edit, Home, Trash2 } from "lucide-react"; - import { useLayout } from "@/app/(presentation-generator)/context/LayoutContext"; - - import html2canvas from "html2canvas"; -import { EditControls } from "../../custom-template/components/EachSlide/EditControls"; -import { useDrawingCanvas } from "../../custom-template/hooks/useDrawingCanvas"; - const GroupLayoutPreview = () => { - const params = useParams(); - const router = useRouter(); - const slug = params.slug as string; - // const isCustom = slug.includes("custom-"); - const isCustom = true; - // Custom hooks - const { - canvasRef, - slideDisplayRef, - strokeWidth, - strokeColor, - eraserMode, - isDrawing, - canvasDimensions, - setCanvasDimensions, - didYourDraw, - handleMouseDown, - handleMouseMove, - handleMouseUp, - handleTouchStart, - handleTouchMove, - handleTouchEnd, - handleClearCanvas, - handleEraserModeChange, - handleStrokeColorChange, - handleStrokeWidthChange, - } = useDrawingCanvas(); - - const slideContentRef = useRef(null); - - const { getFullDataByGroup, loading,refetch } = useLayout(); - const layoutGroup = getFullDataByGroup(slug); - const [isEditMode, setIsEditMode] = useState(false); - const [selectedIndex, setSelectedIndex] = useState(null); - const [prompt, setPrompt] = useState(""); - const [isUpdating, setIsUpdating] = useState(false); - - useEffect(() => { - const existingScript = document.querySelector( - 'script[src*="tailwindcss.com"]' - ); - if (!existingScript) { - const script = document.createElement("script"); - script.src = "https://cdn.tailwindcss.com"; - script.async = true; - document.head.appendChild(script); - } - }, [slug]); - - // Size canvas to content when entering edit mode - useEffect(() => { - if (isEditMode && slideContentRef.current) { - const rect = slideContentRef.current.getBoundingClientRect(); - setCanvasDimensions({ - width: Math.max(rect.width, 800), - height: Math.max(rect.height, 600), - }); - } - }, [isEditMode, setCanvasDimensions]); - - // Handle loading state - if (loading) { - return ; - } - - // Handle empty state - if (!layoutGroup || layoutGroup.length === 0) { - return ; - } - const deleteLayouts = async () => { - const presentationId = slug.replace('custom-',''); - refetch(); - router.back(); - const response = await fetch(`/api/v1/ppt/template-management/delete-templates/${presentationId}`, { - method: "DELETE", - }); - if (response.ok) { - router.push("/template-preview"); - } - } - - const handleSave = async ( - slideDisplayRef: React.RefObject, - didYourDraw: boolean - ) => { - if ( - !slideContentRef.current || - !slideDisplayRef.current - ) - return; - - if (!prompt.trim()) { - alert("Please enter a prompt before saving."); - return; - } - - setIsUpdating(true); - - try { - // Take screenshot of the slide display area (slide only) - const slideOnly = await html2canvas(slideDisplayRef.current, { - backgroundColor: "#ffffff", - scale: 1, - logging: false, - useCORS: true, - ignoreElements: (element) => { - return element.tagName === "CANVAS"; - }, - }); - let slideWithCanvas; - if (didYourDraw) { - // Take screenshot of the entire slide display area including canvas - slideWithCanvas = await html2canvas(slideDisplayRef.current, { - backgroundColor: "#ffffff", - scale: 1, - logging: false, - useCORS: true, - }); - } - - - - const currentUiImageBlob = dataURLToBlob( - slideOnly.toDataURL("image/png") - ); - let sketchImageBlob; - if (didYourDraw && slideWithCanvas) { - sketchImageBlob = dataURLToBlob(slideWithCanvas.toDataURL("image/png")); - } - - // download the images - - const currentUiImageUrl = URL.createObjectURL(currentUiImageBlob); - if (currentUiImageUrl) { - const a = document.createElement("a"); - a.href = currentUiImageUrl; - a.download = `slide-current.png`; - a.click(); - } - if (sketchImageBlob) { - const sketchImageUrl = URL.createObjectURL(sketchImageBlob); - if (sketchImageUrl) { - const b = document.createElement("a"); - b.href = sketchImageUrl; - b.download = `slide-sketch.png`; - b.click(); - } - } - - - - - // const formData = new FormData(); - // formData.append( - // "current_ui_image", - // currentUiImageBlob, - // `slide--current.png` - // ); - // if (didYourDraw && slideWithCanvas && sketchImageBlob) { - // formData.append( - // "sketch_image", - // sketchImageBlob, - // `slide-sketch.png` - // ); - // } - // formData.append("html", ''); - // formData.append("prompt", prompt); - - // const response = await fetch("/api/v1/ppt/html-edit/", { - // method: "POST", - // body: formData, - // }); - - // if (!response.ok) { - // throw new Error(`API call failed: ${response.statusText}`); - // } - - // const data = await response.json(); - - - // Exit edit mode - setIsEditMode(false); - setPrompt(""); - } catch (error) { - console.error("Error updating slide:", error); - alert( - `Error updating slide: ${ - error instanceof Error ? error.message : "Unknown error" - }` - ); - } finally { - setIsUpdating(false); - } - }; - const dataURLToBlob = (dataURL: string): Blob => { - const parts = dataURL.split(","); - const contentType = parts[0].match(/:(.*?);/)?.[1] || "image/png"; - const raw = window.atob(parts[1]); - const rawLength = raw.length; - const uInt8Array = new Uint8Array(rawLength); - - for (let i = 0; i < rawLength; ++i) { - uInt8Array[i] = raw.charCodeAt(i); - } - - return new Blob([uInt8Array], { type: contentType }); - }; - - return ( -
- {/* Header */} -
-
- {/* Navigation */} -
- - - {isCustom && } -
- -
-

- {layoutGroup[0].groupName} Layouts -

-

- {layoutGroup.length} layout{layoutGroup.length !== 1 ? "s" : ""} •{" "} - {layoutGroup[0].groupName} -

-
- -
-
- - -
- {/* Edit Controls (no HTML editor) */} - {isCustom && ( - { - setIsUpdating(true); - setTimeout(() => { - setIsUpdating(false); - setIsEditMode(false); - setSelectedIndex(null); - }, 300); - }} - onCancel={() => { - setIsEditMode(false); - setSelectedIndex(null); - handleClearCanvas(); - }} - onStrokeWidthChange={handleStrokeWidthChange} - onStrokeColorChange={handleStrokeColorChange} - onEraserModeChange={handleEraserModeChange} - onClearCanvas={handleClearCanvas} - /> - )} -
- {layoutGroup.map((layout: any, index: number) => { - const { - component: LayoutComponent, - sampleData, - name, - fileName, - } = layout; - - const isSelected = isCustom && isEditMode && selectedIndex === index; - - return ( - - {/* Layout Header */} -
-
-
-

- {name} -

-
- - {fileName} - - - {layoutGroup[0].groupName} - -
-
-
- {isCustom && ( - - )} -
-
-
- - {/* Layout Content */} -
-
- - {isSelected && ( - e.preventDefault()} - /> - )} -
-
-
- ); - })} -
-
- - {/* Footer */} -
-
-
-

- {layoutGroup[0].groupName} • {layoutGroup.length} components -

-
-
-
-
- ); - }; - - export default GroupLayoutPreview; - diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx index 9c5d2f5d..11f9af4c 100644 --- a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx @@ -1,7 +1,6 @@ "use client"; import React, { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -// import { useGroupLayoutLoader } from '../hooks/useGroupLayoutLoader' import LoadingStates from "../components/LoadingStates"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -14,6 +13,7 @@ import "prismjs/components/prism-javascript"; import "prismjs/components/prism-markup"; import "prismjs/components/prism-jsx"; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet"; +import { useFontLoader } from "../../hooks/useFontLoader"; const GroupLayoutPreview = () => { const params = useParams(); @@ -35,22 +35,7 @@ const GroupLayoutPreview = () => { const [layoutsMap, setLayoutsMap] = useState>({}); const [templateMeta, setTemplateMeta] = useState<{ name?: string; description?: string } | null>(null); - const injectFonts = (fontUrls: string[]) => { - fontUrls.forEach((fontUrl) => { - if (!fontUrl) return; - const existingStyle = document.querySelector(`style[data-font-url="${fontUrl}"]`); - if (existingStyle) return; - const fileName = fontUrl.split("/").pop() || "CustomFont"; - const baseName = fileName.replace(/\.[a-zA-Z0-9]+$/, ""); - const fontFamily = baseName.replace(/[^A-Za-z0-9_-]/g, "_"); - const ext = (fileName.split(".").pop() || "ttf").toLowerCase(); - const format = ext === "otf" ? "opentype" : ext === "woff" ? "woff" : ext === "woff2" ? "woff2" : "truetype"; - const style = document.createElement("style"); - style.setAttribute("data-font-url", fontUrl); - style.textContent = `@font-face { font-family: '${fontFamily}'; src: url('${fontUrl}') format('${format}'); font-display: swap; }`; - document.head.appendChild(style); - }); - }; + useEffect(() => { const loadCustomLayouts = async () => { @@ -74,7 +59,7 @@ const GroupLayoutPreview = () => { setTemplateMeta({ name: data.template.name, description: data.template.description }); } if (Array.isArray(data?.fonts) && data.fonts.length) { - injectFonts(data.fonts); + useFontLoader(data.fonts); } } catch (e) { // noop @@ -102,7 +87,7 @@ const GroupLayoutPreview = () => { Object.values(layoutsMap).forEach((entry) => { (entry.fonts || []).forEach((f) => allFonts.push(f)); }); - if (allFonts.length) injectFonts(allFonts); + if (allFonts.length) useFontLoader(allFonts); }, [layoutsMap, isCustom]); // Handle loading state @@ -134,7 +119,7 @@ const GroupLayoutPreview = () => { setCurrentCode(entry.layout_code || ""); setCurrentFonts(entry.fonts); // Make sure fonts for this layout are loaded before editing - injectFonts(entry.fonts || []); + useFontLoader(entry.fonts || []); setEditorOpen(true); };