diff --git a/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx b/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx index 1c5ddc74..fe46d952 100644 --- a/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx @@ -194,15 +194,15 @@ const EditableLayoutWrapper: React.FC = ({ // Add hover effects without changing layout htmlImg.style.cursor = 'pointer'; - htmlImg.style.transition = 'filter 0.2s, transform 0.2s'; + htmlImg.style.transition = 'opacity 0.2s, transform 0.2s'; const mouseEnterHandler = () => { - htmlImg.style.filter = 'brightness(0.9)'; + htmlImg.style.opacity = '0.8'; }; const mouseLeaveHandler = () => { - htmlImg.style.filter = 'brightness(1)'; + htmlImg.style.opacity = '1'; }; @@ -216,7 +216,7 @@ const EditableLayoutWrapper: React.FC = ({ htmlImg.removeEventListener('mouseleave', mouseLeaveHandler); htmlImg.style.cursor = ''; htmlImg.style.transition = ''; - htmlImg.style.filter = ''; + htmlImg.style.opacity = ''; htmlImg.style.transform = ''; htmlImg.removeAttribute('data-editable-processed'); }; @@ -326,6 +326,18 @@ const EditableLayoutWrapper: React.FC = ({ } }; + const handleFocusPointClick = (propertiesData: any) => { + console.log('activeEditor', activeEditor); + const id = activeEditor?.id; + const editableId = document.querySelector(`[data-editable-id="${id}"]`); + console.log('editableId', editableId); + if (editableId) { + const editableElement = editableId as HTMLImageElement; + editableElement.style.objectPosition = `${propertiesData.initialFocusPoint.x}px ${propertiesData.initialFocusPoint.y}px`; + editableElement.style.objectFit = propertiesData.initialObjectFit; + } + + }; return (
@@ -341,6 +353,7 @@ const EditableLayoutWrapper: React.FC = ({ properties={null} onClose={handleEditorClose} onImageChange={handleImageChange} + onFocusPointClick={handleFocusPointClick} > )} @@ -359,4 +372,49 @@ const EditableLayoutWrapper: React.FC = ({ ); }; -export default EditableLayoutWrapper; \ No newline at end of file +export default EditableLayoutWrapper; + + + + +const setNestedImageValue = (obj: any, path: string, url: string, promptText?: string) => { + const keys = path.split(/[.\[\]]+/).filter(Boolean); + let current = obj; + + // Navigate to the parent object + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (isNaN(Number(key))) { + if (!current[key]) { + current[key] = {}; + } + current = current[key]; + } else { + const index = Number(key); + if (!current[index]) { + current[index] = {}; + } + current = current[index]; + } + } + + // Set the image properties + const finalKey = keys[keys.length - 1]; + const target = isNaN(Number(finalKey)) ? current[finalKey] : current[Number(finalKey)]; + + // Preserve existing properties if the target already exists + const updatedValue = { + ...(target && typeof target === 'object' ? target : {}), + __image_url__: url, + __image_prompt__: promptText || (target?.__image_prompt__) || '' + }; + + if (isNaN(Number(finalKey))) { + current[finalKey] = updatedValue; + } else { + current[Number(finalKey)] = updatedValue; + } + + // Add debugging + console.log('Redux: Updated slide image at path:', path, 'with URL:', url); +}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx index 83cc9906..604d0a9b 100644 --- a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx @@ -27,7 +27,7 @@ interface ImageEditorProps { properties?: null | any; onClose?: () => void; onImageChange?: (newImageUrl: string, prompt?: string) => void; - + onFocusPointClick?: (propertiesData: any) => void; } const ImageEditor = ({ @@ -36,6 +36,7 @@ const ImageEditor = ({ promptContent, properties, onClose, + onFocusPointClick, onImageChange, }: ImageEditorProps) => { @@ -69,9 +70,6 @@ const ImageEditor = ({ // Refs const imageRef = useRef(null); - const imageContainerRef = useRef(null); - const toolbarRef = useRef(null); - const popoverContentRef = useRef(null); useEffect(() => { setPreviewImages(initialImage); @@ -105,28 +103,7 @@ const ImageEditor = ({ } } - // Close toolbar when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - imageContainerRef.current && - !imageContainerRef.current.contains(event.target as Node) && - toolbarRef.current && - !toolbarRef.current.contains(event.target as Node) && - !popoverContentRef.current - ) { - if (isFocusPointMode) { - saveImageProperties(objectFit, focusPoint); - } - setIsFocusPointMode(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [isFocusPointMode, focusPoint, objectFit]); /** * Handles image selection and calls the parent callback @@ -200,6 +177,7 @@ const ImageEditor = ({ initialFocusPoint: focusPoint, }; // TODO: Save to Redux store if needed + onFocusPointClick?.(propertiesData); }; /** @@ -298,6 +276,7 @@ const ImageEditor = ({ Upload + {/* Edit */} {/* Generate Tab */} @@ -455,6 +434,79 @@ const ImageEditor = ({ )}
+ +
+

Current Image

+
{ + + if (isFocusPointMode) { + handleFocusPointClick(e); + } else { + + } + + }} + + className="aspect-[4/3] group rounded-lg overflow-hidden relative border border-gray-200"> +

Click to Change Focus Point

+ {previewImages && { + + setIsFocusPointMode(true); + + } + } src={previewImages} style={{ objectFit: objectFit, objectPosition: `${focusPoint.x}% ${focusPoint.y}%`, }} alt={`Preview`} className="w-full h-full " />} + {isFocusPointMode &&
+
+

+ Click anywhere to set focus point +

+ +
+ +
+
+
+
+
+
+
+
} +
+ {/* Edit Image */} + {/* Object Fit */} + { +
+

Object Fit

+
+ + + +
+
+ + } + {/* Focus Point */} + { + + } +
+
diff --git a/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx b/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx index ce92d946..391c67d5 100644 --- a/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx +++ b/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx @@ -26,7 +26,7 @@ const SlideComponent = ({ data }: { data: Partial }) => { // Validate each data field before rendering using && operator or optional chaining. // These layouts are exported as PDF and PPTX so they must be optimized for both formats. // Content must properly fit in the container, specify min and max constraints in the schema. - // You can check out ExampleComponent.tsx for more details. + // You can check out ExampleSlideLayout.tsx for more details. }; export default SlideComponent; \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx b/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx index 486910ef..f4375d7a 100644 --- a/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx @@ -29,7 +29,7 @@ const bulletIconsOnlySlideSchema = z.object({ title: 'Custom Software', subtitle: 'We create tailored software to optimize processes and boost efficiency.', icon: { - __icon_url__: 'https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/code.js', + __icon_url__: '/static/icons/placeholder.png', __icon_query__: 'code software development' } }, @@ -37,7 +37,7 @@ const bulletIconsOnlySlideSchema = z.object({ title: 'Digital Consulting', subtitle: 'Our consultants guide organizations in leveraging the latest technologies.', icon: { - __icon_url__: 'https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/users.js', + __icon_url__: '/static/icons/placeholder.png', __icon_query__: 'users consulting team' } }, @@ -45,7 +45,7 @@ const bulletIconsOnlySlideSchema = z.object({ title: 'Support Services', subtitle: 'We provide ongoing support to help businesses adapt and maintain performance.', icon: { - __icon_url__: 'https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/headphones.js', + __icon_url__: '/static/icons/placeholder.png', __icon_query__: 'headphones support service' } }, @@ -53,7 +53,7 @@ const bulletIconsOnlySlideSchema = z.object({ title: 'Scalable Marketing', subtitle: 'Our data-driven strategies help businesses expand their reach and engagement.', icon: { - __icon_url__: 'https://cdn.jsdelivr.net/npm/lucide@latest/dist/esm/icons/trending-up.js', + __icon_url__: '/static/icons/placeholder.png', __icon_query__: 'trending up marketing growth' } } diff --git a/servers/nextjs/presentation-layouts/general/QuoteSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/QuoteSlideLayout.tsx new file mode 100644 index 00000000..f40cc9d0 --- /dev/null +++ b/servers/nextjs/presentation-layouts/general/QuoteSlideLayout.tsx @@ -0,0 +1,116 @@ +import React from 'react' +import * as z from "zod"; +import { ImageSchema } from '@/presentation-layouts/defaultSchemes'; + +export const layoutId = 'quote-slide' +export const layoutName = 'Quote' +export const layoutDescription = 'A slide layout with a heading, inspirational quote, and background image with overlay for text visibility.' + +const quoteSlideSchema = z.object({ + heading: z.string().min(3).max(60).default('Words of Wisdom').meta({ + description: "Main heading of the slide", + }), + quote: z.string().min(10).max(200).default('Success is not final, failure is not fatal: it is the courage to continue that counts. The future belongs to those who believe in the beauty of their dreams.').meta({ + description: "The main quote text content", + }), + author: z.string().min(2).max(50).default('Winston Churchill').meta({ + description: "Author of the quote", + }), + backgroundImage: ImageSchema.default({ + __image_url__: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2000&q=80', + __image_prompt__: 'Inspirational mountain landscape with dramatic sky and clouds' + }).meta({ + description: "Background image for the slide", + }) +}) + +export const Schema = quoteSlideSchema + +export type QuoteSlideData = z.infer + +interface QuoteSlideLayoutProps { + data?: Partial +} + +const QuoteSlideLayout: React.FC = ({ data: slideData }) => { + return ( + <> + {/* Import Google Fonts */} + + +
+ {/* Background Image */} +
+ + {/* Background Overlay */} +
+ + {/* Decorative Elements */} +
+
+
+ + {/* Main Content */} +
+
+ + {/* Heading */} +
+

+ {slideData?.heading || 'Words of Wisdom'} +

+ {/* Purple accent line */} +
+
+ + {/* Quote Section */} +
+ {/* Quote Icon */} +
+ + + +
+ + {/* Quote Text */} +
+ "{slideData?.quote || 'Success is not final, failure is not fatal: it is the courage to continue that counts. The future belongs to those who believe in the beauty of their dreams.'}" +
+ + {/* Author */} +
+
+ + {slideData?.author || 'Winston Churchill'} + +
+
+
+
+
+ + {/* Bottom Decorative Border */} +
+
+ + ) +} + +export default QuoteSlideLayout \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/general/TableInfoSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/TableInfoSlideLayout.tsx new file mode 100644 index 00000000..693ae100 --- /dev/null +++ b/servers/nextjs/presentation-layouts/general/TableInfoSlideLayout.tsx @@ -0,0 +1,145 @@ +import React from 'react' +import * as z from "zod"; + +export const layoutId = 'table-info-slide' +export const layoutName = 'Table with Info' +export const layoutDescription = 'A slide layout with a title at the top, structured table in the middle, and descriptive text at the bottom.' + +const tableInfoSlideSchema = z.object({ + title: z.string().min(3).max(40).default('Market Comparison').meta({ + description: "Main title of the slide", + }), + tableData: z.object({ + headers: z.array(z.string().min(1).max(30)).min(2).max(5).meta({ + description: "Table column headers" + }), + rows: z.array(z.array(z.string().min(1).max(50))).min(2).max(6).meta({ + description: "Table rows data - each row should match the number of headers" + }) + }).default({ + headers: ['Company', 'Revenue', 'Growth', 'Market Share'], + rows: [ + ['Company A', '$2.5M', '15%', '25%'], + ['Company B', '$1.8M', '12%', '18%'], + ['Company C', '$3.2M', '20%', '32%'], + ['Our Company', '$1.2M', '35%', '12%'] + ] + }).meta({ + description: "Table structure with headers and rows" + }), + description: z.string().min(10).max(200).default('This comparison shows our competitive position in the market. While we currently have a smaller market share, our growth rate significantly exceeds competitors, indicating strong potential for future expansion.').meta({ + description: "Descriptive text that appears below the table", + }) +}) + +export const Schema = tableInfoSlideSchema + +export type TableInfoSlideData = z.infer + +interface TableInfoSlideLayoutProps { + data?: Partial +} + +const TableInfoSlideLayout: React.FC = ({ data: slideData }) => { + const tableHeaders = slideData?.tableData?.headers || ['Company', 'Revenue', 'Growth', 'Market Share'] + const tableRows = slideData?.tableData?.rows || [ + ['Company A', '$2.5M', '15%', '25%'], + ['Company B', '$1.8M', '12%', '18%'], + ['Company C', '$3.2M', '20%', '32%'], + ['Our Company', '$1.2M', '35%', '12%'] + ] + + return ( + <> + {/* Import Google Fonts */} + + +
+ {/* Decorative Wave Patterns */} +
+ + + + + +
+ +
+ + + + + +
+ + {/* Main Content */} +
+ + {/* Title Section */} +
+

+ {slideData?.title || 'Market Comparison'} +

+ {/* Purple accent line */} +
+
+ + {/* Table Section */} +
+
+
+ {/* Table Header */} +
+
+ {tableHeaders.map((header, index) => ( +
+ {header} +
+ ))} +
+
+ + {/* Table Body */} +
+ {tableRows.map((row, rowIndex) => ( +
+ {row.slice(0, tableHeaders.length).map((cell, cellIndex) => ( +
+ {cell} +
+ ))} +
+ ))} +
+
+
+ +
+ + {/* Description Section */} +
+
+

+ {slideData?.description || 'This comparison shows our competitive position in the market. While we currently have a smaller market share, our growth rate significantly exceeds competitors, indicating strong potential for future expansion.'} +

+
+
+
+
+ + ) +} + +export default TableInfoSlideLayout \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx index 6e1bd452..a968da20 100644 --- a/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx @@ -68,8 +68,8 @@ const IntroPitchDeckSlide: React.FC = ({ > {/* Top Header */}
- {slideData?.companyName} - {slideData?.date} +

{slideData?.companyName}

+

{slideData?.date}

{/* Main Title */} diff --git a/servers/nextjs/presentation-layouts/modern/2AboutCompanySlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/2AboutCompanySlideLayout.tsx index 177bfd25..cea8b48e 100644 --- a/servers/nextjs/presentation-layouts/modern/2AboutCompanySlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/2AboutCompanySlideLayout.tsx @@ -122,14 +122,17 @@ const AboutCompanySlideLayout: React.FC = ({ {/* Right side - Content */}
-

- {slideData?.title || "About Our Company"} -

+ {slideData?.title && ( +

+ {slideData?.title} +

+ )} -
- {slideData?.content || - "In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session."} -
+ {slideData?.content && ( +
+ {slideData?.content} +
+ )}
diff --git a/servers/nextjs/presentation-layouts/modern/3ProblemSlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/3ProblemSlideLayout.tsx index 8bf6b74d..3601aad9 100644 --- a/servers/nextjs/presentation-layouts/modern/3ProblemSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/3ProblemSlideLayout.tsx @@ -150,12 +150,14 @@ const ProblemStatementSlideLayout: React.FC< className="flex items-start gap-5 bg-white bg-opacity-5 rounded-lg p-5" >
- {category.icon?.__icon_query__} + {category.icon?.__icon_url__ && ( + {category.icon?.__icon_query__} + )}

diff --git a/servers/nextjs/presentation-layouts/modern/5ProductOverviewSlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/5ProductOverviewSlideLayout.tsx index bd591075..8b5e5e86 100644 --- a/servers/nextjs/presentation-layouts/modern/5ProductOverviewSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/5ProductOverviewSlideLayout.tsx @@ -162,13 +162,15 @@ const ProductOverviewSlideLayout: React.FC = ({ className="rounded-b-md overflow-hidden" style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }} > - { + {products[0].image.__image_url__ && ( + { + )}

)} @@ -199,13 +201,15 @@ const ProductOverviewSlideLayout: React.FC = ({ className="rounded-b-md overflow-hidden" style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }} > - { + {products[2].image.__image_url__ && ( + { + )} )} @@ -225,13 +229,15 @@ const ProductOverviewSlideLayout: React.FC = ({ className="rounded-t-md overflow-hidden" style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }} > - { + {products[1].image.__image_url__ && ( + { + )} {/* Bottom Section - Blue background with text */}
= ({ className="rounded-t-md overflow-hidden" style={{ height: `${IMAGE_SECTION_HEIGHT + 28}px` }} > - { + {products[3].image.__image_url__ && ( + { + )}
= ({ {slideData?.title || "Market Size"}
- Market World Map with Points + {slideData?.mapImage?.__image_url__ && ( + Market World Map with Points + )}
-

- {slideData?.description || - "Market size is the total amount of all sales and customers that can be seen directly by stakeholders. This technique is usually calculated at the end of the year, the market size can be used by companies to determine the potential of their market and business in the future."} -

+ {slideData?.description && ( +

+ {slideData?.description} +

+ )}
diff --git a/servers/nextjs/presentation-layouts/modern/z10TeamSlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/z10TeamSlideLayout.tsx index c88ee358..aaba6a9b 100644 --- a/servers/nextjs/presentation-layouts/modern/z10TeamSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/z10TeamSlideLayout.tsx @@ -145,11 +145,13 @@ const ModernTeamSlideLayout: React.FC = ({ > {/* Photo */}
- {member.image.__image_prompt__ + {member.image.__image_url__ && ( + {member.image.__image_prompt__ + )}
{/* Name */}