From e57b21838a1f6bb023640afdcbec0b0b313dceb1 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Fri, 18 Jul 2025 23:18:49 +0545 Subject: [PATCH 1/2] fix(Nextjs): Editable Text ClassName error --- .../components/TiptapText.tsx | 4 +- .../components/TiptapTextReplacer.tsx | 61 +++++++++++++++++-- .../hooks/useGroupLayouts.tsx | 1 - 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx index ee140619..aebfdfbf 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapText.tsx @@ -61,10 +61,10 @@ const TiptapText: React.FC = ({ } return ( -
+
{!disabled && ( -
+
); } - if (isEditMode) { return ( Date: Sat, 19 Jul 2025 01:07:44 +0545 Subject: [PATCH 2/2] feat(Nextjs): Text saving to store & Auto saving --- .../components/SmartEditableWrapper.tsx | 6 - .../components/TiptapTextReplacer.tsx | 8 +- .../hooks/useGroupLayouts.tsx | 18 +- .../presentation/components/Header.tsx | 9 +- .../components/PresentationPage.tsx | 47 +-- .../presentation/hooks/index.ts | 3 +- .../presentation/hooks/useAutoSave.tsx | 80 ++++ .../presentation/hooks/usePresentationData.ts | 1 - .../hooks/usePresentationStreaming.ts | 1 - .../presentation/utils/debounce.ts | 21 - .../services/api/presentation-generation.ts | 108 +----- .../store/slices/presentationGeneration.ts | 361 +++--------------- 12 files changed, 190 insertions(+), 473 deletions(-) create mode 100644 servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx delete mode 100644 servers/nextjs/app/(presentation-generator)/presentation/utils/debounce.ts diff --git a/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx b/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx index 730c98d8..30781d00 100644 --- a/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx @@ -61,7 +61,6 @@ export const SmartEditableProvider: React.FC = ({ const findEditableElements = () => { const elements: EditableElement[] = []; - console.log('🔍 Starting smart detection with slideData:', slideData); // Detect Images and Icons only (text is now handled by SmartText components) const detectEditableElementsFromData = (data: any, path: string = '') => { @@ -69,7 +68,6 @@ export const SmartEditableProvider: React.FC = ({ // Check for __image_url__ pattern if (data.__image_url__) { - console.log(`📸 Found __image_url__ at ${path}:`, data.__image_url__); const imgElement = findDOMElementByImageUrl(container, data.__image_url__); if (imgElement) { elements.push({ @@ -83,13 +81,11 @@ export const SmartEditableProvider: React.FC = ({ imageIdx: elements.filter(e => e.type === 'image').length } }); - console.log(`✅ Matched image to DOM element:`, imgElement); } } // Check for __icon_url__ pattern if (data.__icon_url__) { - console.log(`🎯 Found __icon_url__ at ${path}:`, data.__icon_url__); const imgElement = findDOMElementByImageUrl(container, data.__icon_url__); if (imgElement) { elements.push({ @@ -106,7 +102,6 @@ export const SmartEditableProvider: React.FC = ({ icon_prompt: data.__icon_query__ ? [data.__icon_query__] : [] } }); - console.log(`✅ Matched icon to DOM element:`, imgElement); } } // Recursively scan nested objects and arrays @@ -125,7 +120,6 @@ export const SmartEditableProvider: React.FC = ({ }; detectEditableElementsFromData(slideData); - console.log('🎉 Final detected elements:', elements); setEditableElements(elements); }; diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx index ed1a14b1..742473fe 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx @@ -10,7 +10,8 @@ interface TiptapTextReplacerProps { }>; children: ReactNode; slideData?: any; - onContentChange?: (content: string, path: string) => void; + slideIndex?: number; + onContentChange?: (content: string, path: string, slideIndex?: number) => void; isEditMode?: boolean; } @@ -18,6 +19,7 @@ const TiptapTextReplacer: React.FC = ({ children, slideData, layout, + slideIndex, onContentChange = () => { }, isEditMode = true }) => { @@ -109,7 +111,7 @@ const TiptapTextReplacer: React.FC = ({ content={trimmedText} onContentChange={(content: string) => { if (dataPath && onContentChange) { - onContentChange(content, dataPath); + onContentChange(content, dataPath, slideIndex); } }} placeholder="Enter text..." @@ -257,7 +259,7 @@ const TiptapTextReplacer: React.FC = ({ return () => { clearTimeout(timer); }; - }, [slideData, isEditMode]); + }, [slideData, isEditMode, slideIndex]); return (
diff --git a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx index 0725b096..506ca576 100644 --- a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx +++ b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx @@ -1,10 +1,13 @@ 'use client' import React, { useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import { useLayout } from '../context/LayoutContext'; import { SmartEditableProvider } from '../components/SmartEditableWrapper'; import TiptapTextReplacer from '../components/TiptapTextReplacer'; +import { updateSlideContent } from '../../../store/slices/presentationGeneration'; export const useGroupLayouts = () => { + const dispatch = useDispatch(); const { getLayoutByIdAndGroup, getLayoutsByGroup, @@ -53,11 +56,20 @@ export const useGroupLayouts = () => { > { - console.log(`Text content changed at ${dataPath}:`, content); + onContentChange={(content: string, dataPath: string, slideIndex?: number) => { + console.log(`Text content changed at slide ${slideIndex}, path ${dataPath}:`, content); + // Dispatch Redux action to update slide content + if (dataPath && slideIndex !== undefined) { + dispatch(updateSlideContent({ + slideIndex: slideIndex, + dataPath: dataPath, + content: content + })); + } }} > @@ -67,7 +79,7 @@ export const useGroupLayouts = () => { } return ; }; - }, [getGroupLayout]); + }, [getGroupLayout, dispatch]); return { getGroupLayout, diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 02a338c7..640beb24 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -47,7 +47,6 @@ import Modal from "./Modal"; import Announcement from "@/components/Announcement"; import { getFontLink, getStaticFileUrl } from "../../utils/others"; -import JSPowerPointExtractor from "../../components/JSPowerPointExtractor"; const Header = ({ @@ -108,13 +107,7 @@ const Header = ({ themeColors.slideBox ); - // Save in background - await PresentationGenerationApi.setThemeColors(presentation_id, { - name: themeType, - colors: { - ...themeColors, - }, - }); + } catch (error) { console.error("Failed to update theme:", error); toast({ diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index 87f34916..7a368ce5 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -8,14 +8,14 @@ import SidePanel from "../components/SidePanel"; import SlideContent from "../components/SlideContent"; import LoadingState from "../../components/LoadingState"; import Header from "../components/Header"; -import { Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { AlertCircle } from "lucide-react"; +import { AlertCircle, Loader2 } from "lucide-react"; import Help from "./Help"; import { usePresentationStreaming, usePresentationData, - usePresentationNavigation + usePresentationNavigation, + useAutoSave } from "../hooks"; import { PresentationPageProps } from "../types"; @@ -26,7 +26,6 @@ const PresentationPage: React.FC = ({ presentation_id }) const [isFullscreen, setIsFullscreen] = useState(false); const [error, setError] = useState(false); const [isMobilePanelOpen, setIsMobilePanelOpen] = useState(false); - const [autoSaveLoading, setAutoSaveLoading] = useState(false); // Redux state const { currentTheme, currentColors } = useSelector( @@ -36,13 +35,19 @@ const PresentationPage: React.FC = ({ presentation_id }) (state: RootState) => state.presentationGeneration ); + // Auto-save functionality + const { isSaving } = useAutoSave({ + debounceMs: 2000, + enabled: !!presentationData && !isStreaming, + + }); + // Custom hooks const { fetchUserSlides, handleDeleteSlide } = usePresentationData( presentation_id, setLoading, setError ); - const { isPresentMode, stream, @@ -98,33 +103,29 @@ const PresentationPage: React.FC = ({ presentation_id }) role="alert" > - Oops! -

- We encountered an issue loading your presentation. +

+ Something went wrong +

+

+ We couldn't load your presentation. Please try again.

-

- Please check your internet connection or try again later. -

-
); } - return (
- {/* Auto save loading indicator */} - {autoSaveLoading && ( -
- -
- )} + +
+ {isSaving && ( + + )} + +
diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/index.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/index.ts index c416e2b9..9191e5d5 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/index.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/index.ts @@ -1,3 +1,4 @@ export { usePresentationStreaming } from './usePresentationStreaming'; export { usePresentationData } from './usePresentationData'; -export { usePresentationNavigation } from './usePresentationNavigation'; \ No newline at end of file +export { usePresentationNavigation } from './usePresentationNavigation'; +export { useAutoSave } from './useAutoSave'; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx b/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx new file mode 100644 index 00000000..596e2405 --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx @@ -0,0 +1,80 @@ +'use client' +import { useEffect, useRef, useCallback, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/store/store'; +import { PresentationGenerationApi } from '../../services/api/presentation-generation'; + +interface UseAutoSaveOptions { + debounceMs?: number; + enabled?: boolean; +} + +export const useAutoSave = ({ + debounceMs = 2000, + enabled = true, +}: UseAutoSaveOptions = {}) => { + const { presentationData } = useSelector( + (state: RootState) => state.presentationGeneration + ); + + const saveTimeoutRef = useRef(null); + const lastSavedDataRef = useRef(''); + const [isSaving, setIsSaving] = useState(false); + + // Debounced save function + const debouncedSave = useCallback(async (data: any) => { + // Clear existing timeout + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + + // Set new timeout + saveTimeoutRef.current = setTimeout(async () => { + if (!data || isSaving) return; + + const currentDataString = JSON.stringify(data); + + // Skip if data hasn't changed since last save + if (currentDataString === lastSavedDataRef.current) { + return; + } + + try { + setIsSaving(true); + console.log('🔄 Auto-saving presentation data...'); + + // Call the API to update presentation content + await PresentationGenerationApi.updatePresentationContent(data); + + // Update last saved data reference + lastSavedDataRef.current = currentDataString; + + console.log('✅ Auto-save successful'); + + } catch (error) { + console.error('❌ Auto-save failed:', error); + } finally { + setIsSaving(false); + } + }, debounceMs); + }, [debounceMs, isSaving]); + + // Effect to trigger auto-save when presentation data changes + useEffect(() => { + if (!enabled || !presentationData) return; + + // Trigger debounced save + debouncedSave(presentationData); + + // Cleanup timeout on unmount + return () => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current); + } + }; + }, [presentationData, enabled, debouncedSave]); + + return { + isSaving, + }; +}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts index 38710068..4c7681f9 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationData.ts @@ -16,7 +16,6 @@ export const usePresentationData = ( const fetchUserSlides = useCallback(async () => { try { const data = await DashboardApi.getPresentation(presentationId); - console.log('Presentation Data',data); if (data) { dispatch(setPresentationData(data)); setLoading(false); diff --git a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts index ad03efdd..66b7c1b1 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts +++ b/servers/nextjs/app/(presentation-generator)/presentation/hooks/usePresentationStreaming.ts @@ -1,6 +1,5 @@ import { useEffect, useRef } from "react"; import { useDispatch } from "react-redux"; -import { toast } from "@/hooks/use-toast"; import { setPresentationData, setStreaming } from "@/store/slices/presentationGeneration"; import { jsonrepair } from "jsonrepair"; diff --git a/servers/nextjs/app/(presentation-generator)/presentation/utils/debounce.ts b/servers/nextjs/app/(presentation-generator)/presentation/utils/debounce.ts deleted file mode 100644 index 93c2a725..00000000 --- a/servers/nextjs/app/(presentation-generator)/presentation/utils/debounce.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useCallback, useRef } from "react"; - -export function useDebounce void>( - callback: T, - delay: number -) { - const timeoutRef = useRef(); - - return useCallback( - (...args: Parameters) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - timeoutRef.current = setTimeout(() => { - callback(...args); - }, delay); - }, - [callback, delay] - ); -} \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts index 187808ea..6a53e489 100644 --- a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts +++ b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts @@ -3,25 +3,6 @@ import { IconSearch, ImageGenerate, ImageSearch } from "./params"; export class PresentationGenerationApi { - static async getChapterDetails() { - try { - const response = await fetch( - `/api/v1/ppt/chapter-details`, - { - method: "GET", - headers: getHeader(), - cache: "no-cache", - } - ); - if (response.status === 200) { - const data = await response.json(); - return data; - } - } catch (error) { - console.error("Error getting chapter details:", error); - throw error; - } - } static async uploadDoc(documents: File[]) { const formData = new FormData(); @@ -80,62 +61,9 @@ export class PresentationGenerationApi { throw error; } } - static async titleGeneration({ - presentation_id, - }: { - presentation_id: string; - }) { - try { - const response = await fetch( - `/api/v1/ppt/presentation/outlines/generate`, - { - method: "POST", - headers: getHeader(), - body: JSON.stringify({ - prompt: prompt, - presentation_id: presentation_id, - }), - cache: "no-cache", - } - ); - if (response.status === 200) { - const data = await response.json(); + - return data; - } else { - throw new Error(`Failed to generate titles: ${response.statusText}`); - } - } catch (error) { - console.error("error in title generation", error); - throw error; - } - } - - static async generatePresentation(presentationData: any) { - try { - const response = await fetch( - `/api/v1/ppt/generate`, - { - method: "POST", - headers: getHeader(), - body: JSON.stringify(presentationData), - cache: "no-cache", - } - ); - if (response.status === 200) { - const data = await response.json(); - - return data; - } else { - throw new Error( - `Failed to generate presentation: ${response.statusText}` - ); - } - } catch (error) { - console.error("error in presentation generation", error); - throw error; - } - } + static async editSlide( presentation_id: string, index: number, @@ -172,9 +100,9 @@ export class PresentationGenerationApi { static async updatePresentationContent(body: any) { try { const response = await fetch( - `/api/v1/ppt/slides/update`, + `/api/v1/ppt/presentation/update`, { - method: "POST", + method: "PUT", headers: getHeader(), body: JSON.stringify(body), cache: "no-cache", @@ -375,33 +303,7 @@ export class PresentationGenerationApi { throw error; } } - // SET THEME COLORS - static async setThemeColors(presentation_id: string, theme: any) { - try { - const response = await fetch( - `/api/v1/ppt/presentation/theme`, - { - method: "POST", - headers: getHeader(), - body: JSON.stringify({ - presentation_id, - theme, - }), - - } - ); - if (response.ok) { - const data = await response.json(); - return data; - } else { - throw new Error(`Failed to set theme colors: ${response.statusText}`); - } - } catch (error) { - console.error("error in theme colors set", error); - throw error; - } - } - // QUESTIONS + static async createPresentation({ prompt, diff --git a/servers/nextjs/store/slices/presentationGeneration.ts b/servers/nextjs/store/slices/presentationGeneration.ts index b65202c1..8223182d 100644 --- a/servers/nextjs/store/slices/presentationGeneration.ts +++ b/servers/nextjs/store/slices/presentationGeneration.ts @@ -1,40 +1,14 @@ import { Slide } from "@/app/(presentation-generator)/types/slide"; import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -interface Series { - data: number[]; - name?: string; -} -interface DataLabel { - dataLabelPosition: "Outside" | "Inside"; - dataLabelAlignment: "Base" | "Center" | "End"; -} -export interface ChartSettings { - showLegend: boolean; - showGrid: boolean; - showAxisLabel: boolean; - showDataLabel: boolean; - dataLabel: DataLabel; -} + export interface SlideOutline { title?: string; body?: string; } -export interface Chart { - id: string; - name: string; - type: string; - style: ChartSettings | {} | null; - unit?: string | null; - presentation: string; - postfix: string; - data: { - categories: string[]; - series: Series[]; - }; -} + export interface PresentationData { id: string; language: string; @@ -50,20 +24,18 @@ export interface PresentationData { interface PresentationGenerationState { presentation_id: string | null; - documents: string[]; - images: string[]; isLoading: boolean; isStreaming: boolean | null; outlines: SlideOutline[]; error: string | null; presentationData: PresentationData | null; + isSlidesRendered: boolean; } const initialState: PresentationGenerationState = { presentation_id: null, - documents: [], - images: [], outlines: [], + isSlidesRendered: false, isLoading: false, isStreaming: null, error: null, @@ -86,6 +58,10 @@ const presentationGenerationSlice = createSlice({ state.presentation_id = action.payload; state.error = null; }, + // Slides rendered + setSlidesRendered: (state, action: PayloadAction) => { + state.isSlidesRendered = action.payload; + }, // Error setError: (state, action: PayloadAction) => { state.error = action.payload; @@ -97,14 +73,6 @@ const presentationGenerationSlice = createSlice({ state.error = null; state.isLoading = false; }, - // Set documents - setDocs: (state, action: PayloadAction) => { - state.documents = action.payload; - }, - // Set images - setImgs: (state, action: PayloadAction) => { - state.images = action.payload; - }, // Set outlines setOutlines: (state, action: PayloadAction) => { state.outlines = action.payload; @@ -166,252 +134,61 @@ const presentationGenerationSlice = createSlice({ action.payload.slide; } }, - updateSlideVariant: ( + + // Update slide content at specific data path (for Tiptap text editing) + updateSlideContent: ( state, - action: PayloadAction<{ index: number; variant: number }> + action: PayloadAction<{ + slideIndex: number; + dataPath: string; + content: string; + }> ) => { if ( state.presentationData && - state.presentationData.slides[action.payload.index] + state.presentationData.slides && + state.presentationData.slides[action.payload.slideIndex] ) { - state.presentationData.slides[action.payload.index].design_index = - action.payload.variant; - } - }, - updateSlideTitle: ( - state, - action: PayloadAction<{ index: number; title: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[action.payload.index].content.title = - action.payload.title; - } - }, - updateSlideDescription: ( - state, - action: PayloadAction<{ index: number; description: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[ - action.payload.index - ].content.description = action.payload.description; - } - }, - updateSlideBodyString: ( - state, - action: PayloadAction<{ index: number; body: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[action.payload.index].content.body = - action.payload.body; - } - }, - updateSlideBodyHeading: ( - state, - action: PayloadAction<{ index: number; bodyIdx: number; heading: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[action.payload.index].content.body[ - action.payload.bodyIdx - // @ts-ignore - ].heading = action.payload.heading; - } - }, - updateSlideBodyDescription: ( - state, - action: PayloadAction<{ - index: number; - bodyIdx: number; - description: string; - }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[action.payload.index].content.body[ - action.payload.bodyIdx - // @ts-ignore - ].description = action.payload.description; - } - }, - updateSlideImage: ( - state, - action: PayloadAction<{ index: number; imageIdx: number; image: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.images) { - state.presentationData.slides[action.payload.index].images![ - action.payload.imageIdx - ] = action.payload.image; - } - }, - updateSlideIcon: ( - state, - action: PayloadAction<{ index: number; iconIdx: number; icon: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.icons) { - state.presentationData.slides[action.payload.index].icons![ - action.payload.iconIdx - ] = action.payload.icon; - } - }, - updateSlideChart: ( - state, - action: PayloadAction<{ index: number; chart: Chart }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - state.presentationData.slides[action.payload.index].content.graph = - action.payload.chart; - } - }, - updateSlideChartSettings: ( - state, - action: PayloadAction<{ index: number; chartSettings: ChartSettings }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - const defaultSettings: ChartSettings = { - showLegend: false, - showGrid: false, - showAxisLabel: true, - showDataLabel: true, - dataLabel: { - dataLabelPosition: "Outside", - dataLabelAlignment: "Center", - }, + const slide = state.presentationData.slides[action.payload.slideIndex]; + const { dataPath, content } = action.payload; + + // Helper function to set nested property value + const setNestedValue = (obj: any, path: string, value: 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))) { + // String key + if (!current[key]) { + current[key] = {}; + } + current = current[key]; + } else { + // Array index + const index = Number(key); + if (!current[index]) { + current[index] = {}; + } + current = current[index]; + } + } + + // Set the final value + const finalKey = keys[keys.length - 1]; + if (isNaN(Number(finalKey))) { + current[finalKey] = value; + } else { + current[Number(finalKey)] = value; + } }; - state.presentationData.slides[ - action.payload.index - ].content.graph.style = { - ...defaultSettings, - ...action.payload.chartSettings, - }; - } - }, - - addSlideBodyItem: ( - state, - action: PayloadAction<{ - index: number; - item: { heading: string; description: string }; - }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.content.body) { - // @ts-ignore - state.presentationData.slides[action.payload.index].content.body.push( - action.payload.item - ); - } - }, - addSlideImage: ( - state, - action: PayloadAction<{ index: number; image: string }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.images) { - state.presentationData.slides[action.payload.index].images!.push( - action.payload.image - ); - } - }, - deleteSlideImage: ( - state, - action: PayloadAction<{ index: number; imageIdx: number }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.images) { - state.presentationData.slides[action.payload.index].images!.splice( - action.payload.imageIdx, - 1 - ); - } - }, - updateSlideProperties: ( - state, - action: PayloadAction<{ index: number; itemIdx: number; properties: any }> - ) => { - if (state.presentationData?.slides[action.payload.index]) { - // Initialize properties object if it doesn't exist - if (!state.presentationData.slides[action.payload.index].properties) { - state.presentationData.slides[action.payload.index].properties = {}; + + // Update the slide content + if (dataPath && slide.content) { + setNestedValue(slide.content, dataPath, content); } - // Assign the properties to the specific item index - state.presentationData.slides[action.payload.index].properties[ - action.payload.itemIdx - ] = action.payload.properties; - } - }, - // Infographics - addInfographics: ( - state, - action: PayloadAction<{ slideIndex: number; item: any }> - ) => { - if (state.presentationData?.slides[action.payload.slideIndex]?.content) { - // @ts-ignore - state.presentationData.slides[ - action.payload.slideIndex - ].content.infographics.push(action.payload.item); - } - }, - deleteInfographics: ( - state, - action: PayloadAction<{ slideIndex: number; itemIdx: number }> - ) => { - if (state.presentationData?.slides[action.payload.slideIndex]?.content) { - // @ts-ignore - state.presentationData.slides[ - action.payload.slideIndex - ].content.infographics.splice(action.payload.itemIdx, 1); - } - }, - updateInfographicsTitle: ( - state, - action: PayloadAction<{ - slideIndex: number; - itemIdx: number; - title: string; - }> - ) => { - if (state.presentationData?.slides[action.payload.slideIndex]?.content) { - // @ts-ignore - state.presentationData.slides[ - action.payload.slideIndex - ].content.infographics[action.payload.itemIdx].title = - action.payload.title; - } - }, - updateInfographicsDescription: ( - state, - action: PayloadAction<{ - slideIndex: number; - itemIdx: number; - description: string; - }> - ) => { - if (state.presentationData?.slides[action.payload.slideIndex]?.content) { - // @ts-ignore - state.presentationData.slides[ - action.payload.slideIndex - ].content.infographics[action.payload.itemIdx].description = - action.payload.description; - } - }, - updateInfographicsChart: ( - state, - action: PayloadAction<{ slideIndex: number; itemIdx: number; chart: any }> - ) => { - if (state.presentationData?.slides[action.payload.slideIndex]?.content) { - // @ts-ignore - state.presentationData.slides[ - action.payload.slideIndex - ].content.infographics[action.payload.itemIdx].chart = - action.payload.chart; - } - }, - deleteSlideBodyItem: ( - state, - action: PayloadAction<{ index: number; itemIdx: number }> - ) => { - if (state.presentationData?.slides[action.payload.index]?.content.body) { - // @ts-ignore - state.presentationData.slides[action.payload.index].content.body.splice( - action.payload.itemIdx, - 1 - ); } }, }, @@ -421,39 +198,17 @@ export const { setStreaming, setLoading, setPresentationId, + setSlidesRendered, setError, clearPresentationData, - setDocs, - setImgs, - deleteSlideOutline, setPresentationData, setOutlines, // slides operations addSlide, updateSlide, - updateSlideVariant, - updateSlideChart, - updateSlideChartSettings, - updateSlideTitle, - updateSlideDescription, - updateSlideBodyString, - updateSlideBodyHeading, - updateSlideBodyDescription, - updateSlideImage, - updateSlideIcon, deletePresentationSlide, - addSlideBodyItem, - addSlideImage, - deleteSlideImage, - deleteSlideBodyItem, - updateSlideProperties, - // infographics - addInfographics, - deleteInfographics, - updateInfographicsTitle, - updateInfographicsDescription, - updateInfographicsChart, + updateSlideContent, } = presentationGenerationSlice.actions; export default presentationGenerationSlice.reducer;