From 5f58828d021feec8bd8a3408ada4640c8f8b46ae Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Sat, 19 Jul 2025 22:11:18 +0545 Subject: [PATCH 1/7] feat(nextjs): New slide add featture added --- .../components/TiptapTextReplacer.tsx | 1 + .../components/slide_layouts/NewSlide.tsx | 199 ++++-------------- .../hooks/useGroupLayouts.tsx | 3 +- .../components/PresentationPage.tsx | 8 +- .../presentation/components/SlideContent.tsx | 53 ++--- .../presentation/hooks/usePresentationData.ts | 22 +- .../dashboard/components/PresentationCard.tsx | 13 +- .../nextjs/app/layout-preview/[slug]/page.tsx | 2 +- .../hooks/useGroupLayoutLoader.ts | 44 +++- .../layout-preview/hooks/useLayoutLoader.ts | 8 +- .../nextjs/app/layout-preview/types/index.ts | 1 + .../store/slices/presentationGeneration.ts | 16 ++ 12 files changed, 138 insertions(+), 232 deletions(-) diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx index 5482b702..0fd75f3a 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx @@ -107,6 +107,7 @@ const TiptapTextReplacer: React.FC = ({ const root = ReactDOM.createRoot(tiptapContainer); root.render( { if (dataPath && onContentChange) { diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx index c1ee87b2..4ff3c840 100644 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx @@ -1,152 +1,46 @@ import { Trash2 } from 'lucide-react'; import React from 'react' +import { useGroupLayoutLoader } from '@/app/layout-preview/hooks/useGroupLayoutLoader'; +import { useDispatch } from 'react-redux'; +import { addNewSlide } from '@/store/slices/presentationGeneration'; +import { Loader2 } from 'lucide-react'; interface NewSlideProps { - onSelectLayout: (type: number) => void; setShowNewSlideSelection: (show: boolean) => void; + group: string; + index: number; + presentationId: string; } - -const LayoutPreview = ({ type }: { type: string }) => { - switch (type) { - case 'type1': - return ( -
-
-
-
-
-
-

image

-
-
- ) - case 'type2': - return ( -
-
-
- {[1, 2, 3].map((i) => ( -
-
-
-
- ))} -
-
- ) - case 'type4': - return ( -
-
-
- {[1, 2, 3].map((i) => ( -
-
-

image

-
-
-
- ))} -
-
- ) - case 'type5': - return ( -
-
-
-
-

chart

-
-
-
-
- ) - case 'type6': - return ( -
-
-
-
-
-
- {[1, 2].map((i) => ( -
-
-
-
-
-
- ))} -
-
- ) - case 'type7': - return ( -
-
-
- {[1, 2, 3].map((i) => ( -
-
-

Icon

-
-
-
-
- ))} -
-
- ) - case 'type8': - return ( -
-
-
-
-
-
- {[1, 2].map((i) => ( -
-
-

Icon

-
-
-
-
-
- ))} -
-
- ) - case 'type9': - return ( -
-
-
-
-

chart

-
-
-
- {[1, 2, 3].map((i) => ( -
-
-
-
-
-
- ))} -
-
- ) - - default: - return null +const NewSlide = ({ setShowNewSlideSelection, group, index, presentationId }: NewSlideProps) => { + const dispatch = useDispatch(); + const handleNewSlide = (sampleData: any, id: string) => { + const newSlide = { + id: crypto.randomUUID(), + index: index, + content: sampleData, + layout_group: group, + layout: `${group}:${id}`, + presentation: presentationId + } + dispatch(addNewSlide({ slideData: newSlide, index })); + setShowNewSlideSelection(false); + } + const { layoutGroup, loading } = useGroupLayoutLoader(group) + + if (loading) { + return ( +
+
+

Select a Slide Layout

+ setShowNewSlideSelection(false)} className='text-gray-500 text-2xl cursor-pointer' /> +
+
+ +
+
+ ) } -} -const NewSlide = ({ onSelectLayout, setShowNewSlideSelection }: NewSlideProps) => { return (
@@ -155,22 +49,17 @@ const NewSlide = ({ onSelectLayout, setShowNewSlideSelection }: NewSlideProps) = setShowNewSlideSelection(false)} className='text-gray-500 text-2xl cursor-pointer' />
- {['type1', 'type2', 'type4', 'type5', 'type6', 'type7', 'type8', 'type9'].map((type) => ( -
onSelectLayout(parseInt(type.replace('type', '')))} - > -
-
-

Layout {type.replace('type', '')}

-
-
- + {layoutGroup && layoutGroup?.layouts.map((layout: any, index: number) => { + const { component: LayoutComponent, sampleData, layoutId } = layout + return ( +
handleNewSlide(sampleData, layoutId)} key={`${layoutGroup?.groupName}-${index}`} className=" relative cursor-pointer overflow-hidden aspect-video"> +
+
+
-
- ))} + ) + })}
) diff --git a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx index 29c5e29a..8208530d 100644 --- a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx +++ b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx @@ -35,7 +35,7 @@ export const useGroupLayouts = () => { // Render slide content with group validation, automatic Tiptap text editing, and editable images/icons const renderSlideContent = useMemo(() => { - return (slide: any, isEditMode: boolean = true) => { + return (slide: any, isEditMode: boolean) => { const Layout = getGroupLayout(slide.layout, slide.layout_group); if (!Layout) { return ( @@ -55,6 +55,7 @@ export const useGroupLayouts = () => { isEditMode={isEditMode} > = ({ presentation_id }) }); // Custom hooks - const { fetchUserSlides, handleDeleteSlide } = usePresentationData( + const { fetchUserSlides } = usePresentationData( presentation_id, setLoading, setError @@ -72,9 +72,7 @@ const PresentationPage: React.FC = ({ presentation_id }) fetchUserSlides ); - const onDeleteSlide = (index: number) => { - handleDeleteSlide(index, presentationData); - }; + const onSlideChange = (newSlide: number) => { handleSlideChange(newSlide, presentationData); @@ -172,7 +170,7 @@ const PresentationPage: React.FC = ({ presentation_id }) slide={slide} index={index} presentationId={presentation_id} - onDeleteSlide={onDeleteSlide} + /> ))} diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index 5c33adf5..9fdb7dec 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -13,23 +13,21 @@ import { PresentationGenerationApi } from "../../services/api/presentation-gener import ToolTip from "@/components/ToolTip"; import { RootState } from "@/store/store"; import { useDispatch, useSelector } from "react-redux"; -import { addSlide, updateSlide } from "@/store/slices/presentationGeneration"; +import { deletePresentationSlide, updateSlide } from "@/store/slices/presentationGeneration"; import NewSlide from "../../components/slide_layouts/NewSlide"; -import { getEmptySlideContent } from "../../utils/NewSlideContent"; import { useGroupLayouts } from "../../hooks/useGroupLayouts"; interface SlideContentProps { slide: any; index: number; presentationId: string; - onDeleteSlide: (index: number) => void; } const SlideContent = ({ slide, index, presentationId, - onDeleteSlide, + }: SlideContentProps) => { const dispatch = useDispatch(); const [isUpdating, setIsUpdating] = useState(false); @@ -82,29 +80,16 @@ const SlideContent = ({ setIsUpdating(false); } }; - - const handleNewSlide = (type: number, index: number) => { - const newSlide: Slide = getEmptySlideContent( - type, - index + 1, - presentationData?.id! - ); - - dispatch(addSlide({ slide: newSlide, index: index + 1 })); - setShowNewSlideSelection(false); - - // Scroll to the newly added slide after a short delay to ensure it's rendered - setTimeout(() => { - const newSlideElement = document.getElementById(`slide-${newSlide.id}`); - if (newSlideElement) { - newSlideElement.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - } - }, 100); + const onDeleteSlide = async () => { + try { + dispatch(deletePresentationSlide(slide.index)); + } catch (error) { + console.error("Error deleting slide:", error); + } }; + + // Scroll to the new slide when streaming and new slides are being generated useEffect(() => { if ( @@ -139,16 +124,16 @@ const SlideContent = ({ {isStreaming && ( )} -
+
{/* render slides */} {loading ?
: slideContent} - {/* {!showNewSlideSelection && ( + {!showNewSlideSelection && (
- {!isStreaming && ( + {!isStreaming && !loading && (
setShowNewSlideSelection(true)} className=" bg-white shadow-md w-[80px] py-2 border hover:border-[#5141e5] duration-300 flex items-center justify-center rounded-lg cursor-pointer mx-auto" @@ -159,16 +144,18 @@ const SlideContent = ({
)} - {showNewSlideSelection && ( + {showNewSlideSelection && !loading && ( handleNewSlide(type, slide.index)} + index={index} + group={slide.layout_group} setShowNewSlideSelection={setShowNewSlideSelection} + presentationId={presentationId} /> - )} */} - {!isStreaming && ( + )} + {!isStreaming && !loading && (
onDeleteSlide(slide.index)} + onClick={onDeleteSlide} className="absolute top-2 z-20 sm:top-4 right-2 sm:right-4 hidden md:block transition-transform" > diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts index 4c7681f9..0369c342 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts @@ -2,8 +2,7 @@ import { useCallback } from "react"; import { useDispatch } from "react-redux"; import { toast } from "@/hooks/use-toast"; import { DashboardApi } from "@/app/dashboard/api/dashboard"; -import { PresentationGenerationApi } from "../../services/api/presentation-generation"; -import { setPresentationData, deletePresentationSlide } from "@/store/slices/presentationGeneration"; +import { setPresentationData } from "@/store/slices/presentationGeneration"; export const usePresentationData = ( presentationId: string, @@ -32,25 +31,10 @@ export const usePresentationData = ( } }, [presentationId, dispatch, setLoading, setError]); - const handleDeleteSlide = useCallback(async (index: number, presentationData: any) => { - dispatch(deletePresentationSlide(index)); - try { - await PresentationGenerationApi.deleteSlide( - presentationId, - presentationData?.slides[index].id! - ); - } catch (error) { - console.error("Error deleting slide:", error); - toast({ - title: "Error", - description: "Failed to delete slide", - variant: "destructive", - }); - } - }, [presentationId, dispatch]); + return { fetchUserSlides, - handleDeleteSlide, + }; }; \ No newline at end of file diff --git a/servers/nextjs/app/dashboard/components/PresentationCard.tsx b/servers/nextjs/app/dashboard/components/PresentationCard.tsx index b1497c11..720bce3f 100644 --- a/servers/nextjs/app/dashboard/components/PresentationCard.tsx +++ b/servers/nextjs/app/dashboard/components/PresentationCard.tsx @@ -63,11 +63,6 @@ export const PresentationCard = ({ }); } }; - - - - - return ( - e.stopPropagation()}> - - -
-
- ); -}; - -export default CustomThemeSettings; diff --git a/servers/nextjs/app/(presentation-generator)/components/EditableText.tsx b/servers/nextjs/app/(presentation-generator)/components/EditableText.tsx deleted file mode 100644 index b2702807..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/EditableText.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import { useSelector } from "react-redux"; -import TipTapEditor from "./Tiptap"; -import { RootState } from "@/store/store"; -import Typewriter from "./TypeWriter"; - -interface EditableTextProps { - slideIndex: number; - bodyIdx?: number; - elementId: string; - type: - | "title" - | "heading" - | "description-body" - | "description" - | "heading-description" - | "info-heading" - | "info-description"; - content: string; - isAlingCenter?: boolean; -} - -const EditableText = ({ - slideIndex, - elementId, - type, - content, - bodyIdx = 0, - isAlingCenter = false, -}: EditableTextProps) => { - const { isStreaming } = useSelector( - (state: RootState) => state.presentationGeneration - ); - - const elementRef = useRef(null); - - // Add useEffect to initialize content - useEffect(() => { - if (elementRef.current) { - const displayContent = content || getPlaceholder(); - elementRef.current.textContent = displayContent; - - // Add placeholder styling if needed - if (!content) { - elementRef.current.classList.add("text-gray-400"); - } - } - }, [content]); - - const getPlaceholder = () => { - switch (type) { - case "title": - return "Enter title"; - case "heading": - return "Enter heading"; - case "description-body": - return "Enter description"; - case "description": - return "Enter description"; - case "heading-description": - return "Enter description"; - case "info-heading": - return "Enter heading"; - case "info-description": - return "Enter description"; - default: - return "Enter text"; - } - }; - - const getTextStyle = () => { - const baseStyle = "outline-none transition-all duration-200"; - switch (type) { - case "title": - return `${baseStyle} text-[40px] slide-title leading-[48px] font-bold`; - case "heading": - return `${baseStyle} text-[24px] slide-heading leading-[32px] font-bold`; - case "description": - case "description-body": - case "heading-description": - return `${baseStyle} text-[20px] slide-description leading-[24px] font-normal`; - default: - return `${baseStyle} text-[20px] slide-description leading-[24px] font-normal`; - } - }; - - return ( - <> - {isStreaming ? ( -
- -
- ) : ( - - )} - - ); -}; - -export default React.memo(EditableText); diff --git a/servers/nextjs/app/(presentation-generator)/components/ElementMenu.tsx b/servers/nextjs/app/(presentation-generator)/components/ElementMenu.tsx deleted file mode 100644 index 7c7697ec..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/ElementMenu.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { MoreHorizontal } from "lucide-react"; -import React from "react"; - -interface ElementMenuProps { - index: number; - handleDeleteItem: (index: number) => void; -} - -const ElementMenu = ({ index, handleDeleteItem }: ElementMenuProps) => { - return ( - - - - - - handleDeleteItem(index)} - className="px-3 py-2 cursor-pointer" - > - Delete Item {index + 1} - - - - ); -}; - -// Prevent unnecessary re-renders -export default React.memo(ElementMenu, (prevProps, nextProps) => { - return ( - prevProps.index === nextProps.index && prevProps.index === nextProps.index - ); -}); diff --git a/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx b/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx similarity index 95% rename from servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx rename to servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx index 9d213c1a..5f0fbe2e 100644 --- a/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { RootState } from "@/store/store"; import { useSelector } from "react-redux"; -const UserAccount = () => { +const HeaderNav = () => { const canChangeKeys = useSelector((state: RootState) => state.userConfig.can_change_keys); @@ -39,4 +39,4 @@ const UserAccount = () => { ); }; -export default UserAccount; +export default HeaderNav; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx b/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/components/slide_layouts/NewSlide.tsx rename to servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx diff --git a/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx b/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx index 4ceec318..e466711a 100644 --- a/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx @@ -15,7 +15,7 @@ import { useGroupLayouts } from "../hooks/useGroupLayouts"; interface PresentationModeProps { slides: Slide[]; currentSlide: number; - currentTheme: string; + isFullscreen: boolean; onFullscreenToggle: () => void; onExit: () => void; @@ -26,7 +26,7 @@ const PresentationMode: React.FC = ({ slides, currentSlide, - currentTheme, + isFullscreen, onFullscreenToggle, onExit, @@ -190,10 +190,9 @@ const PresentationMode: React.FC = ({
{slides[currentSlide] && - renderSlideContent(slides[currentSlide])} + renderSlideContent(slides[currentSlide], false)}
diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/SlideFooter.tsx b/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/components/slide_layouts/SlideFooter.tsx rename to servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx diff --git a/servers/nextjs/app/(presentation-generator)/components/Tiptap.tsx b/servers/nextjs/app/(presentation-generator)/components/Tiptap.tsx deleted file mode 100644 index 33e11dfb..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/Tiptap.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import { useEditor, EditorContent, BubbleMenu } from "@tiptap/react"; -import StarterKit from "@tiptap/starter-kit"; -import { Markdown } from "tiptap-markdown"; -import Underline from "@tiptap/extension-underline"; -import { - Bold, - Italic, - Underline as UnderlinedIcon, - Strikethrough, - Code, -} from "lucide-react"; - - - -const TipTapEditor = ({ - content, -}: { - content: string; - -}) => { - - const editor = useEditor({ - extensions: [StarterKit, Markdown, Underline], - content: content, - editorProps: { - attributes: { - class: "outline-none transition-all duration-200", - }, - }, - immediatelyRender: false, - }); - - return ( -
- -
- - - - - -
-
- - { - const markdown = editor?.storage.markdown.getMarkdown(); - console.log("🔍 markdown", markdown); - }} - - editor={editor} - /> -
- ); -}; - -export default TipTapEditor; diff --git a/servers/nextjs/app/(presentation-generator)/components/TypeWriter.tsx b/servers/nextjs/app/(presentation-generator)/components/TypeWriter.tsx deleted file mode 100644 index a9b8acd9..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/TypeWriter.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useTypewriter } from "@/hooks/useTypeWriter"; - -const Typewriter = ({ text, speed }: { text: string; speed: number }) => { - const { displayText, isCursorVisible } = useTypewriter(text, speed, true); - - return ( -

- {displayText} - {isCursorVisible && |} -

- ); -}; - -export default Typewriter; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/AllChart.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/AllChart.tsx deleted file mode 100644 index 687b6306..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/AllChart.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useState, useEffect } from "react"; -import ChartEditor from "../ChartEditor"; -import { StoreChartData } from "../../utils/chartDataTransforms"; -import { useDispatch, useSelector } from "react-redux"; -import { - ChartSettings, - updateSlideChart, - updateSlideChartSettings, -} from "@/store/slices/presentationGeneration"; -import { renderChart } from "../slide_config"; -import { RootState } from "@/store/store"; - -interface AllChartProps { - chartData: StoreChartData; - slideIndex: number; -} - -const AllChart = ({ - chartData: initialChartData, - slideIndex, -}: AllChartProps) => { - const dispatch = useDispatch(); - const [localChartData, setLocalChartData] = - useState(initialChartData); - const [isEditorOpen, setIsEditorOpen] = useState(false); - const { currentColors } = useSelector((state: RootState) => state.theme); - - // Use chart settings from the Redux store - const chartSettings = useSelector((state: RootState) => { - const slide = - state.presentationGeneration?.presentationData?.slides[slideIndex]; - - - const style = slide?.content.graph.style || {}; - return Object.keys( - style === null || style === undefined ? {} : (style as ChartSettings) - ).length > 0 - ? (style as ChartSettings) - : { - showLegend: false, - showGrid: false, - showAxisLabel: true, - showDataLabel: true, - dataLabel: { - dataLabelPosition: - slide?.content.graph.type === "pie" - ? ("Outside" as const) - : ("Inside" as const), - dataLabelAlignment: "Center" as const, - }, - }; - }); - - useEffect(() => { - setLocalChartData(initialChartData); - }, [initialChartData]); - - const handleChartClick = () => { - setIsEditorOpen(true); - }; - - const onChartDataChange = (newData: StoreChartData) => { - dispatch(updateSlideChart({ index: slideIndex, chart: newData })); - setLocalChartData(newData); - }; - - const onChartSettingsChange = (newSettings: ChartSettings) => { - dispatch( - updateSlideChartSettings({ - index: slideIndex, - chartSettings: newSettings, - }) - ); - }; - - return ( - <> -
- {renderChart(localChartData, false, currentColors ?? [], chartSettings)} - {/* {localChartData.type} */} -
- - {localChartData && ( - setIsEditorOpen(false)} - chartData={localChartData} - onChartDataChange={onChartDataChange} - /> - )} - - ); -}; - -export default AllChart; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type1Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type1Layout.tsx deleted file mode 100644 index 9570dbe5..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type1Layout.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import ImageEditor from "../ImageEditor"; -import { useSelector } from "react-redux"; -import { RootState } from "@/store/store"; -import SlideFooter from "./SlideFooter"; - -interface Type1LayoutProps { - title: string; - description: string; - slideId: string | null; - images: string[]; - slideIndex: number; - image_prompts?: string[] | null; - properties?: null | any; -} -const Type1Layout = ({ - title, - description, - images, - slideId, - slideIndex, - image_prompts, - properties, -}: Type1LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - return ( -
-
-
- - -
- - - - {/* {imagePosition === 'left' ? ( - <> - - - - ) : ( - <> - - - - )} */} -
- -
- ); -}; - -export default Type1Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type2Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type2Layout.tsx deleted file mode 100644 index aba6a0db..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type2Layout.tsx +++ /dev/null @@ -1,358 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { MoreVertical, Plus } from "lucide-react"; -import ElementMenu from "../ElementMenu"; -import { useSelector } from "react-redux"; -import { numberTranslations } from "../../utils/others"; -import { RootState } from "@/store/store"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type2LayoutProps { - title: string; - body: Array<{ - heading: string; - description: string; - }>; - slideId: string | null; - - slideIndex: number; - language: string; - design_index: number; -} - -const Type2Layout = ({ - title, - body, - slideId, - slideIndex, - design_index, - language, -}: Type2LayoutProps) => { - const { currentColors } = useSelector( - (state: RootState) => state.theme - ); - const { handleAddItem, handleDeleteItem, handleVariantChange } = - useSlideOperations(slideIndex); - - const onAddItem = () => { - if (body.length < 4) { - handleAddItem({ item: { heading: "", description: "" } }); - } - }; - - const onDeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - } - }; - - const VariantMenu = () => ( - - - - - - handleVariantChange({ variant: 1 })} - className={`px-3 py-2 cursor-pointer ${design_index === 1 ? "bg-blue-50" : "" - }`} - > - Default Layout - - handleVariantChange({ variant: 2 })} - className={`px-3 py-2 cursor-pointer ${design_index === 2 ? "bg-blue-50" : "" - }`} - > - Numbered Layout - - handleVariantChange({ variant: 3 })} - className={`px-3 py-2 cursor-pointer ${design_index === 3 ? "bg-blue-50" : "" - }`} - > - Timeline Layout - - - - ); - - const isGridLayout = body.length >= 4; - - const renderContent = () => { - if (design_index === 3) { - return ( -
-
- - {/* Three Dots Icon */} - - - {/* Plus Icon */} - - {/* Timeline Header with Numbers and Line */} -
- {/* Horizontal Line */} -
- - {/* Timeline Numbers */} - {body.map((_, index) => ( -
- - {numberTranslations[language][index || 0]} - -
- ))} -
- - {/* Timeline Content */} -
- {body.map((item, index) => ( -
- -
- - -
-
- ))} -
-
- ); - } - if (isGridLayout) { - return ( -
- {/* Hover Border and Icons for entire layout */} -
- - {/* Three Dots Icon */} - - - {body.map((item, index) => ( -
-
- {design_index === 2 && ( -
- { - numberTranslations[ - language as keyof typeof numberTranslations - ][index] - } -
- )} - -
- - - -
-
-
- ))} -
- ); - } - - // Horizontal layout for 2-3 items - return ( -
- {/* Hover Border and Icons for entire layout */} -
- - {/* Three Dots Icon */} - - - {/* Plus Icon */} - {body.length < 4 && ( - - )} - {body.map((item, index) => ( -
- - - {design_index === 2 && ( -
- { - numberTranslations[ - language as keyof typeof numberTranslations - ][index] - } -
- )} -
- - - -
-
- ))} -
- ); - }; - - return ( -
-
- -
- - {renderContent()} - -
- ); -}; - -export default Type2Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type4Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type4Layout.tsx deleted file mode 100644 index 8fa91f26..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type4Layout.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import ImageEditor from "../ImageEditor"; -import { useDispatch, useSelector } from "react-redux"; -import { RootState } from "@/store/store"; -import ElementMenu from "../ElementMenu"; -import { Plus } from "lucide-react"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type4LayoutProps { - title: string; - body: Array<{ - heading: string; - description: string; - }>; - slideId: string | null; - images: string[]; - slideIndex: number; - image_prompts?: string[] | null; - properties?: null | any; -} - -const Type4Layout = ({ - title, - body, - slideId, - images, - slideIndex, - image_prompts, - properties, -}: Type4LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - const { - handleAddItem, - handleDeleteItem, - handleImageChange, - handleDeleteImage, - } = useSlideOperations(slideIndex); - - const AddItem = () => { - if (body.length < 3) { - handleImageChange({ imageUrl: "", imageIndex: slideIndex }); - handleAddItem({ - item: { heading: "Enter Heading", description: "Enter Description" }, - }); - } - }; - const DeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - handleDeleteImage({ imageIndex: index }); - } - }; - const getGridCols = (length: number) => { - switch (length) { - case 1: return 'lg:grid-cols-1'; - case 2: return 'lg:grid-cols-2'; - case 3: return 'lg:grid-cols-3'; - case 4: return 'lg:grid-cols-4'; - // Add more cases as needed - default: return 'lg:grid-cols-1'; - } - } - - return ( -
-
- -
- -
-
- - {body.map((item, index) => ( -
- - - -
- - -
-
- ))} -
- -
- ); -}; - -export default Type4Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type5Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type5Layout.tsx deleted file mode 100644 index 6d2b2b49..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type5Layout.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import { RootState } from "@/store/store"; -import { useSelector } from "react-redux"; -import AllChart from "./AllChart"; -import SlideFooter from "./SlideFooter"; - -interface Type5LayoutProps { - title: string; - description: string; - slideId: string | null; - chartComponent?: React.ReactNode; - graphData?: any; - slideIndex: number; - isFullSizeGraph?: boolean; -} - -const Type5Layout = ({ - title, - description, - slideId, - chartComponent, - graphData, - slideIndex, - isFullSizeGraph = false, -}: Type5LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - - return ( -
- -
-
- -
-
- -
-
- -
- ); -}; - -export default Type5Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type6Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type6Layout.tsx deleted file mode 100644 index c5f6dc01..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type6Layout.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import { Plus } from "lucide-react"; -import ElementMenu from "../ElementMenu"; -import { useDispatch, useSelector } from "react-redux"; -import { numberTranslations } from "../../utils/others"; -import { RootState } from "@/store/store"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type6LayoutProps { - title: string; - description: string; - body: Array<{ - heading: string; - description: string; - }>; - slideId: string | null; - slideIndex: number; - language: string; -} - -const Type6Layout = ({ - title, - description, - body, - slideId, - slideIndex, - language, -}: Type6LayoutProps) => { - const dispatch = useDispatch(); - const { currentColors } = useSelector((state: RootState) => state.theme); - const { handleAddItem, handleDeleteItem } = useSlideOperations(slideIndex); - - const AddItem = () => { - if (body.length < 3) { - handleAddItem({ item: { heading: "", description: "" } }); - } - }; - const DeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - } - }; - return ( -
-
- {/* Left section - Description */} -
- - - -
- - {/* Right section - Numbered items */} -
-
- -
- {body.map((item, index) => ( -
- -
-
- {numberTranslations[language][index || 0]} -
-
- - -
-
-
- ))} -
-
-
- -
- ); -}; - -export default Type6Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type7Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type7Layout.tsx deleted file mode 100644 index f1831be6..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type7Layout.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import IconsEditor from "../IconsEditor"; -import { Plus } from "lucide-react"; -import ElementMenu from "../ElementMenu"; -import { useSelector } from "react-redux"; -import { RootState } from "@/store/store"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type2LayoutProps { - title: string; - body: Array<{ - heading: string; - description: string; - }>; - icons: string[]; - slideIndex: number; - slideId: string | null; - icon_queries?: Array<{ queries: string[] }> | null; -} - -const Type7Layout = ({ - title, - body, - icons, - slideIndex, - slideId, - icon_queries, -}: Type2LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - const { handleAddItem, handleDeleteItem } = useSlideOperations(slideIndex); - - const AddItem = () => { - if (body.length < 4) { - handleAddItem({ item: { heading: "", description: "" } }); - } - }; - const DeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - } - }; - const getGridCols = (length: number) => { - switch (length) { - case 1: return 'lg:grid-cols-1'; - case 2: return 'lg:grid-cols-2'; - case 3: return 'lg:grid-cols-3'; - case 4: return 'lg:grid-cols-4'; - case 5: return 'lg:grid-cols-5'; - case 6: return 'lg:grid-cols-6'; - case 7: return 'lg:grid-cols-7'; - // Add more cases as needed - default: return 'lg:grid-cols-1'; - } - } - - const isGridLayout = body.length >= 4; - - const renderContent = () => { - if (isGridLayout) { - return ( -
4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full relative group`} - > -
- - - {body.map((item, index) => ( -
- -
-
- -
-
- - -
-
-
- ))} -
- ); - } - - - // Horizontal layout for 2-3 items - return ( -
-
- - - - {body.map((item, index) => ( -
- - -
- - -
-
- ))} -
- ); - }; - - return ( -
-
- -
- {renderContent()} - -
- ); -}; - -export default Type7Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type8Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type8Layout.tsx deleted file mode 100644 index b1af87b3..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type8Layout.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import IconsEditor from "../IconsEditor"; -import { Plus } from "lucide-react"; -import ElementMenu from "../ElementMenu"; -import { useSelector } from "react-redux"; -import { RootState } from "@/store/store"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type6LayoutProps { - title: string; - description: string; - body: Array<{ - heading: string; - description: string; - }>; - icons: string[]; - slideId: string | null; - slideIndex: number; - icon_queries?: Array<{ queries: string[] }> | null; -} - -const Type8Layout = ({ - title, - description, - body, - icons, - slideIndex, - slideId, - icon_queries, -}: Type6LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - const { handleAddItem, handleDeleteItem } = useSlideOperations(slideIndex); - - const AddItem = () => { - if (body.length < 3) { - handleAddItem({ item: { heading: "", description: "" } }); - } - }; - const DeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - } - }; - - return ( -
-
- {/* Left section - Description */} -
- - - -
- - {/* Right section - Numbered items */} -
-
- - - -
- {body && body.length > 0 && body.length === 2 - ? body.map((item, index) => ( -
- - - -
- - -
-
- )) - : body.map((item, index) => ( -
- -
-
- -
-
- - -
-
-
- ))} -
-
-
- -
- ); -}; - -export default Type8Layout; diff --git a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type9Layout.tsx b/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type9Layout.tsx deleted file mode 100644 index b42706ef..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/slide_layouts/Type9Layout.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from "react"; -import EditableText from "../EditableText"; -import { Plus } from "lucide-react"; -import ElementMenu from "../ElementMenu"; -import { useSelector } from "react-redux"; -import { numberTranslations } from "../../utils/others"; -import { RootState } from "@/store/store"; -import AllChart from "./AllChart"; -import { useSlideOperations } from "../../hooks/use-slide-operations"; -import SlideFooter from "./SlideFooter"; - -interface Type9LayoutProps { - title: string; - body: Array<{ - heading: string; - description: string; - }>; - graphData?: any; - slideId: string | null; - language: string; - slideIndex: number; -} - -const Type9Layout = ({ - title, - body, - graphData, - slideId, - slideIndex, - language, -}: Type9LayoutProps) => { - const { currentColors } = useSelector((state: RootState) => state.theme); - const { handleAddItem, handleDeleteItem } = useSlideOperations(slideIndex); - const AddItem = () => { - if (body.length < 3) { - handleAddItem({ item: { heading: "", description: "" } }); - } - }; - const DeleteItem = (index: number) => { - if (body.length > 2) { - handleDeleteItem({ itemIndex: index }); - } - }; - return ( -
-
- {/* Left section - Chart */} -
- -
-
- -
-
-
- - {/* Right section - Numbered items */} - -
-
- -
- {body.length > 0 && - body.map((item, index) => ( -
- -
-
- {numberTranslations[language][index || 0]} -
- -
- - -
-
-
- ))} -
-
-
- -
- ); -}; - -export default Type9Layout; diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx index 0480be69..8357ea4f 100644 --- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx @@ -13,7 +13,6 @@ "use client"; -import styles from "../styles/main.module.css"; import { useEffect, useState, useRef, useMemo } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { OverlayLoader } from "@/components/ui/overlay-loader"; @@ -29,7 +28,6 @@ import { getIconFromFile } from "../../utils/others"; import { ChevronRight, PanelRightOpen, X } from "lucide-react"; import ToolTip from "@/components/ToolTip"; import Header from "@/app/dashboard/components/Header"; -import { useLayout } from "../../context/LayoutContext"; // Types interface LoadingState { @@ -214,7 +212,7 @@ const DocumentsPreviewPage: React.FC = () => { if (!isOpen) return null; return ( -
setIsOpen(false)} @@ -230,7 +228,7 @@ const DocumentsPreviewPage: React.FC = () => {
updateSelectedDocument(key)} - className={`${selectedDocument === key ? styles.selected_border : "" + className={`${selectedDocument === key ? 'border border-blue-500' : "" } flex p-2 rounded-sm gap-2 items-center cursor-pointer`} > { }; return ( -
+
{ - const dispatch = useDispatch(); - - const handleAddItem = ({ item }: { item: any }) => { - dispatch(addSlideBodyItem({ index: slideIndex, item })); - }; - - const handleDeleteItem = ({ itemIndex }: { itemIndex: number }) => { - dispatch(deleteSlideBodyItem({ index: slideIndex, itemIdx: itemIndex })); - }; - - const handleVariantChange = ({ variant }: { variant: number }) => { - dispatch(updateSlideVariant({ index: slideIndex, variant })); - }; - - const handleImageChange = ({ - imageUrl, - imageIndex, - }: { - imageUrl: string; - imageIndex: number; - }) => { - dispatch( - updateSlideImage({ - index: slideIndex, - imageIdx: imageIndex, - image: imageUrl, - }) - ); - }; - const handleDeleteImage = ({ imageIndex }: { imageIndex: number }) => { - dispatch(deleteSlideImage({ index: slideIndex, imageIdx: imageIndex })); - }; - - // Add other common slide operations here - - return { - handleAddItem, - handleDeleteItem, - handleVariantChange, - handleImageChange, - handleDeleteImage, - }; -}; diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 2518f450..64d3a9b7 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -1,12 +1,9 @@ "use client"; import { Button } from "@/components/ui/button"; import { - Menu, - Palette, SquareArrowOutUpRight, Play, Loader2, - ExternalLink, } from "lucide-react"; import React, { useState } from "react"; import Wrapper from "@/components/Wrapper"; @@ -16,38 +13,21 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import UserAccount from "../../components/UserAccount"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import { OverlayLoader } from "@/components/ui/overlay-loader"; -import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { useDispatch, useSelector } from "react-redux"; import Link from "next/link"; -import { ThemeType } from "@/app/(presentation-generator)/upload/type"; -import { - setTheme, - setThemeColors, - defaultColors, - serverColors, -} from "../../store/themeSlice"; -import CustomThemeSettings from "../../components/CustomThemeSettings"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; import { RootState } from "@/store/store"; import { toast } from "@/hooks/use-toast"; -import ThemeSelector from "./ThemeSelector"; import Modal from "./Modal"; import Announcement from "@/components/Announcement"; -import { getFontLink, getStaticFileUrl } from "../../utils/others"; +import { getStaticFileUrl } from "../../utils/others"; import { PptxPresentationModel } from "@/types/pptx_models"; +import HeaderNav from "../../components/HeaderNab"; const Header = ({ @@ -61,66 +41,13 @@ const Header = ({ const [showLoader, setShowLoader] = useState(false); const router = useRouter(); - const [showCustomThemeModal, setShowCustomThemeModal] = useState(false); const [showDownloadModal, setShowDownloadModal] = useState(false); const [downloadPath, setDownloadPath] = useState(""); - const { currentTheme, currentColors } = useSelector( - (state: RootState) => state.theme - ); + const { presentationData, isStreaming } = useSelector( (state: RootState) => state.presentationGeneration ); const dispatch = useDispatch(); - const handleThemeSelect = async (value: string) => { - if (isStreaming) return; - if (value === "custom") { - setShowCustomThemeModal(true); - return; - } else { - const themeType = value as ThemeType; - const themeColors = serverColors[themeType] || defaultColors[themeType]; - - if (themeColors) { - try { - // Update UI - dispatch(setTheme(themeType)); - dispatch(setThemeColors({ ...themeColors, theme: themeType })); - // Set CSS variables - const root = document.documentElement; - root.style.setProperty( - `--${themeType}-slide-bg`, - themeColors.slideBg - ); - root.style.setProperty( - `--${themeType}-slide-title`, - themeColors.slideTitle - ); - root.style.setProperty( - `--${themeType}-slide-heading`, - themeColors.slideHeading - ); - root.style.setProperty( - `--${themeType}-slide-description`, - themeColors.slideDescription - ); - root.style.setProperty( - `--${themeType}-slide-box`, - themeColors.slideBox - ); - - - } catch (error) { - console.error("Failed to update theme:", error); - toast({ - title: "Error updating theme", - description: - "Failed to update the presentation theme. Please try again.", - variant: "destructive", - }); - } - } - } - }; const get_presentation_pptx_model = async (id: string): Promise => { const response = await fetch(`/api/presentation_to_pptx_model?id=${id}`); @@ -212,15 +139,8 @@ const Header = ({ pptx export Export as PPTX - {/*
- -
*/} -

- Font Used: - - {getFontLink(currentColors.fontFamily).name || ''} - -

+ +
); @@ -282,68 +202,16 @@ const Header = ({ {isStreaming && ( )} - - {/* Custom Theme Modal */} - setShowCustomThemeModal(false)} - title="Custom Theme Colors" - > - setShowCustomThemeModal(false)} - presentationId={presentation_id} - /> - + + - +
{/* Mobile Menu */}
- - - - - - -
- - -
-
-
+ +
{/* Download Modal */} diff --git a/servers/nextjs/app/(presentation-generator)/components/LoadingState.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/LoadingState.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/components/LoadingState.tsx rename to servers/nextjs/app/(presentation-generator)/presentation/components/LoadingState.tsx diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index f5d5ec36..dfff071b 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -6,7 +6,6 @@ import { Skeleton } from "@/components/ui/skeleton"; import PresentationMode from "../../components/PresentationMode"; import SidePanel from "../components/SidePanel"; import SlideContent from "../components/SlideContent"; -import LoadingState from "../../components/LoadingState"; import Header from "../components/Header"; import { Button } from "@/components/ui/button"; import { AlertCircle, Loader2 } from "lucide-react"; @@ -18,6 +17,7 @@ import { useAutoSave } from "../hooks"; import { PresentationPageProps } from "../types"; +import LoadingState from "./LoadingState"; const PresentationPage: React.FC = ({ presentation_id }) => { // State management @@ -27,10 +27,7 @@ const PresentationPage: React.FC = ({ presentation_id }) const [error, setError] = useState(false); const [isMobilePanelOpen, setIsMobilePanelOpen] = useState(false); - // Redux state - const { currentTheme, currentColors } = useSelector( - (state: RootState) => state.theme - ); + const { presentationData, isStreaming } = useSelector( (state: RootState) => state.presentationGeneration ); @@ -84,7 +81,7 @@ const PresentationPage: React.FC = ({ presentation_id }) = ({ presentation_id })
diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SidePanel.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SidePanel.tsx index 16c57120..f14364ab 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SidePanel.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SidePanel.tsx @@ -45,9 +45,7 @@ const SidePanel = ({ const { presentationData, isStreaming } = useSelector( (state: RootState) => state.presentationGeneration ); - const { currentTheme, currentColors } = useSelector( - (state: RootState) => state.theme - ); + const dispatch = useDispatch(); // Use the centralized group layouts hook @@ -159,16 +157,10 @@ const SidePanel = ({ `} >
diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index 9fdb7dec..a260734a 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -14,8 +14,8 @@ import ToolTip from "@/components/ToolTip"; import { RootState } from "@/store/store"; import { useDispatch, useSelector } from "react-redux"; import { deletePresentationSlide, updateSlide } from "@/store/slices/presentationGeneration"; -import NewSlide from "../../components/slide_layouts/NewSlide"; import { useGroupLayouts } from "../../hooks/useGroupLayouts"; +import NewSlide from "../../components/NewSlide"; interface SlideContentProps { slide: any; diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx deleted file mode 100644 index fcbdb4f3..00000000 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React from "react"; - -const ThemeSelector = ({ - onSelect, - selectedTheme, -}: { - onSelect: (theme: string) => void; - selectedTheme: string; -}) => { - return ( -
- - - - - - - - - -
- ); -}; - -export default ThemeSelector; - -const ThemePreview = ({ - theme, - color, - isSelected, -}: { - theme: string; - color: string; - isSelected: boolean; -}) => ( -
-
-
-
-
-
-
-
- - {theme} - -
-); diff --git a/servers/nextjs/app/(presentation-generator)/store/themeSlice.ts b/servers/nextjs/app/(presentation-generator)/store/themeSlice.ts deleted file mode 100644 index d848cf05..00000000 --- a/servers/nextjs/app/(presentation-generator)/store/themeSlice.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { ThemeType } from "@/app/(presentation-generator)/upload/type"; -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -export const defaultColors = { - light: { - background: "#c8c7c9", - slideBg: "#F2F2F2", - slideTitle: "#000000", - slideHeading: "#1a1a1a", - slideDescription: "#333333", - slideBox: "#ffffff", - iconBg: "#1F1F2D", - chartColors: ["#1F1F2D", "#3F3F5D", "#62628E", "#8F8FB2", "#C0C0D3"], - fontFamily: "var(--font-inter)", - }, - dark: { - background: "#000000", - slideBg: "#1E1E1E", - slideTitle: "#ffffff", - slideHeading: "#f5f5f5", - slideDescription: "#e0e0e0", - slideBox: "#2d2d2d", - iconBg: "#5E8CF0", - chartColors: ["#5E8CF0", "#8800ff", "#b200ff", "#d700ff", "#ef00ff"], - fontFamily: "var(--font-inter)", - }, - faint_yellow: { - background: "#d9cebc", - slideBg: "#F8F4E8", - slideTitle: "#2C1810", - slideHeading: "#4A3728", - slideDescription: "#665E57", - slideBox: "#FFFFFF", - iconBg: "#281810", - chartColors: ["#281810", "#4A3728", "#665E57", "#665E57", "#665E57"], - fontFamily: "var(--font-inter)", - }, - custom: { - background: "#63ceff", - slideBg: "#F4F4F4", - slideTitle: "#1A1A1A", - slideHeading: "#2D2D2D", - slideDescription: "#4A4A4A", - slideBox: "#d8c6c6", - iconBg: "#281810", - chartColors: ["#281810", "#4A3728", "#665E57", "#665E57", "#665E57"], - fontFamily: "var(--font-inter)", - }, - cream: { - background: "#DDCFBB", - slideBg: "#F9F6F0", - slideTitle: "#484237", - slideHeading: "#484237", - slideDescription: "#595F6C", - slideBox: "#EEE9DD", - iconBg: "#A6825B", - chartColors: ["#765939", "#A6825B", "#B89B7C", "#CAB49D", "#DBCDBD"], - fontFamily: "var(--font-fraunces)", - }, - royal_blue: { - background: "#010103", - slideBg: "#091433", - slideTitle: "#ffffff", - slideHeading: "#ffffff", - slideDescription: "#E6E6E6", - slideBox: "#29136C", - iconBg: "#5E8CF0", - chartColors: ["#5E8CF0", "#496CEB", "#f051b5", "#F7A8FF", "#FCD8FF"], - fontFamily: "var(--font-instrument-sans)", - }, - light_red: { - background: "#F8E9E8", - slideBg: "#FFFAFA", - slideTitle: "#181D27", - slideHeading: "#252B37", - slideDescription: "#595F6C", - slideBox: "#F3E8E8", - iconBg: "#F0695F", - chartColors: [ - "#F0695F", - "#450808", - "#8F1010", - "#C1392F", - "#EC5555", - "#F49E9E", - ], - fontFamily: "var(--font-montserrat)", - }, - dark_pink: { - background: "#F3AEED", - slideBg: "#F9E8FF", - slideTitle: "#261827", - slideHeading: "#252B37", - slideDescription: "#6A596C", - slideBox: "#F0D4F7", - iconBg: "#D02CE5", - chartColors: ["#D02CE5", "#B414C9", "#6E1886", "#A724CC", "#C65FE3"], - fontFamily: "var(--font-inria-serif)", - }, -}; - -// Store the server-provided colors -export const serverColors: { [key in ThemeType]?: ThemeColors } = {}; - -export interface ThemeColors { - background: string; - slideBg: string; - slideTitle: string; - slideHeading: string; - slideDescription: string; - slideBox: string; - iconBg: string; - chartColors: string[]; - fontFamily: string; -} - -interface ThemeState { - currentTheme: ThemeType; - currentColors: ThemeColors; - isLoading: boolean; -} - -const initialState: ThemeState = { - currentTheme: ThemeType.Light, - currentColors: defaultColors.light, - isLoading: false, -}; - -const themeSlice = createSlice({ - name: "theme", - initialState, - reducers: { - setTheme: (state, action: PayloadAction) => { - state.currentTheme = action.payload; - // Use server colors if available, otherwise fall back to default - state.currentColors = - serverColors[action.payload] || defaultColors[action.payload]; - }, - setThemeColors: ( - state, - action: PayloadAction & { theme: ThemeType }> - ) => { - const newColors = { ...state.currentColors, ...action.payload }; - state.currentColors = newColors; - state.currentTheme = action.payload.theme; - // Store the colors for this theme - serverColors[action.payload.theme] = newColors; - }, - setLoadingState: (state, action: PayloadAction) => { - state.isLoading = action.payload; - }, - loadSavedTheme: (state, action: PayloadAction) => { - if (action.payload.name === "custom") { - state.currentTheme = ThemeType.Custom; - state.currentColors = action.payload.colors; - serverColors.custom = action.payload.colors; - } - }, - }, -}); - -export const { setTheme, setThemeColors, setLoadingState, loadSavedTheme } = - themeSlice.actions; -export default themeSlice.reducer; diff --git a/servers/nextjs/app/(presentation-generator)/styles/themes.css b/servers/nextjs/app/(presentation-generator)/styles/themes.css deleted file mode 100644 index 0063ee06..00000000 --- a/servers/nextjs/app/(presentation-generator)/styles/themes.css +++ /dev/null @@ -1,148 +0,0 @@ -/* Light Theme */ -.slide-theme[data-theme="light"] { - --slide-bg: var(--light-slide-bg); - --slide-title: var(--light-slide-title); - --slide-heading: var(--light-slide-heading); - --slide-description: var(--light-slide-description); - --slide-box: var(--light-slide-box); -} - -/* Dark Theme */ -.slide-theme[data-theme="dark"] { - --slide-bg: var(--dark-slide-bg); - --slide-title: var(--dark-slide-title); - --slide-heading: var(--dark-slide-heading); - --slide-description: var(--dark-slide-description); - --slide-box: var(--dark-slide-box); -} - -/* Classic Theme */ -.slide-theme[data-theme="faint_yellow"] { - --slide-bg: var(--faint_yellow-slide-bg); - --slide-title: var(--faint_yellow-slide-title); - --slide-heading: var(--faint_yellow-slide-heading); - --slide-description: var(--faint_yellow-slide-description); - --slide-box: var(--faint_yellow-slide-box); -} - -/* Custom Theme */ -.slide-theme[data-theme="custom"] { - --slide-bg: var(--custom-slide-bg); - --slide-title: var(--custom-slide-title); - --slide-heading: var(--custom-slide-heading); - --slide-description: var(--custom-slide-description); - --slide-box: var(--custom-slide-box); -} - -/* Royal Blue Theme */ -.slide-theme[data-theme="royal_blue"] { - --slide-bg: var(--royal_blue-slide-bg); - --slide-title: var(--royal_blue-slide-title); - --slide-heading: var(--royal_blue-slide-heading); - --slide-description: var(--royal_blue-slide-description); - --slide-box: var(--royal_blue-slide-box); -} - -/* Light Red Theme */ -.slide-theme[data-theme="light_red"] { - --slide-bg: var(--light_red-slide-bg); - --slide-title: var(--light_red-slide-title); - --slide-heading: var(--light_red-slide-heading); - --slide-description: var(--light_red-slide-description); - --slide-box: var(--light_red-slide-box); -} - -/* Dark Pink Theme */ -.slide-theme[data-theme="dark_pink"] { - --slide-bg: var(--dark_pink-slide-bg); - --slide-title: var(--dark_pink-slide-title); - --slide-heading: var(--dark_pink-slide-heading); - --slide-description: var(--dark_pink-slide-description); - --slide-box: var(--dark_pink-slide-box); -} - -/* Cream Theme */ -.slide-theme[data-theme="cream"] { - --slide-bg: var(--cream-slide-bg); - --slide-title: var(--cream-slide-title); - --slide-heading: var(--cream-slide-heading); - --slide-description: var(--cream-slide-description); - --slide-box: var(--cream-slide-box); -} - - -/* Apply theme styles - Make sure these selectors are more specific */ -.slide-theme .slide-container { - background-color: var(--slide-bg); - -} - - -.slide-theme .slide-title { - color: var(--slide-title); -} - -.slide-theme .slide-heading { - color: var(--slide-heading); -} - -.slide-theme .slide-description { - color: var(--slide-description); -} - -.slide-theme .slide-box { - background-color: var(--slide-box); -} - -/* Add to your existing CSS */ -.slide-theme { - transition: background-color 0.3s ease, color 0.3s ease; -} - -.slide-theme .border-color{ - border-color: var(--slide-box); -} - - -@keyframes progress { - 0% { width: 5%; } - 20% { width: 25%; } - 50% { width: 45%; } - 75% { width: 75%; } - 90% { width: 85%; } - 100% { width: 90%; } -} - -@keyframes fade-in { - 0% { opacity: 0; transform: translateY(10px); } - 100% { opacity: 1; transform: translateY(0); } -} - -.animate-progress { - animation: progress 20s ease-out forwards; -} - -.animate-fade-in { - animation: fade-in 0.5s ease-out forwards; -} - -@keyframes border-dance { - 0% { - background-position: 0 0, 100% 0, 100% 100%, 0 100%; - } - 100% { - background-position: 100% 0, 100% 100%, 0 100%, 0 0; - } -} - -.animate-border { - background-image: - linear-gradient(90deg, #5141e5 50%, transparent 50%), /* top */ - linear-gradient(90deg, #5141e5 50%, transparent 50%), /* right */ - linear-gradient(90deg, #5141e5 50%, transparent 50%), /* bottom */ - linear-gradient(90deg, #5141e5 50%, transparent 50%); /* left */ - background-repeat: repeat-x, repeat-y, repeat-x, repeat-y; - background-size: 15px 2px, 2px 15px, 15px 2px, 2px 15px; - background-position: 0 0, 100% 0, 100% 100%, 0 100%; - animation: border-dance 6s infinite linear; -} \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx b/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx deleted file mode 100644 index fb03d405..00000000 --- a/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx +++ /dev/null @@ -1,184 +0,0 @@ -"use client"; -import React, { useEffect, useState } from "react"; -import { Card } from "@/components/ui/card"; -import { defaultColors, setTheme, ThemeColors } from "../store/themeSlice"; -import Header from "@/app/dashboard/components/Header"; -import Wrapper from "@/components/Wrapper"; -import { useDispatch } from "react-redux"; -import { useRouter } from "next/navigation"; -import { ThemeType } from "../upload/type"; -import { Button } from "@/components/ui/button"; - -import { toast } from "@/hooks/use-toast"; - -interface ThemeCardProps { - name: string; - font: string; - colors: ThemeColors; - selected: boolean; - onClick: () => void; -} - -const ThemeCard = ({ - name, - font, - colors, - selected, - onClick, -}: ThemeCardProps) => { - return ( -
- -
-
-
-

- {name} -

-

- This is the body paragraph -

-
-
-
-
-
- ); -}; - -const ThemePage = () => { - const themes = [ - { - name: "Dark Theme", - colors: defaultColors.dark, - type: "dark", - font: "var(--font-inter)", - }, - - { - name: "Royal Blue Theme", - colors: defaultColors.royal_blue, - type: "royal_blue", - font: "var(--font-instrument-sans)", - }, - { - name: "Creme Theme", - colors: defaultColors.cream, - type: "cream", - font: "var(--font-fraunces)", - }, - { - name: "Light Red Theme", - colors: defaultColors.light_red, - type: "light_red", - font: "var(--font-montserrat)", - }, - { - name: "Dark Pink Theme", - colors: defaultColors.dark_pink, - type: "dark_pink", - font: "var(--font-inria-serif)", - }, - { - name: "Light Theme", - colors: defaultColors.light, - type: "light", - font: "var(--font-inter)", - }, - - { - name: "Faint Yellow Theme", - colors: defaultColors.faint_yellow, - type: "faint_yellow", - font: "var(--font-inter)", - }, - ]; - const dispatch = useDispatch(); - const router = useRouter(); - const [selectedTheme, setSelectedTheme] = useState(null); - const handleThemeClick = async (theme: ThemeColors, type: string) => { - setSelectedTheme(type as ThemeType); - }; - const handleSubmit = () => { - if (!selectedTheme) { - toast({ - title: "Please select a theme", - variant: "destructive", - }); - return; - } - dispatch(setTheme(selectedTheme as ThemeType)); - - router.push("/outline"); - }; - - return ( -
-
- -

Select a Theme

-
- {themes.map((theme, index) => ( - handleThemeClick(theme.colors, theme.type)} - /> - ))} -
- -
-
- ); -}; - -export default ThemePage; diff --git a/servers/nextjs/app/(presentation-generator)/theme/loading.tsx b/servers/nextjs/app/(presentation-generator)/theme/loading.tsx deleted file mode 100644 index 3a4314c1..00000000 --- a/servers/nextjs/app/(presentation-generator)/theme/loading.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Skeleton } from '@/components/ui/skeleton' -import React from 'react' - -const loading = () => { - return ( -
- -
- - -
- { - Array.from({ length: 6 }).map((_, index) => ( - - )) - } -
-
-
- - ) -} - -export default loading \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/theme/page.tsx b/servers/nextjs/app/(presentation-generator)/theme/page.tsx deleted file mode 100644 index 0437a922..00000000 --- a/servers/nextjs/app/(presentation-generator)/theme/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import ThemePage from './ThemePage' -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: "Theme Selection | Presenton.ai", - description: "Select a Presenton theme which will be suitable for your presentation", -} - -const page = () => { - return ( - - ) -} - -export default page diff --git a/servers/nextjs/app/(presentation-generator)/utils/NewSlideContent.ts b/servers/nextjs/app/(presentation-generator)/utils/NewSlideContent.ts deleted file mode 100644 index 115dcd71..00000000 --- a/servers/nextjs/app/(presentation-generator)/utils/NewSlideContent.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { randomChartGenerator } from "@/lib/utils"; -import { Slide } from "../types/slide"; -import { generateRandomId } from "./others"; - -const randomGraph = (presentation_id: string) => { - const randomData = randomChartGenerator(); - - return { - id: generateRandomId(), - name: "Sales Performance", - type: "bar", - presentation: presentation_id, - postfix: "", - data: randomData, - }; -}; - -export const getEmptySlideContent = ( - type: number, - index: number, - presentation_id: string -): Slide => { - const baseSlide: Slide = { - id: generateRandomId(), - type, - index, - design_index: 1, - properties: null, - images: [], - icons: [], - graph_id: null, - presentation: presentation_id, - content: { - title: "", - body: "", - infographics: [], - }, - }; - const graph = randomGraph(presentation_id); - - switch (type) { - case 1: - return { - ...baseSlide, - images: [""], - content: { - title: "New Title", - body: "Add your description here", - image_prompts: [""], - }, - }; - case 2: - return { - ...baseSlide, - content: { - title: "New Title", - body: [ - { heading: "First Point", description: "Add description" }, - { heading: "Second Point", description: "Add description" }, - ], - }, - }; - case 4: - return { - ...baseSlide, - images: ["", "", ""], - content: { - title: "New Title", - body: [ - { heading: "First Item", description: "Add description" }, - { heading: "Second Item", description: "Add description" }, - { heading: "Third Item", description: "Add description" }, - ], - image_prompts: ["", "", ""], - }, - }; - case 5: - return { - ...baseSlide, - graph_id: graph.id, - content: { - graph: graph, - title: "New Title", - body: "Add your description here", - }, - }; - case 6: - return { - ...baseSlide, - content: { - title: "New Title", - description: "Add your description here", - body: [ - { heading: "First Point", description: "Add description" }, - { heading: "Second Point", description: "Add description" }, - ], - }, - }; - case 7: - return { - ...baseSlide, - icons: ["", "", ""], - content: { - title: "New Title", - body: [ - { heading: "First Point", description: "Add description" }, - { heading: "Second Point", description: "Add description" }, - ], - icon_queries: [ - { - queries: [""], - }, - ], - }, - }; - case 8: - return { - ...baseSlide, - icons: ["", "", ""], - content: { - title: "New Title", - description: "Add your description here", - body: [ - { heading: "First Point", description: "Add description" }, - { heading: "Second Point", description: "Add description" }, - ], - icon_queries: [ - { - queries: [""], - }, - ], - }, - }; - case 9: - return { - ...baseSlide, - graph_id: graph.id, - content: { - graph: graph, - title: "New Subheading", - body: [ - { heading: "First Point", description: "Add description" }, - { heading: "Second Point", description: "Add description" }, - ], - }, - }; - - default: - return baseSlide; - } -}; diff --git a/servers/nextjs/app/(presentation-generator)/utils/chart.tsx b/servers/nextjs/app/(presentation-generator)/utils/chart.tsx deleted file mode 100644 index daced823..00000000 --- a/servers/nextjs/app/(presentation-generator)/utils/chart.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { formatLargeNumber } from "@/lib/utils"; -import { Chart } from "@/store/slices/presentationGeneration"; - -export const formatTooltipValue = (localChartData: Chart, value: number) => { - const formattedValue = formatLargeNumber(value); - if (localChartData.postfix) { - return `${formattedValue}${localChartData.postfix}`; - } - return formattedValue; -}; -export const transformedData = (localChartData: Chart) => { - if (!localChartData) return []; - if (!localChartData.data || localChartData.data.categories.length === 0) - return []; - if (localChartData && localChartData.type === "pie") { - return localChartData.data.categories.map((category, index) => ({ - name: category, - value: localChartData.data.series[0].data[index], - actualValue: localChartData.data.series[0].data[index], - seriesName: localChartData.data.series[0].name || "Series 1", - })); - } else { - return localChartData.data.categories.map((category, index) => { - const dataPoint: any = { name: category }; - localChartData.data.series.forEach((serie) => { - const seriesName = serie.name || "Series"; - dataPoint[seriesName] = serie.data[index]; - dataPoint[`${seriesName}Value`] = serie.data[index]; - }); - return dataPoint; - }); - } -}; - -export const formatYAxisTick = (value: number) => { - if (value >= 1_000_000_000_000) { - return `${(value / 1_000_000_000_000).toFixed(0)}T`; - } else if (value >= 1_000_000_000) { - return `${(value / 1_000_000_000).toFixed(0)}B`; - } else if (value >= 1_000_000) { - return `${(value / 1_000_000).toFixed(0)}M`; - } else if (value >= 1_000) { - return `${(value / 1_000).toFixed(0)}k`; - } - return value.toString(); -}; diff --git a/servers/nextjs/app/(presentation-generator)/utils/chartDataTransforms.ts b/servers/nextjs/app/(presentation-generator)/utils/chartDataTransforms.ts deleted file mode 100644 index fb07825f..00000000 --- a/servers/nextjs/app/(presentation-generator)/utils/chartDataTransforms.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Store Chart Data Type -export interface StoreChartData { - id: string; - name: string; - type: 'bar' | 'line' | 'pie'; - style: { - - }; - presentation: string; - postfix: string; - data: { - categories: string[]; - series: Array<{ - name?: string; - data: number[]; - }>; - }; -} diff --git a/servers/nextjs/app/(presentation-generator)/utils/layoutsExtractor.ts b/servers/nextjs/app/(presentation-generator)/utils/layoutsExtractor.ts deleted file mode 100644 index c76facc4..00000000 --- a/servers/nextjs/app/(presentation-generator)/utils/layoutsExtractor.ts +++ /dev/null @@ -1,285 +0,0 @@ -import * as z from 'zod'; -import fs from 'fs'; -import * as path from 'path'; - -interface LayoutInfo { - id: string; - name: string; - description: string; - json_schema: Record; - group: string; -} - -interface LayoutGroup { - id: string; - ordered: boolean; - slides: string[]; -} - -interface LayoutStructure { - name: string; - ordered: boolean; - slides: LayoutInfo[]; -} - -interface GroupedLayoutsResponse { - group: string; - files: string[]; -} - -// Cache for layouts to avoid repeated file system operations -let layoutsCache: LayoutStructure[] | null = null; - -/** - * Dynamically imports a layout file and extracts its schema and metadata - */ -async function extractLayoutFromFile(filePath: string, fileName: string, groupName: string): Promise { - try { - // Import the layout module dynamically - const module = await import(filePath); - - // Check if the module has a Schema export - if (!module.Schema) { - console.warn(`No Schema export found in ${fileName}`); - return null; - } - - // Extract layout metadata (optional) - const layoutId = module.layoutId || fileName.replace(/\.tsx?$/, '').toLowerCase().replace(/layout$/, ''); - const layoutName = module.layoutName || fileName.replace(/\.tsx?$/, '').replace(/([A-Z])/g, ' $1').trim(); - const layoutDescription = module.layoutDescription || `${layoutName} layout for presentations`; - - // Convert Zod schema to JSON schema - const jsonSchema = z.toJSONSchema(module.Schema, { - override: (ctx) => { - delete ctx.jsonSchema.default; - }, - }); - - return { - id: layoutId, - name: layoutName, - description: layoutDescription, - json_schema: jsonSchema, - group: groupName - }; - } catch (error: unknown) { - console.error(`Error extracting layout from ${fileName}:`, error); - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - throw new Error(`Failed to extract schema from ${fileName}: ${errorMessage}`); - } -} - -/** - * Gets all layout files from the grouped presentation-layouts directory - */ -async function getGroupedLayoutFiles(): Promise { - const layoutsDirectory = path.join(process.cwd(), 'presentation-layouts'); - - if (!fs.existsSync(layoutsDirectory)) { - throw new Error(`Layouts directory not found at ${layoutsDirectory}`); - } - - const items = fs.readdirSync(layoutsDirectory, { withFileTypes: true }); - const groupDirectories = items - .filter(item => item.isDirectory()) - .map(dir => dir.name); - - const allLayouts: GroupedLayoutsResponse[] = []; - - for (const groupName of groupDirectories) { - try { - const groupPath = path.join(layoutsDirectory, groupName); - const groupFiles = fs.readdirSync(groupPath); - - // Filter for TypeScript/TSX files, excluding setting.json and other non-layout files - const layoutFiles = groupFiles.filter(file => - (file.endsWith('.ts') || file.endsWith('.tsx')) && - file !== 'setting.json' && - !file.startsWith('.') && - !file.includes('.test.') && - !file.includes('.spec.') - ); - - if (layoutFiles.length > 0) { - allLayouts.push({ - group: groupName, - files: layoutFiles - }); - } - } catch (error) { - console.error(`Error reading group directory ${groupName}:`, error); - // Continue with other groups even if one fails - } - } - - return allLayouts; -} - -/** - * Extracts layout groups from layoutGroup.ts file in presentation-layouts directory - */ -async function extractLayoutGroups(): Promise { - try { - const layoutGroupPath = path.join(process.cwd(), 'presentation-layouts', 'layoutGroup.ts'); - - if (!fs.existsSync(layoutGroupPath)) { - throw new Error('layoutGroup.ts file not found in presentation-layouts directory'); - } - - const module = await import(layoutGroupPath); - - // Extract all exported layout groups - const layoutGroups: LayoutGroup[] = []; - - Object.keys(module).forEach(key => { - const exportedItem = module[key]; - - // Check if it's a layout group object - if (exportedItem && - typeof exportedItem === 'object' && - exportedItem.id && - Array.isArray(exportedItem.slides)) { - - layoutGroups.push({ - id: exportedItem.id, - ordered: exportedItem.ordered || false, - slides: exportedItem.slides - }); - } - }); - - if (layoutGroups.length === 0) { - throw new Error('No valid layout groups found in layoutGroup.ts'); - } - - return layoutGroups; - } catch (error) { - console.error('Error extracting layout groups:', error); - throw error; - } -} - -/** - * Maps layout information to layout groups - */ -function mapLayoutsToGroups( - layoutInfos: LayoutInfo[], - layoutGroups: LayoutGroup[] -): LayoutStructure[] { - return layoutGroups.map(group => { - const groupSlides: LayoutInfo[] = []; - - // Map slides in the group to their layout info - group.slides.forEach(slideId => { - const layoutInfo = layoutInfos.find(layout => - layout.id === slideId || - layout.id.replace('-', '') === slideId.replace('-', '') || - layout.id.toLowerCase() === slideId.toLowerCase() - ); - - if (layoutInfo) { - groupSlides.push(layoutInfo); - } else { - console.warn(`Layout info not found for slide ID: ${slideId}`); - } - }); - - return { - name: group.id, - ordered: group.ordered, - slides: groupSlides - }; - }); -} - -/** - * Main function to extract all layouts dynamically from grouped structure - */ -export async function extractLayouts(): Promise { - // Return cached layouts if available - if (layoutsCache) { - return layoutsCache; - } - - try { - // Get all grouped layout files - const groupedLayoutFiles = await getGroupedLayoutFiles(); - - if (groupedLayoutFiles.length === 0) { - throw new Error('No layout files found in the presentation-layouts directory'); - } - - // Extract layout information from each file in each group - const allLayoutPromises: Promise[] = []; - - for (const groupData of groupedLayoutFiles) { - for (const fileName of groupData.files) { - const filePath = path.join(process.cwd(), 'presentation-layouts', groupData.group, fileName); - allLayoutPromises.push(extractLayoutFromFile(filePath, fileName, groupData.group)); - } - } - - const layoutResults = await Promise.all(allLayoutPromises); - - // Filter out null results (files without valid schemas) - const validLayouts = layoutResults.filter((layout): layout is LayoutInfo => layout !== null); - - if (validLayouts.length === 0) { - throw new Error('No valid schemas found in any layout files'); - } - - // Extract layout groups - const layoutGroups = await extractLayoutGroups(); - - // Map layouts to groups - const mappedLayouts = mapLayoutsToGroups(validLayouts, layoutGroups); - - // Cache the results - layoutsCache = mappedLayouts; - - return mappedLayouts; - } catch (error) { - console.error('Error extracting layouts:', error); - throw error; - } -} - -/** - * Clears the layout cache (useful for development/testing) - */ -export function clearLayoutCache(): void { - layoutsCache = null; -} - -/** - * Gets a specific layout by ID - */ -export async function getLayoutById(layoutId: string): Promise { - const layouts = await extractLayouts(); - - for (const group of layouts) { - const layout = group.slides.find(slide => slide.id === layoutId); - if (layout) { - return layout; - } - } - - return null; -} - -/** - * Gets all available layout IDs - */ -export async function getAllLayoutIds(): Promise { - const layouts = await extractLayouts(); - const ids: string[] = []; - - layouts.forEach(group => { - group.slides.forEach(slide => { - ids.push(slide.id); - }); - }); - - return ids; -} \ No newline at end of file diff --git a/servers/nextjs/app/dashboard/components/Header.tsx b/servers/nextjs/app/dashboard/components/Header.tsx index 6ca4c67e..6063e8e1 100644 --- a/servers/nextjs/app/dashboard/components/Header.tsx +++ b/servers/nextjs/app/dashboard/components/Header.tsx @@ -3,9 +3,9 @@ import Wrapper from "@/components/Wrapper"; import React from "react"; import Link from "next/link"; -import UserAccount from "@/app/(presentation-generator)/components/UserAccount"; import BackBtn from "@/components/BackBtn"; import { usePathname } from "next/navigation"; +import HeaderNav from "@/app/(presentation-generator)/components/HeaderNab"; const Header = () => { const pathname = usePathname(); return ( @@ -23,7 +23,7 @@ const Header = () => {
- +
diff --git a/servers/nextjs/app/globals.css b/servers/nextjs/app/globals.css index 6c02fcac..49e38ed9 100644 --- a/servers/nextjs/app/globals.css +++ b/servers/nextjs/app/globals.css @@ -2,7 +2,6 @@ @tailwind components; @tailwind utilities; -@import '../app/(presentation-generator)/styles/themes.css'; body { font-family: Arial, Helvetica, sans-serif; diff --git a/servers/nextjs/app/layout-preview/README.md b/servers/nextjs/app/layout-preview/README.md deleted file mode 100644 index 75ab0824..00000000 --- a/servers/nextjs/app/layout-preview/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Layout Preview Studio - -A modular, responsive layout preview system for viewing and testing presentation layout components with realistic sample data. - -## ✨ Features - -- **Dynamic Layout Discovery**: Automatically discovers and loads layout components -- **Interactive Navigation**: Easy navigation with quick select grid -- **Beautiful Presentation Display**: Mock browser frame with professional styling -- **Detailed Information Panel**: Layout metadata and sample data inspection -- **Responsive Design**: Mobile-friendly with collapsible sidebar -- **Professional Loading States**: Skeleton loaders and error handling -- **Type Safety**: Full TypeScript support with shared types - -## 🏗️ Architecture - -The system is built with a modular architecture for maintainability and reusability: - -``` -layout-preview/ -├── components/ # Modular UI components -│ ├── LayoutNavigation.tsx # Navigation controls & layout selector -│ ├── LayoutDisplay.tsx # Main layout preview area -│ ├── LayoutInfoPanel.tsx # Information and data structure display -│ └── LoadingStates.tsx # Loading, error, and empty states -├── hooks/ # Custom React hooks -│ └── useLayoutLoader.ts # Layout loading logic and state management -├── utils/ # Utility functions -│ └── sampleDataGenerator.ts # Realistic sample data generation -├── types/ # Shared TypeScript types -│ └── index.ts # Common interfaces and types -├── page.tsx # Main page component -└── README.md # This file -``` - -## 🧩 Components - -### LayoutNavigation -- Previous/Next navigation buttons -- Layout counter and current layout info -- Quick select grid for fast switching -- Responsive design with mobile optimization - -### LayoutDisplay -- Mock browser frame presentation -- Layout rendering with sample data -- Professional shadow and styling effects -- Empty state with helpful messaging - -### LayoutInfoPanel -- Layout metadata display -- Collapsible sample data viewer -- Copy to clipboard functionality -- Position tracking in collection - -### LoadingStates -- Loading spinner with animated dots -- Error state with retry functionality -- Empty state with helpful instructions -- Skeleton loading animations - -## 🎯 Custom Hooks - -### useLayoutLoader -Handles all layout loading logic: -- Fetches layout files from API -- Imports and validates components -- Generates realistic sample data -- Provides retry functionality -- Manages loading and error states - -## 🛠️ Utilities - -### sampleDataGenerator -Intelligent sample data generation: -- Context-aware field value generation -- Support for images, emails, phones, URLs -- Realistic business content -- Zod schema parsing and validation -- Array and object handling - -## 📱 Responsive Design - -The layout preview system is fully responsive: -- **Desktop**: Sidebar navigation with main preview area -- **Tablet**: Collapsible navigation panels -- **Mobile**: Stacked layout with touch-friendly controls - -## 🎨 Styling - -Built with: -- **Tailwind CSS**: Utility-first styling -- **shadcn/ui**: Consistent component library -- **Gradient Backgrounds**: Modern visual appeal -- **Glass Morphism**: Backdrop blur effects -- **Smooth Animations**: Hover and transition effects - -## 🔧 Usage - -The system automatically discovers layout components that export: -```typescript -// Layout component -export default function MyLayout({ data }: { data: any }) { - return
{/* Your layout */}
-} - -// Zod schema for type safety and sample data generation -export const Schema = z.object({ - title: z.string(), - description: z.string(), - // ... other fields -}) -``` - -## 🚀 Getting Started - -1. **Add Layout Components**: Place your layout files in the appropriate directory -2. **Export Requirements**: Ensure each layout exports both a default component and Schema -3. **Navigate**: Use the navigation controls or quick select grid -4. **Inspect**: View layout information and sample data structure -5. **Test**: See how your layouts render with realistic data - -## 🎯 Benefits - -- **Modular Architecture**: Easy to maintain and extend -- **Type Safety**: Full TypeScript support prevents runtime errors -- **Beautiful UI**: Professional design that's pleasant to use -- **Developer Experience**: Quick feedback loop for layout development -- **Responsive**: Works on all device sizes -- **Accessible**: Keyboard navigation and screen reader support - -## 📈 Performance - -- **Lazy Loading**: Components are imported only when needed -- **Optimized Rendering**: Efficient re-renders with React best practices -- **Minimal Bundle**: Modular structure enables tree shaking -- **Caching**: Sample data generation is memoized \ No newline at end of file diff --git a/servers/nextjs/data/presenton-settings.db b/servers/nextjs/data/presenton-settings.db deleted file mode 100644 index 39a091a560437e81a23f718a8112b8430543656d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI$Pj3=I6aesr8ml$5-bfEkw!@`OpzgAzY)MR{utXyLBW$CMX~=YUmTtN*xI1WT z(}QjNBHsKOUOe^#_z^stc=Qd+5>1-4@n-TS?6ULT{CWJ~GIUZzbd0gIRr{-75n@9A3Gs@j%nSGQW0nncx=Id$Al z*qLfuuWfa&-Q2FW57ZadL4J}OMJ#N(ae8OhR(JMWtt97CL2u4DA)|u1T{v6UVPm_w zZK-vuQQdFZYHh#Wws!2U40i15_Fl5wXb215b<()J)hFru+1gS{@dqwDjot=urti{` z;NyNb`Ji1rd6MV)2huYGS2}a3WDe)Q%RV*~Kmim$0Te(16hHwKKmim$fqxLVJg?7Y zmp?r+3~|i*EZ`0e3i2R%P6o8kNQF3~Nbo+%6Nd*pj7a6298u?_7xK}-g?!F6D5Yf> z5Cy)=wt6z=c~04x#%x~%a;cnd>h|C-jHH=Y#Pqsvqgz zbOU}G?_heS4Ehcq#G5v>jm=U+R(ecBQJa+ehUBJ++$=HrKsN!x+K=@;_}ri!|*s4Ec}x{;61(<@*xX_&&KT0F@N8JD5;2W#PVc3 zf=B+uDPW#}QuvTMzJO}AH8UR5?9(3bpA(mcCr~80xQG)S__86P$Gu(8gR{3B2f$jM z$R%K4T-i2@qRA? { - const [displayText, setDisplayText] = useState(""); - const [isCursorVisible, setIsCursorVisible] = useState(true); - const [index, setIndex] = useState(0); - - // Reset when text changes or enabled status changes - useEffect(() => { - if (enabled) { - setDisplayText(""); - setIndex(0); - setIsCursorVisible(true); - } else { - // When disabled, immediately show full text and hide cursor - setDisplayText(text); - setIsCursorVisible(false); - } - }, [text, enabled]); - - // Only run the typing effect when enabled - useEffect(() => { - if (!enabled) return; - - if (index >= text.length) { - setIsCursorVisible(false); - return; - } - - const timeout = setTimeout(() => { - setDisplayText((prev) => prev + text.charAt(index)); - setIndex((prev) => prev + 1); - }, speed); - - return () => clearTimeout(timeout); - }, [index, text, speed, enabled]); - - return { displayText, isCursorVisible }; -}; diff --git a/servers/nextjs/lib/utils.ts b/servers/nextjs/lib/utils.ts index 97033366..ccd54c72 100644 --- a/servers/nextjs/lib/utils.ts +++ b/servers/nextjs/lib/utils.ts @@ -4,66 +4,3 @@ import { twMerge } from 'tailwind-merge' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } - -interface ChartData { - categories: string[]; - series: { - name: string; - data: number[]; - }[]; -} - -export function randomChartGenerator(): ChartData { - const templates = [ - { - categories: ["first Q", "second Q", "third Q", "fourth Q"], - series: [ - { - name: "Revenue 2023", - data: [32500, 47800, 41500, 59000] - }, - { - name: "Revenue 2022", - data: [28000, 35000, 37500, 41000] - } - ] - }, - { - categories: ["Mobile", "Desktop", "Tablet", "Other"], - series: [ - { - name: "Active Users", - data: [85000, 65000, 22000, 95000] - }, - { - name: "Last Month", - data: [72000, 58000, 18000, 85000] - } - ] - }, - { - categories: ["Product A", "Product B", "Product C", "Product D"], - series: [ - { - name: "Sales Volume", - data: [1200, 980, 850, 674] - }, - { - name: "Target", - data: [1000, 800, 900, 700] - } - ] - } - ]; - - return templates[Math.floor(Math.random() * templates.length)]; -} - -export function formatLargeNumber(num: number): string { - if (num >= 1000000) { - return (num / 1000000).toFixed(1) + 'M'; - } else if (num >= 1000) { - return (num / 1000).toFixed(1) + 'K'; - } - return num.toString(); -} \ No newline at end of file diff --git a/servers/nextjs/store/store.ts b/servers/nextjs/store/store.ts index 8a104c3d..680ed2b7 100644 --- a/servers/nextjs/store/store.ts +++ b/servers/nextjs/store/store.ts @@ -1,13 +1,11 @@ import { configureStore } from "@reduxjs/toolkit"; import presentationGenerationReducer from "./slices/presentationGeneration"; -import themeReducer from "@/app/(presentation-generator)/store/themeSlice"; import pptGenUploadReducer from "./slices/presentationGenUpload"; import userConfigReducer from "./slices/userConfig"; export const store = configureStore({ reducer: { presentationGeneration: presentationGenerationReducer, - theme: themeReducer, pptGenUpload: pptGenUploadReducer, userConfig: userConfigReducer, }, diff --git a/servers/nextjs/utils/helpers.ts b/servers/nextjs/utils/helpers.ts deleted file mode 100644 index d103906b..00000000 --- a/servers/nextjs/utils/helpers.ts +++ /dev/null @@ -1,122 +0,0 @@ -export const toDateTime = (secs: number) => { - var t = new Date('1970-01-01T00:30:00Z'); - t.setSeconds(secs); - return t; -}; - -export const calculateTrialEndUnixTimestamp = ( - trialPeriodDays: number | null | undefined -) => { - // Check if trialPeriodDays is null, undefined, or less than 2 days - if ( - trialPeriodDays === null || - trialPeriodDays === undefined || - trialPeriodDays < 2 - ) { - return undefined; - } - - const currentDate = new Date(); // Current date and time - const trialEnd = new Date( - currentDate.getTime() + (trialPeriodDays + 1) * 24 * 60 * 60 * 1000 - ); // Add trial days - return Math.floor(trialEnd.getTime() / 1000); // Convert to Unix timestamp in seconds -}; - -const toastKeyMap: { [key: string]: string[] } = { - status: ['status', 'status_description'], - error: ['error', 'error_description'] -}; - -const getToastRedirect = ( - path: string, - toastType: string, - toastName: string, - toastDescription: string = '', - disableButton: boolean = false, - arbitraryParams: string = '' -): string => { - const [nameKey, descriptionKey] = toastKeyMap[toastType]; - - let redirectPath = `${path}?${nameKey}=${encodeURIComponent(toastName)}`; - - if (toastDescription) { - redirectPath += `&${descriptionKey}=${encodeURIComponent(toastDescription)}`; - } - - if (disableButton) { - redirectPath += `&disable_button=true`; - } - - if (arbitraryParams) { - redirectPath += `&${arbitraryParams}`; - } - - return redirectPath; -}; - -export const getStatusRedirect = ( - path: string, - statusName: string, - statusDescription: string = '', - disableButton: boolean = false, - arbitraryParams: string = '' -) => - getToastRedirect( - path, - 'status', - statusName, - statusDescription, - disableButton, - arbitraryParams - ); - -export const getErrorRedirect = ( - path: string, - errorName: string, - errorDescription: string = '', - disableButton: boolean = false, - arbitraryParams: string = '' -) => - getToastRedirect( - path, - 'error', - errorName, - errorDescription, - disableButton, - arbitraryParams - ); - -export const getMaxLimits = (tier: 'free' | 'standard' | 'premium') => { - const limits = { - free: { - exports: 5, - slides: 8, - aiVideos: 5, - fileSize: 6 * 1024 * 1024, // 6MB in bytes - videoDuration: 5 * 60, // 5 minutes in seconds - hasWatermark: true, - hasAvatar: false - }, - standard: { - exports: 10, - slides: 15, - aiVideos: 10, - fileSize: 10 * 1024 * 1024, // 10MB - videoDuration: 10 * 60, // 10 minutes - hasWatermark: false, - hasAvatar: true - }, - premium: { - exports: 25, - slides: 25, - aiVideos: 25, - fileSize: 25 * 1024 * 1024, // 25MB - videoDuration: 25 * 60, // 25 minutes - hasWatermark: false, - hasAvatar: true - } - }; - - return limits[tier]; -}; From 4b9cabbada7238aa71ba1695c3051f6c4432cddf Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Sat, 19 Jul 2025 23:05:40 +0545 Subject: [PATCH 3/7] feat(nextjs): implements presentation export for all elements except svg, canvas and tables --- package-lock.json | 24 +++ package.json | 5 + servers/fastapi/models/pptx_models.py | 4 + .../services/pptx_presentation_creator.py | 28 +++ .../api/presentation_to_pptx_model/route.ts | 178 +++++++++++++++++- servers/nextjs/package-lock.json | 26 ++- servers/nextjs/package.json | 1 + servers/nextjs/types/element_attibutes.ts | 2 + servers/nextjs/types/pptx_models.ts | 4 + servers/nextjs/utils/pptx_models_utils.ts | 45 ++++- 10 files changed, 295 insertions(+), 22 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..df2d8fbf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "presenton", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "uuid": "^11.1.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..220d763d --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "uuid": "^11.1.0" + } +} diff --git a/servers/fastapi/models/pptx_models.py b/servers/fastapi/models/pptx_models.py index 768d31c1..dd42fb2a 100644 --- a/servers/fastapi/models/pptx_models.py +++ b/servers/fastapi/models/pptx_models.py @@ -61,11 +61,13 @@ class PptxFontModel(BaseModel): class PptxFillModel(BaseModel): color: str + opacity: float = 1.0 class PptxStrokeModel(BaseModel): color: str thickness: float + opacity: float = 1.0 class PptxShadowModel(BaseModel): @@ -85,6 +87,7 @@ class PptxParagraphModel(BaseModel): spacing: Optional[PptxSpacingModel] = None alignment: Optional[PP_ALIGN] = None font: Optional[PptxFontModel] = None + line_height: Optional[float] = None text: Optional[str] = None text_runs: Optional[List[PptxTextRunModel]] = None @@ -141,6 +144,7 @@ class PptxConnectorModel(PptxShapeModel): position: PptxPositionModel thickness: float = 0.5 color: str = "000000" + opacity: float = 1.0 class PptxSlideModel(BaseModel): diff --git a/servers/fastapi/services/pptx_presentation_creator.py b/servers/fastapi/services/pptx_presentation_creator.py index 53bd6fb8..1bbde2a5 100644 --- a/servers/fastapi/services/pptx_presentation_creator.py +++ b/servers/fastapi/services/pptx_presentation_creator.py @@ -10,6 +10,7 @@ from pptx.text.text import _Paragraph, TextFrame, Font, _Run from pptx.opc.constants import RELATIONSHIP_TYPE as RT from lxml.etree import fromstring, tostring from PIL import Image +from pptx.oxml.xmlchemy import OxmlElement from pptx.util import Pt from pptx.dml.color import RGBColor @@ -55,6 +56,13 @@ class PptxPresentationCreator: self._ppt.slide_width = Pt(1280) self._ppt.slide_height = Pt(720) + def get_sub_element(self, parent, tagname, **kwargs): + """Helper method to create XML elements""" + element = OxmlElement(tagname) + element.attrib.update(kwargs) + parent.append(element) + return element + async def fetch_network_assets(self): image_urls = [] models_with_network_asset: List[PptxPictureBoxModel] = [] @@ -158,6 +166,8 @@ class PptxPresentationCreator: ) connector_shape.line.width = Pt(connector_model.thickness) connector_shape.line.color.rgb = RGBColor.from_string(connector_model.color) + # Set line opacity using XML manipulation for better reliability + self.set_line_opacity(connector_shape, connector_model.opacity) def add_picture(self, slide: Slide, picture_model: PptxPictureBoxModel): image_path = picture_model.picture.path @@ -252,6 +262,9 @@ class PptxPresentationCreator: if paragraph_model.spacing: self.apply_spacing_to_paragraph(paragraph, paragraph_model.spacing) + if paragraph_model.line_height: + paragraph.line_spacing = paragraph_model.line_height + if paragraph_model.alignment: paragraph.alignment = paragraph_model.alignment @@ -365,6 +378,7 @@ class PptxPresentationCreator: else: shape.fill.solid() shape.fill.fore_color.rgb = RGBColor.from_string(fill.color) + self.set_fill_opacity(shape.fill, fill.opacity) def apply_stroke_to_shape( self, shape: Shape, stroke: Optional[PptxStrokeModel] = None @@ -375,6 +389,7 @@ class PptxPresentationCreator: shape.line.fill.solid() shape.line.fill.fore_color.rgb = RGBColor.from_string(stroke.color) shape.line.width = Pt(stroke.thickness) + self.set_fill_opacity(shape.line.fill, stroke.opacity) def apply_shadow_to_shape( self, shape: Shape, shadow: Optional[PptxShadowModel] = None @@ -427,6 +442,19 @@ class PptxPresentationCreator: nsmap=nsmap, ) + def set_fill_opacity(self, fill, opacity): + if opacity is None or opacity >= 1.0: + return + + alpha = int((opacity) * 100000) + + try: + ts = fill._xPr.solidFill + sF = ts.get_or_change_to_srgbClr() + self.get_sub_element(sF, "a:alpha", val=str(alpha)) + except Exception as e: + print(f"Could not set fill opacity: {e}") + def get_margined_position( self, position: PptxPositionModel, margin: Optional[PptxSpacingModel] ) -> PptxPositionModel: diff --git a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts index 02da924f..f855ac44 100644 --- a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts +++ b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts @@ -4,6 +4,9 @@ import puppeteer, { Browser, ElementHandle } from "puppeteer"; import { ElementAttributes, SlideAttributesResult } from "@/types/element_attibutes"; import { convertElementAttributesToPptxSlides } from "@/utils/pptx_models_utils"; import { PptxPresentationModel } from "@/types/pptx_models"; +import fs from "fs"; +import path from "path"; +import crypto from "crypto"; // Interface for getAllChildElementsAttributes function arguments interface GetAllChildElementsAttributesArgs { @@ -57,9 +60,13 @@ async function getPresentationId(request: NextRequest) { } async function getSlidesAttributes(slides: ElementHandle[]) { - const slideResults = await Promise.all(slides.map(async (slide) => { - return await getAllChildElementsAttributes({ element: slide }); - })); + const slideResults: SlideAttributesResult[] = []; + //? Can't use Promise.all because of the screenshot + //? taking screenshot with mess up position of elements + for (const slide of slides) { + const result = await getAllChildElementsAttributes({ element: slide }); + slideResults.push(result); + } const elements = slideResults.map(result => result.elements); const backgroundColors = slideResults.map(result => result.backgroundColor); @@ -93,9 +100,10 @@ async function getPresentationPage(browser: Browser, id: string) { page.on('console', (msg) => { const type = msg.type(); const text = msg.text(); + console.log(`${type}: ${text}`); }); - await page.setViewport({ width: 1640, height: 720, deviceScaleFactor: 1 }); + await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 }); await page.goto(`http://localhost/presentation?id=${id}`, { waitUntil: "networkidle0", timeout: 60000, @@ -116,6 +124,42 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = }; }); + // Check if this element is SVG or canvas or table + const tagName = await element.evaluate((el) => el.tagName.toLowerCase()); + + if (tagName === 'svg' || tagName === 'canvas' || tagName === 'table') { + return { + elements: [], + backgroundColor: undefined + }; + // // Take screenshot of SVG/canvas element + // const screenshotPath = await takeElementScreenshot(element); + + // // Get basic attributes for the element + // const attributes = await getElementAttributes(element); + + // // Update image source to point to the screenshot + // if (screenshotPath) { + // attributes.imageSrc = screenshotPath; + // } + + // // Adjust position relative to root + // if (attributes.position && attributes.position.left !== undefined && attributes.position.top !== undefined) { + // attributes.position = { + // left: attributes.position.left - currentRootRect.left, + // top: attributes.position.top - currentRootRect.top, + // width: attributes.position.width, + // height: attributes.position.height, + // }; + // } + + // // Return early without processing children for SVG/canvas elements + // return { + // elements: [attributes], + // backgroundColor: undefined + // }; + } + // Get direct children only (not all descendants) const directChildElementHandles = await element.$$(':scope > *'); @@ -232,7 +276,6 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = } -// Do not edit this function, it is used to get the attributes of an element async function getElementAttributes(element: ElementHandle): Promise { const attributes = await element.evaluate((el: Element) => { @@ -333,6 +376,7 @@ async function getElementAttributes(element: ElementHandle): Promise): Promise): Promise singleLineHeight * 2; // Allow some tolerance + const hasOverflow = htmlEl.scrollHeight > htmlEl.clientHeight; + + const isMultiline = hasExplicitLineBreaks || hasTextWrapping || hasOverflow; + + // Only return line height if text is multiline + if (isMultiline && lineHeight && lineHeight !== 'normal') { + const parsedLineHeight = parseFloat(lineHeight); + if (!isNaN(parsedLineHeight)) { + return parsedLineHeight; + } + } + + return undefined; + } + function parseMargin(computedStyles: CSSStyleDeclaration) { const marginTop = parseFloat(computedStyles.marginTop); const marginBottom = parseFloat(computedStyles.marginBottom); @@ -657,6 +732,8 @@ async function getElementAttributes(element: ElementHandle): Promise): Promise): Promise): Promise { + try { + // Validate environment configuration + const tempDir = process.env.TEMP_DIRECTORY; + if (!tempDir) { + console.warn('TEMP_DIRECTORY environment variable not set, skipping screenshot'); + return undefined; + } + + // Check element visibility and dimensions + const elementInfo = await element.evaluate((el) => { + const rect = el.getBoundingClientRect(); + const styles = window.getComputedStyle(el); + + return { + isVisible: styles.visibility !== 'hidden' && + styles.display !== 'none' && + styles.opacity !== '0', + hasValidDimensions: rect.width > 0 && rect.height > 0, + isInViewport: rect.top < window.innerHeight && + rect.bottom > 0 && + rect.left < window.innerWidth && + rect.right > 0, + dimensions: { + width: rect.width, + height: rect.height, + top: rect.top, + left: rect.left + } + }; + }).catch((error) => { + console.warn('Failed to evaluate element visibility:', error.message); + return { isVisible: false, hasValidDimensions: false, isInViewport: false, dimensions: null }; + }); + + if (!elementInfo.isVisible || !elementInfo.hasValidDimensions) { + console.warn('Element is not visible or has invalid dimensions, skipping screenshot', { + visible: elementInfo.isVisible, + validDimensions: elementInfo.hasValidDimensions, + dimensions: elementInfo.dimensions + }); + return undefined; + } + + // Scroll element into viewport if not visible + if (!elementInfo.isInViewport) { + try { + await element.evaluate((el) => { + el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' }); + }); + + // Wait a brief moment for scrolling to complete + await new Promise(resolve => setTimeout(resolve, 200)); + + console.log('Element scrolled into viewport for screenshot'); + } catch (scrollError: any) { + console.warn('Failed to scroll element into view:', scrollError.message); + // Continue with screenshot attempt even if scrolling fails + } + } + + // Ensure screenshots directory exists + const screenshotsDir = path.join(tempDir, 'screenshots'); + if (!fs.existsSync(screenshotsDir)) { + fs.mkdirSync(screenshotsDir, { recursive: true }); + } + + // Generate unique filename + const uuid = crypto.randomUUID(); + const filename = `${uuid}.png`; + const filePath = path.join(screenshotsDir, filename); + + // Take screenshot of the element + await element.screenshot({ + path: filePath as `${string}.png`, + type: 'png', + omitBackground: false + }); + + console.log(`Screenshot saved: ${filePath}`); + return filePath; + + } catch (error) { + console.error('Error taking element screenshot:', error); + return undefined; + } +} \ No newline at end of file diff --git a/servers/nextjs/package-lock.json b/servers/nextjs/package-lock.json index 480621bc..11b32e27 100644 --- a/servers/nextjs/package-lock.json +++ b/servers/nextjs/package-lock.json @@ -64,6 +64,7 @@ "@types/puppeteer": "^5.4.7", "@types/react": "^18", "@types/react-dom": "^18", + "@types/uuid": "^10.0.0", "cypress": "^14.3.3", "tailwindcss": "^3.4.1", "typescript": "^5" @@ -152,6 +153,15 @@ "node": ">= 6" } }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -2386,6 +2396,12 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -7381,16 +7397,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/servers/nextjs/package.json b/servers/nextjs/package.json index 27bc483e..80516abf 100644 --- a/servers/nextjs/package.json +++ b/servers/nextjs/package.json @@ -67,6 +67,7 @@ "@types/puppeteer": "^5.4.7", "@types/react": "^18", "@types/react-dom": "^18", + "@types/uuid": "^10.0.0", "cypress": "^14.3.3", "tailwindcss": "^3.4.1", "typescript": "^5" diff --git a/servers/nextjs/types/element_attibutes.ts b/servers/nextjs/types/element_attibutes.ts index 89e22a7e..df113fc1 100644 --- a/servers/nextjs/types/element_attibutes.ts +++ b/servers/nextjs/types/element_attibutes.ts @@ -10,6 +10,7 @@ export interface ElementAttributes { border?: { color?: string; width?: number; + opacity?: number; }; shadow?: { offset?: [number, number]; @@ -47,6 +48,7 @@ export interface ElementAttributes { }; zIndex?: number; textAlign?: 'left' | 'center' | 'right' | 'justify'; + lineHeight?: number; borderRadius?: number[]; imageSrc?: string; objectFit?: 'contain' | 'cover' | 'fill'; diff --git a/servers/nextjs/types/pptx_models.ts b/servers/nextjs/types/pptx_models.ts index 8b61701b..caefb0a4 100644 --- a/servers/nextjs/types/pptx_models.ts +++ b/servers/nextjs/types/pptx_models.ts @@ -236,11 +236,13 @@ export interface PptxFontModel { export interface PptxFillModel { color: string; + opacity: number; } export interface PptxStrokeModel { color: string; thickness: number; + opacity: number; } export interface PptxShadowModel { @@ -260,6 +262,7 @@ export interface PptxParagraphModel { spacing?: PptxSpacingModel; alignment?: PptxAlignment; font?: PptxFontModel; + line_height?: number; text?: string; text_runs?: PptxTextRunModel[]; } @@ -313,6 +316,7 @@ export interface PptxConnectorModel extends PptxShapeModel { position: PptxPositionModel; thickness: number; color: string; + opacity: number; } export interface PptxSlideModel { diff --git a/servers/nextjs/utils/pptx_models_utils.ts b/servers/nextjs/utils/pptx_models_utils.ts index 9186b2b9..35466f86 100644 --- a/servers/nextjs/utils/pptx_models_utils.ts +++ b/servers/nextjs/utils/pptx_models_utils.ts @@ -40,6 +40,28 @@ function convertTextAlignToPptxAlignment(textAlign?: string): PptxAlignment | un } } +/** + * Converts line height from pixels to relative format (e.g., 1.5) + * If lineHeight is already a relative number (less than 10), return as is + * Otherwise, convert from pixels to relative by dividing by font size + */ +function convertLineHeightToRelative(lineHeight?: number, fontSize?: number): number | undefined { + if (!lineHeight) return undefined; + + let calculatedLineHeight = 1.2; + // If lineHeight is already a relative number (typically between 1.0 and 3.0) + if (lineHeight < 10) { + calculatedLineHeight = lineHeight; + } + + // If we have font size, convert from pixels to relative + if (fontSize && fontSize > 0) { + calculatedLineHeight = Math.round((lineHeight / fontSize) * 100) / 100; // Round to 2 decimal places + } + + return calculatedLineHeight - 0.4 + (fontSize ?? 16) * 0.004; +} + /** * Converts ElementAttributes[][] to PptxSlideModel[] * Each inner array represents elements on a slide @@ -60,7 +82,8 @@ export function convertElementAttributesToPptxSlides( // Add background color if available if (backgroundColors && backgroundColors[index]) { slide.background = { - color: backgroundColors[index]! + color: backgroundColors[index]!, + opacity: 1.0 }; } @@ -80,7 +103,7 @@ function convertElementToPptxShape( } // Check if it's an image element - if (element.tagName === 'img' || (element.className && typeof element.className === 'string' && element.className.includes('image'))) { + if (element.tagName === 'img' || (element.className && typeof element.className === 'string' && element.className.includes('image')) || element.imageSrc) { return convertToPictureBox(element); } @@ -109,7 +132,10 @@ function convertToTextBox(element: ElementAttributes): PptxTextBoxModel { height: Math.round(element.position?.height ?? 0) }; - const fill: PptxFillModel | undefined = element.background?.color ? { color: element.background.color } : undefined; + const fill: PptxFillModel | undefined = element.background?.color ? { + color: element.background.color, + opacity: element.background.opacity ?? 1.0 + } : undefined; const font: PptxFontModel | undefined = element.font ? { name: element.font.name ?? "Inter", @@ -123,6 +149,7 @@ function convertToTextBox(element: ElementAttributes): PptxTextBoxModel { spacing: undefined, alignment: convertTextAlignToPptxAlignment(element.textAlign), font, + line_height: convertLineHeightToRelative(element.lineHeight, element.font?.size), text: element.innerText }; @@ -145,11 +172,15 @@ function convertToAutoShapeBox(element: ElementAttributes): PptxAutoShapeBoxMode width: Math.round(element.position?.width ?? 0), height: Math.round(element.position?.height ?? 0) }; - const fill: PptxFillModel | undefined = element.background?.color ? { color: element.background.color } : undefined; + const fill: PptxFillModel | undefined = element.background?.color ? { + color: element.background.color, + opacity: element.background.opacity ?? 1.0 + } : undefined; const stroke: PptxStrokeModel | undefined = element.border?.color ? { color: element.border.color, - thickness: element.border.width ?? 1 // float - keep as number + thickness: element.border.width ?? 1, // float - keep as number + opacity: element.border.opacity ?? 1.0 } : undefined; const shadow: PptxShadowModel | undefined = element.shadow?.color ? { @@ -171,6 +202,7 @@ function convertToAutoShapeBox(element: ElementAttributes): PptxAutoShapeBoxMode italic: element.font.italic ?? false, color: element.font.color ?? "000000" } : undefined, + line_height: convertLineHeightToRelative(element.lineHeight, element.font?.size), text: element.innerText }] : undefined; @@ -235,6 +267,7 @@ function convertToConnector(element: ElementAttributes): PptxConnectorModel { type: PptxConnectorType.STRAIGHT, // Default to straight connector position, thickness: element.border?.width ?? 0.5, // float - keep as number - color: element.border?.color || element.background?.color || '000000' + color: element.border?.color || element.background?.color || '000000', + opacity: element.border?.opacity ?? 1.0 }; } From e04fdc55582b19862d1a38b7dbdcb5829201f067 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Sun, 20 Jul 2025 00:38:24 +0545 Subject: [PATCH 4/7] refactor(Nextjs): Remove toast add sonner with different varient & colors --- .../components/SlideFooter.tsx | 32 +-- .../context/LayoutContext.tsx | 8 +- .../components/DocumentPreviewPage.tsx | 14 +- .../outline/hooks/useOutlineStreaming.ts | 20 +- .../hooks/usePresentationGeneration.ts | 14 +- .../pdf-maker/PdfMakerPage.tsx | 8 +- .../presentation/components/Header.tsx | 10 +- .../presentation/components/SlideContent.tsx | 19 +- .../presentation/hooks/useAutoSave.tsx | 4 +- .../presentation/hooks/usePresentationData.ts | 16 +- .../hooks/usePresentationStreaming.ts | 12 +- .../upload/components/SupportingDoc.tsx | 17 +- .../upload/components/ToastTesting.tsx | 115 +++++++++++ .../upload/components/UploadPage.tsx | 20 +- .../(presentation-generator)/utils/others.ts | 160 --------------- .../dashboard/components/PresentationCard.tsx | 16 +- .../hooks/useGroupLayoutLoader.ts | 14 +- .../layout-preview/hooks/useLayoutLoader.ts | 18 +- servers/nextjs/app/layout.tsx | 6 +- servers/nextjs/app/settings/SettingPage.tsx | 61 +++--- servers/nextjs/app/settings/page.tsx | 6 + servers/nextjs/components/Home.tsx | 37 ++-- servers/nextjs/components/ui/sonner.tsx | 2 + servers/nextjs/components/ui/toast.tsx | 129 ------------ servers/nextjs/components/ui/toaster.tsx | 35 ---- servers/nextjs/hooks/use-toast.ts | 194 ------------------ 26 files changed, 241 insertions(+), 746 deletions(-) create mode 100644 servers/nextjs/app/(presentation-generator)/upload/components/ToastTesting.tsx delete mode 100644 servers/nextjs/components/ui/toast.tsx delete mode 100644 servers/nextjs/components/ui/toaster.tsx delete mode 100644 servers/nextjs/hooks/use-toast.ts diff --git a/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx b/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx index 0ae124f3..6905aaca 100644 --- a/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx @@ -18,12 +18,12 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import React, { useRef, useState, useEffect } from "react"; import { Camera, Loader2, Plus } from "lucide-react"; -import { toast } from "@/hooks/use-toast"; import { useSelector } from "react-redux"; import { RootState } from "@/store/store"; import { getStaticFileUrl, isDarkColor } from "../../utils/others"; import { defaultFooterProperties, useFooterContext } from "../../context/footerContext"; import { FooterProperties } from "../../services/footerService"; +import { toast } from "sonner"; const SlideFooter: React.FC = () => { const [showEditor, setShowEditor] = useState(false); @@ -43,17 +43,13 @@ const SlideFooter: React.FC = () => { const handleSave = async () => { await saveFooterProperties(footerProperties); setIsPropertyChanged(false); - toast({ - title: "Footer properties saved successfully", - }); + toast.success("Footer properties saved successfully"); }; const handleReset = async () => { await resetFooterProperties(); setFooterProperties(defaultFooterProperties); - toast({ - title: "Footer properties reset to default", - }); + toast.success("Footer properties reset to default"); }; const updateProperty = (path: string, value: any): void => { @@ -186,9 +182,7 @@ const SlideFooter: React.FC = () => { if (!file) return; if (!file.type.startsWith("image/")) { - toast({ - title: "Please Upload An Image File", - }); + toast.error("Please Upload An Image File"); return; } @@ -208,10 +202,7 @@ const SlideFooter: React.FC = () => { })); } catch (error) { console.error("Error converting image:", error); - toast({ - title: "Error uploading image", - variant: "destructive", - }); + toast.error("Error uploading image"); } finally { setIsUploading({ ...isUploading, white: false }); } @@ -225,9 +216,7 @@ const SlideFooter: React.FC = () => { if (!file) return; if (!file.type.startsWith("image/")) { - toast({ - title: "Please Upload An Image File", - }); + toast.error("Please Upload An Image File"); return; } @@ -247,10 +236,7 @@ const SlideFooter: React.FC = () => { })); } catch (error) { console.error("Error converting image:", error); - toast({ - title: "Error uploading image", - variant: "destructive", - }); + toast.error("Error uploading image"); } finally { setIsUploading({ ...isUploading, dark: false }); } @@ -277,10 +263,8 @@ const SlideFooter: React.FC = () => { const handleSheetClose = () => { if (isPropertyChanged) { - toast({ - title: "Unsaved Changes", + toast.error("Unsaved Changes", { description: "Please save changes before closing the editor", - variant: "destructive", }); return; } diff --git a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx index 77550392..20f2c292 100644 --- a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx +++ b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx @@ -1,7 +1,7 @@ "use client"; import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; import dynamic from 'next/dynamic'; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import * as z from 'zod'; export interface LayoutInfo { @@ -98,8 +98,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children }) const module = await import(`@/presentation-layouts/${groupData.groupName}/${file}`); if (!module.default) { - toast({ - title: `${file} has no default export`, + toast.error(`${file} has no default export`, { description: 'Please ensure the layout file exports a default component', }); console.warn(`❌ ${file} has no default export`); @@ -107,8 +106,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children }) } if (!module.Schema) { - toast({ - title: `${file} has no Schema export`, + toast.error(`${file} has no Schema export`, { description: 'Please ensure the layout file exports a Schema', }); console.warn(`❌ ${file} has no Schema export`); diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx index 8357ea4f..7b106d23 100644 --- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx @@ -22,7 +22,7 @@ import { useDispatch, useSelector } from "react-redux"; import { useRouter } from "next/navigation"; import { RootState } from "@/store/store"; import { Button } from "@/components/ui/button"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import MarkdownRenderer from "./MarkdownRenderer"; import { getIconFromFile } from "../../utils/others"; import { ChevronRight, PanelRightOpen, X } from "lucide-react"; @@ -122,11 +122,7 @@ const DocumentsPreviewPage: React.FC = () => { }); } catch (error) { console.error('Error reading files:', error); - toast({ - title: "Error", - description: "Failed to read document content", - variant: "destructive", - }); + toast.error("Failed to read document content"); } setDownloadingDocuments([]); } @@ -155,11 +151,7 @@ const DocumentsPreviewPage: React.FC = () => { router.push("/outline"); } catch (error) { console.error("Error in presentation creation:", error); - toast({ - title: "Error in presentation creation.", - description: "Please try again.", - variant: "destructive", - }); + toast.error("Error in presentation creation. Please try again."); setShowLoading({ message: "Error in presentation creation.", show: true, diff --git a/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts b/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts index bd84a275..24a81604 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { setOutlines, SlideOutline } from "@/store/slices/presentationGeneration"; import { jsonrepair } from "jsonrepair"; import { StreamState } from "../types/index"; @@ -56,11 +56,7 @@ export const useOutlineStreaming = (presentationId: string | null) => { eventSource.close(); } catch (error) { console.error("Error parsing accumulated chunks:", error); - toast({ - title: "Error", - description: "Failed to parse presentation data", - variant: "destructive", - }); + toast.error("Failed to parse presentation data"); eventSource.close(); } accumulatedChunks = ""; @@ -76,19 +72,11 @@ export const useOutlineStreaming = (presentationId: string | null) => { eventSource.onerror = () => { setStreamState({ isStreaming: false, isLoading: false }); eventSource.close(); - toast({ - title: "Connection Error", - description: "Failed to connect to the server. Please try again.", - variant: "destructive", - }); + toast.error("Failed to connect to the server. Please try again."); }; } catch (error) { setStreamState({ isStreaming: false, isLoading: false }); - toast({ - title: "Error", - description: "Failed to initialize connection", - variant: "destructive", - }); + toast.error("Failed to initialize connection"); } }; diff --git a/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts b/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts index 79a492ed..b8ee619e 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts @@ -1,7 +1,7 @@ import { useState, useCallback } from "react"; import { useDispatch } from "react-redux"; import { useRouter } from "next/navigation"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { clearPresentationData, setPresentationData, SlideOutline } from "@/store/slices/presentationGeneration"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import { useLayout } from "../../context/LayoutContext"; @@ -27,19 +27,15 @@ export const usePresentationGeneration = ( const validateInputs = useCallback(() => { if (!outlines || outlines.length === 0) { - toast({ - title: "No Outlines", + toast.error("No Outlines", { description: "Please wait for outlines to load before generating presentation", - variant: "destructive", }); return false; } if (!selectedLayoutGroup) { - toast({ - title: "Select Layout Group", + toast.error("Select Layout Group", { description: "Please select a layout group before generating presentation", - variant: "destructive", }); return false; } @@ -101,10 +97,8 @@ export const usePresentationGeneration = ( } } catch (error) { console.error("Error in data generation", error); - toast({ - title: "Generation Error", + toast.error("Generation Error", { description: "Failed to generate presentation. Please try again.", - variant: "destructive", }); } finally { setLoadingState(DEFAULT_LOADING_STATE); diff --git a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx index e1cb7b4d..9c63e419 100644 --- a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx @@ -8,7 +8,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { DashboardApi } from "@/app/dashboard/api/dashboard"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; @@ -40,11 +40,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { setContentLoading(false); } catch (error) { setError(true); - toast({ - title: "Error", - description: "Failed to load presentation", - variant: "destructive", - }); + toast.error("Failed to load presentation"); console.error("Error fetching user slides:", error); setContentLoading(false); } diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 64d3a9b7..c198445b 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -20,7 +20,7 @@ import { useDispatch, useSelector } from "react-redux"; import Link from "next/link"; import { RootState } from "@/store/store"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import Modal from "./Modal"; @@ -75,11 +75,9 @@ const Header = ({ } catch (error) { console.error("Export failed:", error); setShowLoader(false); - toast({ - title: "Having trouble exporting!", + toast.error("Having trouble exporting!", { description: "We are having trouble exporting your presentation. Please try again.", - variant: "default", }); } finally { setShowLoader(false); @@ -111,11 +109,9 @@ const Header = ({ } catch (err) { console.error(err); - toast({ - title: "Having trouble exporting!", + toast.error("Having trouble exporting!", { description: "We are having trouble exporting your presentation. Please try again.", - variant: "default", }); } finally { setShowLoader(false); diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index a260734a..beda2448 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/popover"; import { Textarea } from "@/components/ui/textarea"; import { SendHorizontal } from "lucide-react"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import ToolTip from "@/components/ToolTip"; import { RootState } from "@/store/store"; @@ -45,11 +45,7 @@ const SlideContent = ({ ) as HTMLInputElement; const value = element?.value; if (!value?.trim()) { - toast({ - title: "Error", - description: "Please enter a prompt before submitting", - variant: "destructive", - }); + toast.error("Please enter a prompt before submitting"); return; } setIsUpdating(true); @@ -64,18 +60,11 @@ const SlideContent = ({ if (response) { console.log("response", response); dispatch(updateSlide({ index: slide.index, slide: response })); - toast({ - title: "Success", - description: "Slide updated successfully", - }); + toast.success("Slide updated successfully"); } } catch (error) { console.error("Error updating slide:", error); - toast({ - title: "Error", - description: "Failed to update slide. Please try again.", - variant: "destructive", - }); + toast.error("Failed to update slide. Please try again."); } finally { setIsUpdating(false); } diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx b/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx index 596e2405..963b17cc 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx @@ -13,7 +13,7 @@ export const useAutoSave = ({ debounceMs = 2000, enabled = true, }: UseAutoSaveOptions = {}) => { - const { presentationData } = useSelector( + const { presentationData, isStreaming, isLoading } = useSelector( (state: RootState) => state.presentationGeneration ); @@ -61,7 +61,7 @@ export const useAutoSave = ({ // Effect to trigger auto-save when presentation data changes useEffect(() => { - if (!enabled || !presentationData) return; + if (!enabled || !presentationData || isStreaming || isLoading) return; // Trigger debounced save debouncedSave(presentationData); diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts index 0369c342..90d60c2c 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts @@ -1,6 +1,6 @@ -import { useCallback } from "react"; +import { useCallback, useEffect } from 'react'; import { useDispatch } from "react-redux"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { DashboardApi } from "@/app/dashboard/api/dashboard"; import { setPresentationData } from "@/store/slices/presentationGeneration"; @@ -11,7 +11,6 @@ export const usePresentationData = ( ) => { const dispatch = useDispatch(); - const fetchUserSlides = useCallback(async () => { try { const data = await DashboardApi.getPresentation(presentationId); @@ -21,20 +20,17 @@ export const usePresentationData = ( } } catch (error) { setError(true); - toast({ - title: "Error", - description: "Failed to load presentation", - variant: "destructive", - }); + toast.error("Failed to load presentation"); console.error("Error fetching user slides:", error); setLoading(false); } }, [presentationId, dispatch, setLoading, setError]); - + useEffect(() => { + fetchUserSlides(); + }, [fetchUserSlides]); return { fetchUserSlides, - }; }; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts index 66b7c1b1..78ccea88 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts @@ -1,7 +1,8 @@ import { useEffect, useRef } from "react"; -import { useDispatch } from "react-redux"; -import { setPresentationData, setStreaming } from "@/store/slices/presentationGeneration"; +import { useDispatch, useSelector } from "react-redux"; +import { clearPresentationData, setPresentationData, setStreaming } from "@/store/slices/presentationGeneration"; import { jsonrepair } from "jsonrepair"; +import { RootState } from "@/store/store"; export const usePresentationStreaming = ( presentationId: string, @@ -10,6 +11,8 @@ export const usePresentationStreaming = ( setError: (error: boolean) => void, fetchUserSlides: () => void ) => { + const { presentationData } = useSelector((state: RootState) => state.presentationGeneration); + const dispatch = useDispatch(); const previousSlidesLength = useRef(0); @@ -19,6 +22,7 @@ export const usePresentationStreaming = ( const initializeStream = async () => { dispatch(setStreaming(true)); + dispatch(clearPresentationData()); eventSource = new EventSource( `/api/v1/ppt/presentation/stream?presentation_id=${presentationId}` @@ -98,7 +102,9 @@ export const usePresentationStreaming = ( if (stream) { initializeStream(); } else { - fetchUserSlides(); + if(!presentationData || presentationData.slides.length === 0){ + fetchUserSlides(); + } } return () => { diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx index 90a1beb1..72d0525f 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/SupportingDoc.tsx @@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react' import { File, X, Upload } from 'lucide-react' -import { useToast } from '@/hooks/use-toast' +import { toast } from 'sonner' import { cn } from '@/lib/utils' interface FileWithId extends File { @@ -17,7 +17,6 @@ interface SupportingDocProps { const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => { const [isDragging, setIsDragging] = useState(false) const fileInputRef = useRef(null) - const { toast } = useToast() // Convert Files to FileWithId with proper type checking const filesWithIds: FileWithId[] = files.map(file => { @@ -57,19 +56,15 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => { const invalidFiles = droppedFiles.filter(file => !validTypes.includes(file.type)); if (invalidFiles.length > 0) { - toast({ - title: 'Invalid file type', + toast.error('Invalid file type', { description: 'Please upload only PDF, TXT, PPTX, or DOCX files', - variant: 'destructive', }); return; } if (hasPdf && droppedFiles.some(file => file.type === 'application/pdf')) { - toast({ - title: 'Multiple PDF files are not allowed', + toast.error('Multiple PDF files are not allowed', { description: 'Please select only one PDF file', - variant: 'destructive', }); return; } @@ -82,8 +77,7 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => { const updatedFiles = [...files, ...validFiles] onFilesChange(updatedFiles) - toast({ - title: 'Files selected', + toast.success('Files selected', { description: `${validFiles.length} file(s) have been added`, }) } @@ -102,8 +96,7 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => { const updatedFiles = [...files, ...validFiles] onFilesChange(updatedFiles) - toast({ - title: 'Files selected', + toast.success('Files selected', { description: `${validFiles.length} file(s) have been added`, }) } diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/ToastTesting.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/ToastTesting.tsx new file mode 100644 index 00000000..e21ece2b --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/upload/components/ToastTesting.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import { Button } from '@/components/ui/button' +import { toast } from 'sonner' + +const ToastTesting = () => { + return ( +
+

Toast Testing - All Variants

+ +
+ {/* Success Toast */} + + + {/* Error Toast */} + + + {/* Info Toast */} + + + {/* Warning Toast */} + + + {/* Loading Toast */} + + + + + {/* Promise Toast */} + + + + + + + + + + + +
+ + +
+ ) +} + +export default ToastTesting diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index bd513217..52efb4ea 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -20,12 +20,12 @@ import { LanguageType, PresentationConfig } from "../type"; import SupportingDoc from "./SupportingDoc"; import { Button } from "@/components/ui/button"; import { ChevronRight } from "lucide-react"; -import { useToast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import { OverlayLoader } from "@/components/ui/overlay-loader"; import Wrapper from "@/components/Wrapper"; import { setPptGenUploadState } from "@/store/slices/presentationGenUpload"; -import { useLayout } from "../../context/LayoutContext"; +import ToastTesting from "./ToastTesting"; // Types for loading state interface LoadingState { @@ -39,7 +39,6 @@ interface LoadingState { const UploadPage = () => { const router = useRouter(); const dispatch = useDispatch(); - const { toast } = useToast(); // State management const [files, setFiles] = useState([]); @@ -72,18 +71,12 @@ const UploadPage = () => { */ const validateConfiguration = (): boolean => { if (!config.language || !config.slides) { - toast({ - title: "Please select number of Slides & Language", - variant: "destructive", - }); + toast.error("Please select number of Slides & Language"); return false; } if (!config.prompt.trim() && files.length === 0) { - toast({ - title: "No Prompt or Document Provided", - variant: "destructive", - }); + toast.error("No Prompt or Document Provided"); return false; } return true; @@ -177,10 +170,8 @@ const UploadPage = () => { duration: 0, showProgress: false, }); - toast({ - title: "Error", + toast.error("Error", { description: "Failed to generate presentation. Please try again.", - variant: "destructive", }); }; @@ -200,6 +191,7 @@ const UploadPage = () => { onConfigChange={handleConfigChange} />
+
{ - if (!fontName) { - return { link: '', name: '' }; - } - if (fontName.includes('instrument')) { - return { link: 'https://fonts.google.com/specimen/Instrument+Sans', name: 'Instrument Sans' } - } - if (fontName.includes('fraunces')) { - return { link: 'https://fonts.google.com/specimen/Fraunces', name: 'Fraunces' } - } - if (fontName.includes('montserrat')) { - return { link: 'https://fonts.google.com/specimen/Montserrat', name: 'Montserrat' } - } - if (fontName.includes('inria-serif')) { - return { link: 'https://fonts.google.com/specimen/Inria+Serif', name: 'Inria Serif' } - } - if (fontName.includes('inter')) { - return { link: 'https://fonts.google.com/specimen/Inter', name: 'Inter' } - } - else { - return { link: '', name: '' }; - } -} -export const numberTranslations: any = { - // Key languages - "English (English)": ["01", "02", "03", "04", "05"], - "English(English)": ["01", "02", "03", "04", "05"], - English: ["01", "02", "03", "04", "05"], - "Spanish (Español)": ["01", "02", "03", "04", "05"], - "French (Français)": ["01", "02", "03", "04", "05"], - "German (Deutsch)": ["01", "02", "03", "04", "05"], - "Portuguese (Português)": ["01", "02", "03", "04", "05"], - "Italian (Italiano)": ["01", "02", "03", "04", "05"], - "Dutch (Nederlands)": ["01", "02", "03", "04", "05"], - "Russian (Русский)": ["01", "02", "03", "04", "05"], - "Chinese (Simplified & Traditional - 中文, 汉语/漢語)": [ - "一", - "二", - "三", - "四", - "五", - ], - "Japanese (日本語)": ["一", "二", "三", "四", "五"], - "Korean (한국어)": ["일", "이", "삼", "사", "오"], - "Arabic (العربية)": ["١", "٢", "٣", "٤", "٥"], - "Hindi (हिन्दी)": ["०१", "०२", "०३", "०४", "०५"], - "Bengali (বাংলা)": ["০১", "০২", "০৩", "০৪", "০৫"], - - // European Languages - "Polish (Polski)": ["01", "02", "03", "04", "05"], - "Czech (Čeština)": ["01", "02", "03", "04", "05"], - "Slovak (Slovenčina)": ["01", "02", "03", "04", "05"], - "Hungarian (Magyar)": ["01", "02", "03", "04", "05"], - "Romanian (Română)": ["01", "02", "03", "04", "05"], - "Bulgarian (Български)": ["01", "02", "03", "04", "05"], - "Greek (Ελληνικά)": ["α΄", "β΄", "γ΄", "δ΄", "ε΄"], - "Serbian (Српски)": ["01", "02", "03", "04", "05"], - "Croatian (Hrvatski)": ["01", "02", "03", "04", "05"], - "Bosnian (Bosanski)": ["01", "02", "03", "04", "05"], - "Slovenian (Slovenščina)": ["01", "02", "03", "04", "05"], - "Finnish (Suomi)": ["01", "02", "03", "04", "05"], - "Swedish (Svenska)": ["01", "02", "03", "04", "05"], - "Danish (Dansk)": ["01", "02", "03", "04", "05"], - "Norwegian (Norsk)": ["01", "02", "03", "04", "05"], - "Icelandic (Íslenska)": ["01", "02", "03", "04", "05"], - "Lithuanian (Lietuvių)": ["01", "02", "03", "04", "05"], - "Latvian (Latviešu)": ["01", "02", "03", "04", "05"], - "Estonian (Eesti)": ["01", "02", "03", "04", "05"], - "Maltese (Malti)": ["01", "02", "03", "04", "05"], - "Welsh (Cymraeg)": ["01", "02", "03", "04", "05"], - "Irish (Gaeilge)": ["01", "02", "03", "04", "05"], - "Scottish Gaelic (Gàidhlig)": ["01", "02", "03", "04", "05"], - - // Middle Eastern and Central Asian Languages - "Hebrew (עברית)": ["א׳", "ב׳", "ג׳", "ד׳", "ה׳"], - "Persian/Farsi (فارسی)": ["۱", "۲", "۳", "۴", "۵"], - "Turkish (Türkçe)": ["01", "02", "03", "04", "05"], - "Kurdish (Kurdî / کوردی)": ["١", "٢", "٣", "٤", "٥"], - "Pashto (پښتو)": ["١", "٢", "٣", "٤", "٥"], - "Dari (دری)": ["١", "٢", "٣", "٤", "٥"], - "Uzbek (Oʻzbek)": ["01", "02", "03", "04", "05"], - "Kazakh (Қазақша)": ["01", "02", "03", "04", "05"], - "Tajik (Тоҷикӣ)": ["01", "02", "03", "04", "05"], - "Turkmen (Türkmençe)": ["01", "02", "03", "04", "05"], - "Azerbaijani (Azərbaycan dili)": ["01", "02", "03", "04", "05"], - - // South Asian Languages - "Urdu (اردو)": ["١", "٢", "٣", "٤", "٥"], - "Tamil (தமிழ்)": ["௧", "௨", "௩", "௪", "௫"], - "Telugu (తెలుగు)": ["౧", "౨", "౩", "౪", "౫"], - "Marathi (मराठी)": ["०१", "०२", "०३", "०४", "०५"], - "Punjabi (ਪੰਜਾਬੀ / پنجابی)": ["੦੧", "੦੨", "੦੩", "੦੪", "੦੫"], - "Gujarati (ગુજરાતી)": ["૦૧", "૦૨", "૦૩", "૦૪", "૦૫"], - "Malayalam (മലയാളം)": ["൧", "൨", "൩", "൪", "൫"], - "Kannada (ಕನ್ನಡ)": ["೧", "೨", "೩", "೪", "೫"], - "Odia (ଓଡ଼ିଆ)": ["୧", "୨", "୩", "୪", "୫"], - "Sinhala (සිංහල)": ["෧", "෨", "෩", "෪", "෫"], - "Nepali (नेपाली)": ["०१", "०२", "०३", "०४", "०५"], - - // East and Southeast Asian Languages - "Thai (ไทย)": ["๑", "๒", "๓", "๔", "๕"], - "Vietnamese (Tiếng Việt)": ["01", "02", "03", "04", "05"], - "Lao (ລາວ)": ["໑", "໒", "໓", "໔", "໕"], - "Khmer (ភាសាខ្មែរ)": ["១", "២", "៣", "៤", "៥"], - "Burmese (မြန်မာစာ)": ["၁", "၂", "၃", "၄", "၅"], - "Tagalog/Filipino (Tagalog/Filipino)": ["01", "02", "03", "04", "05"], - "Javanese (Basa Jawa)": ["01", "02", "03", "04", "05"], - "Sundanese (Basa Sunda)": ["01", "02", "03", "04", "05"], - "Malay (Bahasa Melayu)": ["01", "02", "03", "04", "05"], - "Mongolian (Монгол)": ["01", "02", "03", "04", "05"], - - // African Languages - "Swahili (Kiswahili)": ["01", "02", "03", "04", "05"], - "Hausa (Hausa)": ["01", "02", "03", "04", "05"], - "Yoruba (Yoruba)": ["01", "02", "03", "04", "05"], - "Igbo (Igbo)": ["01", "02", "03", "04", "05"], - "Amharic (አማርኛ)": ["፩", "፪", "፫", "፬", "፭"], - "Zulu (isiZulu)": ["01", "02", "03", "04", "05"], - "Xhosa (isiXhosa)": ["01", "02", "03", "04", "05"], - "Shona (ChiShona)": ["01", "02", "03", "04", "05"], - "Somali (Soomaaliga)": ["01", "02", "03", "04", "05"], - - // Indigenous and Lesser-Known Languages - "Basque (Euskara)": ["01", "02", "03", "04", "05"], - "Catalan (Català)": ["01", "02", "03", "04", "05"], - "Galician (Galego)": ["01", "02", "03", "04", "05"], - "Quechua (Runasimi)": ["01", "02", "03", "04", "05"], - "Nahuatl (Nāhuatl)": ["01", "02", "03", "04", "05"], - "Hawaiian (ʻŌlelo Hawaiʻi)": ["01", "02", "03", "04", "05"], - "Maori (Te Reo Māori)": ["01", "02", "03", "04", "05"], - "Tahitian (Reo Tahiti)": ["01", "02", "03", "04", "05"], - "Samoan (Gagana Samoa)": ["01", "02", "03", "04", "05"], -}; - -export const ThemeImagePrompt = { - light: - "Classy and modern with a corporate and minimalist touch. Tone is serious yet elegant, using a palette of light, white, and cool gray colors.", - dark: "Luxurious and futuristic with a simple, clean design. Professional yet elegant using a color scheme of dark, black, and high contrast.", - faint_yellow: - "Fresh young creatively vibrant style, utilizing a playful mixture of light colors like orange, salmon, and pastel purple, all set against a warm gradient.", - cream: - "Elegant with a classic and professional look. Subtle and minimalist using a warm palette of cream, beige, and light beige colors.", - royal_blue: - "Playful and creative, bold and loud with a futuristic touch, using a gradient of vibrant colors including blue, purple, and royal blue.", - light_red: - "Fun and organic with a playful and inspirational aesthetic, featuring pastel colors like pink, coral, and orange for a vibrant and warm feel.", - dark_pink: - " Inspirational and creative with a youthful and playful tone, featuring light, pastel colors including blue, pink, and purple, all blending in a vibrant gradient.", - custom: "", -}; export function sanitizeFilename(input: string, replacement = '') { diff --git a/servers/nextjs/app/dashboard/components/PresentationCard.tsx b/servers/nextjs/app/dashboard/components/PresentationCard.tsx index 720bce3f..55294912 100644 --- a/servers/nextjs/app/dashboard/components/PresentationCard.tsx +++ b/servers/nextjs/app/dashboard/components/PresentationCard.tsx @@ -9,7 +9,7 @@ import { PopoverContent, } from "@/components/ui/popover"; import { useRouter } from "next/navigation"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { useGroupLayouts } from "@/app/(presentation-generator)/hooks/useGroupLayouts"; export const PresentationCard = ({ @@ -39,28 +39,20 @@ export const PresentationCard = ({ e.preventDefault(); e.stopPropagation(); - toast({ - title: "Deleting presentation", + toast.loading("Deleting presentation", { description: "Please wait while we delete the presentation", - variant: "default", }); const response = await DashboardApi.deletePresentation(id); if (response) { - toast({ - title: "Presentation deleted", + toast.success("Presentation deleted", { description: "The presentation has been deleted successfully", - variant: "default", }); if (onDeleted) { onDeleted(id); } } else { - toast({ - title: "Error", - description: "Failed to delete presentation", - variant: "destructive", - }); + toast.error("Error deleting presentation"); } }; return ( diff --git a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts index 856f5b54..00a14e2a 100644 --- a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts +++ b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react' import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types' -import { toast } from '@/hooks/use-toast' +import { toast } from 'sonner' interface UseGroupLayoutLoaderReturn { layoutGroup: LayoutGroup | null @@ -42,8 +42,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet const response = await fetch('/api/layouts') if (!response.ok) { - toast({ - title: 'Error loading layouts', + toast.error('Error loading layouts', { description: response.statusText, }) return @@ -75,8 +74,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet const module = await import(`@/presentation-layouts/${targetGroupData.groupName}/${layoutName}`) if (!module.default) { - toast({ - title: `${layoutName} has no default export`, + toast.error(`${layoutName} has no default export`, { description: 'Please ensure the layout file exports a default component', }) console.warn(`${layoutName} has no default export`) @@ -84,8 +82,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet } if (!module.Schema) { - toast({ - title: `${layoutName} is missing required Schema export`, + toast.error(`${layoutName} is missing required Schema export`, { description: 'Please ensure the layout file exports a Schema', }) console.error(`${layoutName} is missing required Schema export`) @@ -139,8 +136,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet } if (groupLayouts.length === 0) { - toast({ - title: 'No valid layouts found', + toast.error('No valid layouts found', { description: `No valid layouts found in "${groupSlug}" group.`, }) setError(`No valid layouts found in "${groupSlug}" group.`) diff --git a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts index 20673e69..3d4e3a2d 100644 --- a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts +++ b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types' -import { toast } from '@/hooks/use-toast' +import { toast } from 'sonner' interface UseLayoutLoaderReturn { layoutGroups: LayoutGroup[] @@ -25,8 +25,7 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => { const response = await fetch('/api/layouts') if (!response.ok) { - toast({ - title: 'Error loading layouts', + toast.error('Error loading layouts', { description: response.statusText, }) return @@ -50,21 +49,16 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => { const module = await import(`@/presentation-layouts/${groupData.groupName}/${layoutName}`) if (!module.default) { - toast({ - title: `${layoutName} has no default export`, + toast.error(`${layoutName} has no default export`, { description: 'Please ensure the layout file exports a default component', - }) console.warn(`${layoutName} has no default export`) continue } if (!module.Schema) { - toast({ - title: `${layoutName} is missing required Schema export`, + toast.error(`${layoutName} is missing required Schema export`, { description: 'Please ensure the layout file exports a Schema', - - }) console.error(`${layoutName} is missing required Schema export`) continue @@ -130,10 +124,8 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => { } if (allLayouts.length === 0) { - toast({ - title: 'No valid layouts found', + toast.error('No valid layouts found', { description: 'Make sure your layout files export both a default component and a Schema.', - }) setError('No valid layouts found. Make sure your layout files export both a default component and a Schema.') } else { diff --git a/servers/nextjs/app/layout.tsx b/servers/nextjs/app/layout.tsx index d8ea12de..a579f8c4 100644 --- a/servers/nextjs/app/layout.tsx +++ b/servers/nextjs/app/layout.tsx @@ -3,9 +3,9 @@ import localFont from "next/font/local"; import { Fraunces, Montserrat, Inria_Serif, Roboto, Instrument_Sans } from "next/font/google"; import "./globals.css"; import { Providers } from "./providers"; -import { Toaster } from "@/components/ui/toaster"; import { FooterProvider } from "./(presentation-generator)/context/footerContext"; import { LayoutProvider } from "./(presentation-generator)/context/LayoutContext"; +import { Toaster } from "sonner"; const fraunces = Fraunces({ subsets: ["latin"], @@ -105,13 +105,11 @@ export default function RootLayout({ - - {children} - + ); diff --git a/servers/nextjs/app/settings/SettingPage.tsx b/servers/nextjs/app/settings/SettingPage.tsx index c7c39f9d..d70d90a7 100644 --- a/servers/nextjs/app/settings/SettingPage.tsx +++ b/servers/nextjs/app/settings/SettingPage.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react"; import Header from "../dashboard/components/Header"; import Wrapper from "@/components/Wrapper"; import { Settings, Key, Loader2, Check, ChevronsUpDown } from "lucide-react"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { RootState } from "@/store/store"; import { useSelector } from "react-redux"; import { handleSaveLLMConfig } from "@/utils/storeHelpers"; @@ -156,22 +156,16 @@ const SettingsPage = () => { setIsLoading(true); await pullOllamaModels(); } - toast({ - title: "Success", - description: "Configuration saved successfully", - }); + toast.success("Configuration saved successfully"); setIsLoading(false); router.back(); } catch (error) { console.error("Error:", error); - toast({ - title: "Error", - description: - error instanceof Error - ? error.message - : "Failed to save configuration", - variant: "destructive", - }); + toast.error( + error instanceof Error + ? error.message + : "Failed to save configuration" + ); setIsLoading(false); } }; @@ -284,23 +278,18 @@ const SettingsPage = () => { const isModelAvailable = data.includes(llmConfig.CUSTOM_MODEL); if (!isModelAvailable) { setLlmConfig({ ...llmConfig, CUSTOM_MODEL: "" }); - toast({ - title: "Model Unavailable", - description: `The selected model "${llmConfig.CUSTOM_MODEL}" is no longer available. Please select a different model.`, - variant: "destructive", - }); + toast.error( + `The selected model "${llmConfig.CUSTOM_MODEL}" is no longer available. Please select a different model.` + ); } } } catch (error) { console.error("Error fetching custom models:", error); // Don't set customModelsChecked to true on error, so the button remains visible setCustomModels([]); - toast({ - title: "Error", - description: - "Failed to fetch available models. Please check your URL and API key.", - variant: "destructive", - }); + toast.error( + "Failed to fetch available models. Please check your URL and API key." + ); } finally { setCustomModelsLoading(false); } @@ -385,15 +374,15 @@ const SettingsPage = () => { key={provider} onClick={() => changeProvider(provider)} className={`relative p-4 rounded-lg border-2 transition-all duration-200 ${llmConfig.LLM === provider - ? "border-blue-500 bg-blue-50" - : "border-gray-200 hover:border-blue-200 hover:bg-gray-50" + ? "border-blue-500 bg-blue-50" + : "border-gray-200 hover:border-blue-200 hover:bg-gray-50" }`} >
{provider === "openai" @@ -770,8 +759,8 @@ const SettingsPage = () => { customModelsLoading || !llmConfig.CUSTOM_LLM_URL } className={`w-full py-2.5 px-4 rounded-lg transition-all duration-200 border-2 font-semibold ${customModelsLoading || !llmConfig.CUSTOM_LLM_URL - ? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500" - : "bg-white border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-2 focus:ring-blue-500/20" + ? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500" + : "bg-white border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-2 focus:ring-blue-500/20" }`} > {customModelsLoading ? ( @@ -804,8 +793,8 @@ const SettingsPage = () => { customModelsLoading || !llmConfig.CUSTOM_LLM_URL } className={`w-full py-2.5 px-4 rounded-lg transition-all duration-200 border-2 font-semibold ${customModelsLoading || !llmConfig.CUSTOM_LLM_URL - ? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500" - : "bg-white border-gray-600 text-gray-600 hover:bg-gray-50 focus:ring-2 focus:ring-gray-500/20" + ? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500" + : "bg-white border-gray-600 text-gray-600 hover:bg-gray-50 focus:ring-2 focus:ring-gray-500/20" }`} > {customModelsLoading ? ( @@ -995,10 +984,10 @@ const SettingsPage = () => { (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) } className={`mt-8 w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${isLoading || - (llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL) || - (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) - ? "bg-gray-400 cursor-not-allowed" - : "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200" + (llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL) || + (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) + ? "bg-gray-400 cursor-not-allowed" + : "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200" } text-white`} > {isLoading ? ( diff --git a/servers/nextjs/app/settings/page.tsx b/servers/nextjs/app/settings/page.tsx index 1aca4ed3..e60eb189 100644 --- a/servers/nextjs/app/settings/page.tsx +++ b/servers/nextjs/app/settings/page.tsx @@ -1,6 +1,12 @@ import React from 'react' import SettingPage from './SettingPage' + +export const metadata = { + title: 'Settings | Presenton', + description: 'Settings page', +} const page = () => { + return ( ) diff --git a/servers/nextjs/components/Home.tsx b/servers/nextjs/components/Home.tsx index 8df8cc13..deeea0a3 100644 --- a/servers/nextjs/components/Home.tsx +++ b/servers/nextjs/components/Home.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "sonner"; import { Info, ExternalLink, @@ -276,22 +276,20 @@ export default function Home() { setIsLoading(true); await pullOllamaModels(); } - toast({ - title: "Success", - description: "Configuration saved successfully", - }); + toast.success("Configuration saved successfully"); setIsLoading(false); router.push("/upload"); } catch (error) { console.error("Error:", error); - toast({ - title: "Error", - description: - error instanceof Error - ? error.message - : "Failed to save configuration", - variant: "destructive", - }); + toast.error( + error instanceof Error + ? error.message + : "Failed to save configuration", + { + description: + "Failed to save configuration", + } + ); setIsLoading(false); } }; @@ -413,12 +411,13 @@ export default function Home() { console.error("Error fetching custom models:", error); // Don't set customModelsChecked to true on error, so the button remains visible setCustomModels([]); - toast({ - title: "Error", - description: - "Failed to fetch available models. Please check your URL and API key.", - variant: "destructive", - }); + toast.error( + "Failed to fetch available models. Please check your URL and API key.", + { + description: + "Failed to fetch available models. Please check your URL and API key.", + } + ); } finally { setCustomModelsLoading(false); } diff --git a/servers/nextjs/components/ui/sonner.tsx b/servers/nextjs/components/ui/sonner.tsx index 452f4d9f..261ee62d 100644 --- a/servers/nextjs/components/ui/sonner.tsx +++ b/servers/nextjs/components/ui/sonner.tsx @@ -12,6 +12,8 @@ const Toaster = ({ ...props }: ToasterProps) => { , - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastViewport.displayName = ToastPrimitives.Viewport.displayName - -const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", - { - variants: { - variant: { - default: "border bg-green-50 text-green-700 ", - success: "border bg-green-50 text-green-700 border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive/90 text-destructive-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Toast = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, ...props }, ref) => { - return ( - - ) -}) -Toast.displayName = ToastPrimitives.Root.displayName - -const ToastAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastAction.displayName = ToastPrimitives.Action.displayName - -const ToastClose = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)) -ToastClose.displayName = ToastPrimitives.Close.displayName - -const ToastTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastTitle.displayName = ToastPrimitives.Title.displayName - -const ToastDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -ToastDescription.displayName = ToastPrimitives.Description.displayName - -type ToastProps = React.ComponentPropsWithoutRef - -type ToastActionElement = React.ReactElement - -export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, -} diff --git a/servers/nextjs/components/ui/toaster.tsx b/servers/nextjs/components/ui/toaster.tsx deleted file mode 100644 index 171beb46..00000000 --- a/servers/nextjs/components/ui/toaster.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client" - -import { useToast } from "@/hooks/use-toast" -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast" - -export function Toaster() { - const { toasts } = useToast() - - return ( - - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && ( - {description} - )} -
- {action} - -
- ) - })} - -
- ) -} diff --git a/servers/nextjs/hooks/use-toast.ts b/servers/nextjs/hooks/use-toast.ts deleted file mode 100644 index 02e111d8..00000000 --- a/servers/nextjs/hooks/use-toast.ts +++ /dev/null @@ -1,194 +0,0 @@ -"use client" - -// Inspired by react-hot-toast library -import * as React from "react" - -import type { - ToastActionElement, - ToastProps, -} from "@/components/ui/toast" - -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 - -type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} - -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const - -let count = 0 - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() -} - -type ActionType = typeof actionTypes - -type Action = - | { - type: ActionType["ADD_TOAST"] - toast: ToasterToast - } - | { - type: ActionType["UPDATE_TOAST"] - toast: Partial - } - | { - type: ActionType["DISMISS_TOAST"] - toastId?: ToasterToast["id"] - } - | { - type: ActionType["REMOVE_TOAST"] - toastId?: ToasterToast["id"] - } - -interface State { - toasts: ToasterToast[] -} - -const toastTimeouts = new Map>() - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) - - toastTimeouts.set(toastId, timeout) -} - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t - ), - } - - case "DISMISS_TOAST": { - const { toastId } = action - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId) - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t - ), - } - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - } - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - } - } -} - -const listeners: Array<(state: State) => void> = [] - -let memoryState: State = { toasts: [] } - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action) - listeners.forEach((listener) => { - listener(memoryState) - }) -} - -type Toast = Omit - -function toast({ ...props }: Toast) { - const id = genId() - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss() - }, - }, - }) - - return { - id: id, - dismiss, - update, - } -} - -function useToast() { - const [state, setState] = React.useState(memoryState) - - React.useEffect(() => { - listeners.push(setState) - return () => { - const index = listeners.indexOf(setState) - if (index > -1) { - listeners.splice(index, 1) - } - } - }, [state]) - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - } -} - -export { useToast, toast } From 433de2d6e9a7d29f6f31e7c5c2db0bed43e1d924 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Sun, 20 Jul 2025 00:48:31 +0545 Subject: [PATCH 5/7] feat(nextjs): adds border radius inheritance logic for child elements --- .../api/presentation_to_pptx_model/route.ts | 111 ++-- servers/nextjs/package-lock.json | 500 ++++++++++++++++++ servers/nextjs/package.json | 2 + 3 files changed, 549 insertions(+), 64 deletions(-) diff --git a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts index f855ac44..eb51b36c 100644 --- a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts +++ b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts @@ -7,6 +7,7 @@ import { PptxPresentationModel } from "@/types/pptx_models"; import fs from "fs"; import path from "path"; import crypto from "crypto"; +import sharp from "sharp"; // Interface for getAllChildElementsAttributes function arguments interface GetAllChildElementsAttributesArgs { @@ -15,6 +16,8 @@ interface GetAllChildElementsAttributesArgs { depth?: number; inheritedFont?: ElementAttributes['font']; inheritedBackground?: ElementAttributes['background']; + inheritedBorderRadius?: number[]; + screenshotsDir: string; } @@ -27,8 +30,19 @@ export async function GET(request: NextRequest) { args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + // Ensure screenshots directory exists + const tempDir = process.env.TEMP_DIRECTORY; + if (!tempDir) { + console.warn('TEMP_DIRECTORY environment variable not set, skipping screenshot'); + return undefined; + } + const screenshotsDir = path.join(tempDir, 'screenshots'); + if (!fs.existsSync(screenshotsDir)) { + fs.mkdirSync(screenshotsDir, { recursive: true }); + } + const slides = await getSlides(browser, id); - const slides_attributes = await getSlidesAttributes(slides); + const slides_attributes = await getSlidesAttributes(slides, screenshotsDir); const slides_pptx_models = convertElementAttributesToPptxSlides(slides_attributes.elements, slides_attributes.backgroundColors); const presentation_pptx_model: PptxPresentationModel = { slides: slides_pptx_models, @@ -59,12 +73,12 @@ async function getPresentationId(request: NextRequest) { return id; } -async function getSlidesAttributes(slides: ElementHandle[]) { +async function getSlidesAttributes(slides: ElementHandle[], screenshotsDir: string) { const slideResults: SlideAttributesResult[] = []; //? Can't use Promise.all because of the screenshot //? taking screenshot with mess up position of elements for (const slide of slides) { - const result = await getAllChildElementsAttributes({ element: slide }); + const result = await getAllChildElementsAttributes({ element: slide, screenshotsDir }); slideResults.push(result); } @@ -112,7 +126,7 @@ async function getPresentationPage(browser: Browser, id: string) { } -async function getAllChildElementsAttributes({ element, rootRect = null, depth = 0, inheritedFont, inheritedBackground }: GetAllChildElementsAttributesArgs): Promise { +async function getAllChildElementsAttributes({ element, rootRect = null, depth = 0, inheritedFont, inheritedBackground, inheritedBorderRadius, screenshotsDir }: GetAllChildElementsAttributesArgs): Promise { // Get rootRect if not provided (first call) const currentRootRect = rootRect || await element.evaluate((el) => { const rect = el.getBoundingClientRect(); @@ -127,16 +141,17 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = // Check if this element is SVG or canvas or table const tagName = await element.evaluate((el) => el.tagName.toLowerCase()); + if (tagName === 'svg' || tagName === 'canvas' || tagName === 'table') { return { elements: [], backgroundColor: undefined }; - // // Take screenshot of SVG/canvas element - // const screenshotPath = await takeElementScreenshot(element); // // Get basic attributes for the element // const attributes = await getElementAttributes(element); + // // Take screenshot of SVG/canvas/table element with accurate colors and opacity + // const screenshotPath = await takeElementScreenshot(element, screenshotsDir); // // Update image source to point to the screenshot // if (screenshotPath) { @@ -153,7 +168,7 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = // }; // } - // // Return early without processing children for SVG/canvas elements + // // Return early without processing children for these elements // return { // elements: [attributes], // backgroundColor: undefined @@ -178,6 +193,10 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = if (inheritedBackground && !attributes.background && attributes.shadow) { attributes.background = inheritedBackground; } + // Apply inherited border radius if element doesn't have it + if (inheritedBorderRadius && !attributes.borderRadius) { + attributes.borderRadius = inheritedBorderRadius; + } // Adjust position relative to root if (attributes.position && attributes.position.left !== undefined && attributes.position.top !== undefined) { @@ -199,6 +218,8 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth = depth: depth + 1, inheritedFont: attributes.font || inheritedFont, inheritedBackground: attributes.background || inheritedBackground, + inheritedBorderRadius: attributes.borderRadius || inheritedBorderRadius, + screenshotsDir, }); allResults.push(...childResults.elements.map(attr => ({ attributes: attr, depth: depth + 1 }))); } @@ -784,83 +805,44 @@ async function getElementAttributes(element: ElementHandle): Promise): Promise { +async function takeElementScreenshot(element: ElementHandle, screenshotsDir: string): Promise { try { - // Validate environment configuration - const tempDir = process.env.TEMP_DIRECTORY; - if (!tempDir) { - console.warn('TEMP_DIRECTORY environment variable not set, skipping screenshot'); - return undefined; - } - // Check element visibility and dimensions const elementInfo = await element.evaluate((el) => { const rect = el.getBoundingClientRect(); const styles = window.getComputedStyle(el); + // Check if element is visible + const isVisible = styles.visibility !== 'hidden' && + styles.display !== 'none' && + styles.opacity !== '0'; + + if (!isVisible || rect.width <= 0 || rect.height <= 0) { + return null; + } + return { - isVisible: styles.visibility !== 'hidden' && - styles.display !== 'none' && - styles.opacity !== '0', - hasValidDimensions: rect.width > 0 && rect.height > 0, - isInViewport: rect.top < window.innerHeight && - rect.bottom > 0 && - rect.left < window.innerWidth && - rect.right > 0, - dimensions: { - width: rect.width, - height: rect.height, - top: rect.top, - left: rect.left - } + width: rect.width, + height: rect.height }; - }).catch((error) => { - console.warn('Failed to evaluate element visibility:', error.message); - return { isVisible: false, hasValidDimensions: false, isInViewport: false, dimensions: null }; }); - if (!elementInfo.isVisible || !elementInfo.hasValidDimensions) { - console.warn('Element is not visible or has invalid dimensions, skipping screenshot', { - visible: elementInfo.isVisible, - validDimensions: elementInfo.hasValidDimensions, - dimensions: elementInfo.dimensions - }); + if (!elementInfo) { + console.warn('Element is not visible or has invalid dimensions, skipping screenshot'); return undefined; } - // Scroll element into viewport if not visible - if (!elementInfo.isInViewport) { - try { - await element.evaluate((el) => { - el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' }); - }); - - // Wait a brief moment for scrolling to complete - await new Promise(resolve => setTimeout(resolve, 200)); - - console.log('Element scrolled into viewport for screenshot'); - } catch (scrollError: any) { - console.warn('Failed to scroll element into view:', scrollError.message); - // Continue with screenshot attempt even if scrolling fails - } - } - - // Ensure screenshots directory exists - const screenshotsDir = path.join(tempDir, 'screenshots'); - if (!fs.existsSync(screenshotsDir)) { - fs.mkdirSync(screenshotsDir, { recursive: true }); - } - // Generate unique filename const uuid = crypto.randomUUID(); const filename = `${uuid}.png`; const filePath = path.join(screenshotsDir, filename); - // Take screenshot of the element + // Take screenshot of the element with accurate colors and opacity + // This captures the element exactly as rendered in the browser with all CSS styles applied await element.screenshot({ path: filePath as `${string}.png`, type: 'png', - omitBackground: false + omitBackground: true // Use transparent background for better quality }); console.log(`Screenshot saved: ${filePath}`); @@ -870,4 +852,5 @@ async function takeElementScreenshot(element: ElementHandle): Promise=16.8.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", @@ -274,6 +285,402 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", + "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", + "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", + "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", + "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", + "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", + "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", + "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", + "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", + "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", + "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", + "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", + "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", + "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", + "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", + "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", + "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.4" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", + "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", + "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", + "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2376,6 +2783,16 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/sharp": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "deprecated": "This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "sharp": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -3180,6 +3597,18 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3198,6 +3627,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -3576,6 +4014,14 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -6505,6 +6951,47 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "license": "MIT" }, + "node_modules/sharp": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", + "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-libvips-darwin-arm64": "1.2.0", + "@img/sharp-libvips-darwin-x64": "1.2.0", + "@img/sharp-libvips-linux-arm": "1.2.0", + "@img/sharp-libvips-linux-arm64": "1.2.0", + "@img/sharp-libvips-linux-ppc64": "1.2.0", + "@img/sharp-libvips-linux-s390x": "1.2.0", + "@img/sharp-libvips-linux-x64": "1.2.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", + "@img/sharp-libvips-linuxmusl-x64": "1.2.0", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-ppc64": "0.34.3", + "@img/sharp-linux-s390x": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-linuxmusl-arm64": "0.34.3", + "@img/sharp-linuxmusl-x64": "0.34.3", + "@img/sharp-wasm32": "0.34.3", + "@img/sharp-win32-arm64": "0.34.3", + "@img/sharp-win32-ia32": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6609,6 +7096,19 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", diff --git a/servers/nextjs/package.json b/servers/nextjs/package.json index 80516abf..6af53e80 100644 --- a/servers/nextjs/package.json +++ b/servers/nextjs/package.json @@ -54,6 +54,7 @@ "react-element-to-jsx-string": "^15.0.0", "react-redux": "^9.1.2", "recharts": "^2.15.4", + "sharp": "^0.34.3", "sonner": "^2.0.6", "tailwind-merge": "^2.5.3", "tailwind-scrollbar-hide": "^2.0.0", @@ -67,6 +68,7 @@ "@types/puppeteer": "^5.4.7", "@types/react": "^18", "@types/react-dom": "^18", + "@types/sharp": "^0.32.0", "@types/uuid": "^10.0.0", "cypress": "^14.3.3", "tailwindcss": "^3.4.1", From a1c94c0a73665bd15edac349af89d71ca78b6450 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Sun, 20 Jul 2025 01:02:10 +0545 Subject: [PATCH 6/7] refactor(Nextjs): Remove some unused components & packages --- .../upload/components/UploadPage.tsx | 2 +- servers/nextjs/app/layout.tsx | 2 +- servers/nextjs/components/ui/alert-dialog.tsx | 141 ---------- servers/nextjs/components/ui/alert.tsx | 59 ---- servers/nextjs/components/ui/aspect-ratio.tsx | 7 - servers/nextjs/components/ui/avatar.tsx | 50 ---- servers/nextjs/components/ui/badge.tsx | 36 --- servers/nextjs/components/ui/breadcrumb.tsx | 114 -------- servers/nextjs/components/ui/calendar.tsx | 213 -------------- servers/nextjs/components/ui/carousel.tsx | 261 ------------------ servers/nextjs/components/ui/checkbox.tsx | 29 -- servers/nextjs/components/ui/context-menu.tsx | 199 ------------- servers/nextjs/components/ui/drawer.tsx | 118 -------- .../nextjs/components/ui/dropdown-menu.tsx | 200 -------------- servers/nextjs/components/ui/form.tsx | 178 ------------ servers/nextjs/components/ui/hover-card.tsx | 29 -- servers/nextjs/components/ui/input-otp.tsx | 70 ----- servers/nextjs/components/ui/menubar.tsx | 255 ----------------- .../nextjs/components/ui/navigation-menu.tsx | 127 --------- .../nextjs/components/ui/overlay-loader.tsx | 39 +-- servers/nextjs/components/ui/pagination.tsx | 116 -------- servers/nextjs/components/ui/progress-bar.tsx | 39 ++- servers/nextjs/components/ui/resizable.tsx | 44 --- servers/nextjs/components/ui/toggle-group.tsx | 61 ---- servers/nextjs/package.json | 3 - 25 files changed, 32 insertions(+), 2360 deletions(-) delete mode 100644 servers/nextjs/components/ui/alert-dialog.tsx delete mode 100644 servers/nextjs/components/ui/alert.tsx delete mode 100644 servers/nextjs/components/ui/aspect-ratio.tsx delete mode 100644 servers/nextjs/components/ui/avatar.tsx delete mode 100644 servers/nextjs/components/ui/badge.tsx delete mode 100644 servers/nextjs/components/ui/breadcrumb.tsx delete mode 100644 servers/nextjs/components/ui/calendar.tsx delete mode 100644 servers/nextjs/components/ui/carousel.tsx delete mode 100644 servers/nextjs/components/ui/checkbox.tsx delete mode 100644 servers/nextjs/components/ui/context-menu.tsx delete mode 100644 servers/nextjs/components/ui/drawer.tsx delete mode 100644 servers/nextjs/components/ui/dropdown-menu.tsx delete mode 100644 servers/nextjs/components/ui/form.tsx delete mode 100644 servers/nextjs/components/ui/hover-card.tsx delete mode 100644 servers/nextjs/components/ui/input-otp.tsx delete mode 100644 servers/nextjs/components/ui/menubar.tsx delete mode 100644 servers/nextjs/components/ui/navigation-menu.tsx delete mode 100644 servers/nextjs/components/ui/pagination.tsx delete mode 100644 servers/nextjs/components/ui/resizable.tsx delete mode 100644 servers/nextjs/components/ui/toggle-group.tsx diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index 52efb4ea..a8e58058 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -191,7 +191,7 @@ const UploadPage = () => { onConfigChange={handleConfigChange} />
- +
- + ); diff --git a/servers/nextjs/components/ui/alert-dialog.tsx b/servers/nextjs/components/ui/alert-dialog.tsx deleted file mode 100644 index 57760f2e..00000000 --- a/servers/nextjs/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,141 +0,0 @@ -"use client" - -import * as React from "react" -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" - -import { cn } from "@/lib/utils" -import { buttonVariants } from "@/components/ui/button" - -const AlertDialog = AlertDialogPrimitive.Root - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger - -const AlertDialogPortal = AlertDialogPrimitive.Portal - -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName - -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - -)) -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName - -const AlertDialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -AlertDialogHeader.displayName = "AlertDialogHeader" - -const AlertDialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -AlertDialogFooter.displayName = "AlertDialogFooter" - -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName - -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogDescription.displayName = - AlertDialogPrimitive.Description.displayName - -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName - -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName - -export { - AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogAction, - AlertDialogCancel, -} diff --git a/servers/nextjs/components/ui/alert.tsx b/servers/nextjs/components/ui/alert.tsx deleted file mode 100644 index 5afd41d1..00000000 --- a/servers/nextjs/components/ui/alert.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)) -Alert.displayName = "Alert" - -const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertTitle.displayName = "AlertTitle" - -const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertDescription.displayName = "AlertDescription" - -export { Alert, AlertTitle, AlertDescription } diff --git a/servers/nextjs/components/ui/aspect-ratio.tsx b/servers/nextjs/components/ui/aspect-ratio.tsx deleted file mode 100644 index d6a5226f..00000000 --- a/servers/nextjs/components/ui/aspect-ratio.tsx +++ /dev/null @@ -1,7 +0,0 @@ -"use client" - -import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" - -const AspectRatio = AspectRatioPrimitive.Root - -export { AspectRatio } diff --git a/servers/nextjs/components/ui/avatar.tsx b/servers/nextjs/components/ui/avatar.tsx deleted file mode 100644 index 51e507ba..00000000 --- a/servers/nextjs/components/ui/avatar.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client" - -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" - -import { cn } from "@/lib/utils" - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -Avatar.displayName = AvatarPrimitive.Root.displayName - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AvatarImage.displayName = AvatarPrimitive.Image.displayName - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName - -export { Avatar, AvatarImage, AvatarFallback } diff --git a/servers/nextjs/components/ui/badge.tsx b/servers/nextjs/components/ui/badge.tsx deleted file mode 100644 index e87d62bf..00000000 --- a/servers/nextjs/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ) -} - -export { Badge, badgeVariants } diff --git a/servers/nextjs/components/ui/breadcrumb.tsx b/servers/nextjs/components/ui/breadcrumb.tsx deleted file mode 100644 index 1fd4b2c0..00000000 --- a/servers/nextjs/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cn } from "@/lib/utils" -import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons" - -const Breadcrumb = React.forwardRef< - HTMLElement, - React.ComponentPropsWithoutRef<"nav"> & { - separator?: React.ReactNode - } ->(({ ...props }, ref) =>