Merge branch 'pdf-pptx-layout' of https://github.com/presenton/presenton into pdf-pptx-layout

merge
This commit is contained in:
Suraj Jha 2025-08-02 21:40:04 +05:45
commit 39ca51f95d
7 changed files with 80 additions and 49 deletions

View file

@ -96,6 +96,31 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
wordBreak: computedStyles.wordBreak,
overflow: computedStyles.overflow,
textAlignLast: computedStyles.textAlignLast,
position: computedStyles.position,
top: computedStyles.top,
left: computedStyles.left,
right: computedStyles.right,
bottom: computedStyles.bottom,
display: computedStyles.display,
flexDirection: computedStyles.flexDirection,
flexWrap: computedStyles.flexWrap,
flexGrow: computedStyles.flexGrow,
flexShrink: computedStyles.flexShrink,
flexBasis: computedStyles.flexBasis,
alignItems: computedStyles.alignItems,
justifyContent: computedStyles.justifyContent,
gap: computedStyles.gap,
gridTemplateColumns: computedStyles.gridTemplateColumns,
gridTemplateRows: computedStyles.gridTemplateRows,
gridTemplateAreas: computedStyles.gridTemplateAreas,
gridTemplate: computedStyles.gridTemplate,
gridAutoFlow: computedStyles.gridAutoFlow,
gridAutoColumns: computedStyles.gridAutoColumns,
gridAutoRows: computedStyles.gridAutoRows,
gridColumn: computedStyles.gridColumn,
gridRow: computedStyles.gridRow,
gridArea: computedStyles.gridArea,
grid: computedStyles.grid,
};
// Try to find matching data path
const dataPath = findDataPath(slideData, trimmedText);

View file

@ -81,8 +81,11 @@ const createCacheKey = (groupName: string, fileName: string): string =>
const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
const cleanCode = layoutCode
.replace(/import\s+React\s+from\s+'react';?/g, "")
.replace(/import\s*{\s*z\s*}\s*from\s+'zod';?/g, "");
.replace(/import\s*{\s*z\s*}\s*from\s+'zod';?/g, "")
.replace(/import\s+.*\s+from\s+['"]zod['"];?/g, "")
// remove every zod import (any style)
.replace(/import\s+.*\s+from\s+['"]zod['"];?/g, "")
.replace(/const\s+[^=]*=\s*require\(['"]zod['"]\);?/g, "");
const compiled = Babel.transform(cleanCode, {
presets: [
["react", { runtime: "classic" }],
@ -93,8 +96,9 @@ const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
const factory = new Function(
"React",
"z",
"_z",
`
const z = _z;
${compiled}
/* everything declared in the string is in scope here */
@ -108,7 +112,7 @@ const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
};
`
);
// globalThis.z = z;
return factory(React, z);
};
@ -120,6 +124,7 @@ export const LayoutProvider: React.FC<{
const [error, setError] = useState<string | null>(null);
const [isPreloading, setIsPreloading] = useState(false);
const dispatch = useDispatch();
console.log("🔍 layoutData", layoutData);
const buildData = async (groupedLayoutsData: GroupedLayoutsResponse[]) => {
const layouts: LayoutInfo[] = [];
@ -343,6 +348,7 @@ export const LayoutProvider: React.FC<{
const customGroup = customGroupData.presentations;
console.log("🔍 customGroup", customGroup);
for (const group of customGroup) {
console.log("🔍 group", group);
const groupName = `custom-${group.presentation_id}`;
fullDataByGroup.set(groupName, []);
if (!layoutsByGroup.has(groupName)) {

View file

@ -1,7 +1,7 @@
import React from 'react'
import Header from '@/app/dashboard/components/Header'
import { Metadata } from 'next'
import OutlinePage from './components/OutlinePage'
import Header from '@/components/Header'
export const metadata: Metadata = {
title: "Outline Presentation",
description: "Customize and organize your presentation outline. Drag and drop slides, add charts, and generate your presentation with ease.",

View file

@ -1,8 +1,13 @@
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { clearPresentationData, setPresentationData, setStreaming } from "@/store/slices/presentationGeneration";
import {
clearPresentationData,
setPresentationData,
setStreaming,
} from "@/store/slices/presentationGeneration";
import { jsonrepair } from "jsonrepair";
import { RootState } from "@/store/store";
import { toast } from "sonner";
export const usePresentationStreaming = (
presentationId: string,
@ -11,7 +16,9 @@ export const usePresentationStreaming = (
setError: (error: boolean) => void,
fetchUserSlides: () => void
) => {
const { presentationData } = useSelector((state: RootState) => state.presentationGeneration);
const { presentationData } = useSelector(
(state: RootState) => state.presentationGeneration
);
const dispatch = useDispatch();
const previousSlidesLength = useRef(0);
@ -64,7 +71,7 @@ export const usePresentationStreaming = (
dispatch(setStreaming(false));
setLoading(false);
eventSource.close();
// Remove stream parameter from URL
const newUrl = new URL(window.location.href);
newUrl.searchParams.delete("stream");
@ -81,12 +88,20 @@ export const usePresentationStreaming = (
setLoading(false);
dispatch(setStreaming(false));
eventSource.close();
// Remove stream parameter from URL
const newUrl = new URL(window.location.href);
newUrl.searchParams.delete("stream");
window.history.replaceState({}, "", newUrl.toString());
break;
case "error":
eventSource.close();
toast.error("Error in outline streaming", {
description:
data.detail ||
"Failed to connect to the server. Please try again.",
});
break;
}
});
@ -102,7 +117,7 @@ export const usePresentationStreaming = (
if (stream) {
initializeStream();
} else {
if(!presentationData || presentationData.slides.length === 0){
if (!presentationData || presentationData.slides.length === 0) {
fetchUserSlides();
}
}
@ -113,4 +128,4 @@ export const usePresentationStreaming = (
}
};
}, [presentationId, stream, dispatch, setLoading, setError, fetchUserSlides]);
};
};

View file

@ -88,11 +88,11 @@ const FontManager: React.FC<FontManagerProps> = ({
}
return (
<Card className="mb-6">
<Card className="my-6">
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<Type className="w-6 h-6" />
Global Font Management
Font Management
</CardTitle>
<p className="text-sm text-gray-600">
Manage fonts across all slides. Upload fonts once and they'll be
@ -158,7 +158,7 @@ const FontManager: React.FC<FontManagerProps> = ({
variant="outline"
disabled={uploadingFonts.has(fontName)}
onClick={() => fileInputRefs.current[fontName]?.click()}
className="text-xs bg-blue-600 text-white hover:bg-blue-700 border-blue-600"
className="text-xs bg-blue-600 text-white hover:text-white hover:bg-blue-700 border-blue-600"
>
{uploadingFonts.has(fontName) ? (
<>

View file

@ -18,6 +18,7 @@ import { v4 as uuidv4 } from "uuid";
import EachSlide from "./components/EachSlide";
import FontManager from "./components/FontManager";
import Header from "@/components/Header";
import { useLayout } from "../(presentation-generator)/context/LayoutContext";
// Types
interface SlideData {
@ -50,6 +51,7 @@ interface FontData {
}
const CustomLayoutPage = () => {
const { refetch } = useLayout();
// State management
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isProcessingPptx, setIsProcessingPptx] = useState(false);
@ -311,6 +313,7 @@ const CustomLayoutPage = () => {
);
toast.success(`Layout saved successfully`);
refetch();
setIsLayoutSaved(true);
} catch (error) {
console.error("Error saving layout:", error);
@ -559,8 +562,6 @@ const CustomLayoutPage = () => {
const completedSlides = slides.filter(
(slide) => slide.processed || slide.error
).length;
const progressPercentage =
slides.length > 0 ? Math.round((completedSlides / slides.length) * 100) : 0;
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 ">
@ -577,17 +578,6 @@ const CustomLayoutPage = () => {
</p>
</div>
{/* Global Font Management */}
{fontsData && (
<FontManager
fontsData={fontsData}
UploadedFonts={UploadedFonts}
uploadFont={uploadFont}
removeFont={removeFont}
getAllUnsupportedFonts={getAllUnsupportedFonts}
/>
)}
{/* Upload Section */}
<Card className="w-full">
<CardHeader>
@ -599,6 +589,14 @@ const CustomLayoutPage = () => {
Select a PowerPoint file (.pptx) to process. Maximum file size:
50MB
</CardDescription>
{slides.length > 0 && (
<div className="flex items-center justify-end gap-2">
{slides.some((s) => s.processing) && (
<Loader2 className="w-6 h-6 animate-spin text-blue-600" />
)}
{completedSlides}/{slides.length} slides completed
</div>
)}
</CardHeader>
<CardContent className="space-y-4">
{!selectedFile ? (
@ -663,28 +661,15 @@ const CustomLayoutPage = () => {
</CardContent>
</Card>
{/* Progress Section */}
{slides.length > 0 && (
<Card className="mt-10">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Processing Progress</span>
<span className="text-sm font-normal text-gray-600">
{completedSlides}/{slides.length} slides completed
</span>
</CardTitle>
<CardDescription>
Converting slides to HTML layouts...
</CardDescription>
</CardHeader>
<CardContent>
<Progress value={progressPercentage} className="w-full" />
<div className="flex justify-between text-sm text-gray-600 mt-2">
<span>Progress: {progressPercentage}%</span>
<span>{slides.length} total slides</span>
</div>
</CardContent>
</Card>
{/* Global Font Management */}
{fontsData && (
<FontManager
fontsData={fontsData}
UploadedFonts={UploadedFonts}
uploadFont={uploadFont}
removeFont={removeFont}
getAllUnsupportedFonts={getAllUnsupportedFonts}
/>
)}
{/* Slides Section */}