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-01 13:44:40 +05:45
commit e6f166a29c
3 changed files with 90 additions and 57 deletions

View file

@ -11,6 +11,7 @@ import {
RotateCcw,
SendHorizontal,
X,
Repeat2,
} from "lucide-react";
import React, { useState, useEffect, useRef, useCallback } from "react";
import ToolTip from "@/components/ToolTip";
@ -391,32 +392,63 @@ const EachSlide = ({
const strokeWidths = [1, 3, 5, 8, 12];
return (
<Card key={slide.slide_number} className="border-2 w-full relative">
<CardHeader className="pb-4">
<Card
key={slide.slide_number}
className="border-2 font-instrument_sans w-full relative"
>
<CardHeader className=" max-w-[1280px] mx-auto px-0 py-6">
<CardTitle className="text-xl flex items-center justify-between">
{slide.processing ? (
<Loader2 className="w-6 h-6 text-blue-600 animate-spin" />
) : slide.processed ? (
<CheckCircle className="w-6 h-6 text-green-600" />
) : slide.error ? (
<AlertCircle className="w-6 h-6 text-red-600" />
) : (
<div className="w-6 h-6 border-2 border-gray-300 rounded-full" />
)}
<div className="flex items-center w-full justify-between gap-2">
<div>
{slide.processing ? (
<Loader2 className="w-6 h-6 text-blue-600 animate-spin" />
) : slide.processed ? (
<CheckCircle className="w-6 h-6 text-green-600" />
) : slide.error ? (
<AlertCircle className="w-6 h-6 text-red-600" />
) : (
<div className="w-6 h-6 border-2 border-gray-300 rounded-full" />
)}
</div>
<div className="flex gap-6">
{slide.processed && slide.html && !isEditMode && (
<div className=" ">
<ToolTip content="Edit slide">
<button
onClick={handleEditClick}
className={`px-6 py-2 flex gap-2 text-sm items-center group-hover:scale-105 rounded-lg bg-[#5141e5] hover:shadow-md transition-all duration-300 cursor-pointer shadow-md `}
>
<Edit className="w-4 sm:w-5 h-4 sm:h-5 text-white" />
<span className="text-white">Edit Slide</span>
</button>
</ToolTip>
</div>
)}
<div>
<ToolTip content="Retry fetch">
<button
onClick={() => retrySlide(index)}
disabled={slide.processing}
className="px-6 py-2 flex gap-2 text-sm items-center group-hover:scale-105 rounded-lg bg-[#5141e5] hover:shadow-md transition-all duration-300 cursor-pointer shadow-md"
>
<Repeat2 className="w-4 sm:w-5 h-4 sm:h-5 text-white" />
<span className="text-white">Retry Fetch</span>
</button>
</ToolTip>
</div>
</div>
</div>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Edit Mode Controls */}
{isEditMode && slide.processed && slide.html && (
<div className="border-2 border-blue-200 rounded-lg p-4 bg-blue-50 space-y-4">
<div className="border-2 max-w-[1280px] mx-auto border-blue-200 rounded-lg p-4 bg-blue-50 space-y-4">
{/* Drawing Tools */}
<div className="flex items-center justify-between flex-wrap gap-4">
<div className="flex items-center gap-4 flex-wrap">
<h4 className="text-sm font-semibold text-blue-800">
Edit Mode
</h4>
{/* Drawing Tools */}
<div className="flex items-center gap-2">
<Button
@ -504,10 +536,10 @@ const EachSlide = ({
</div>
{/* Prompt Section */}
<div className="space-y-2">
<div className="space-y-2 mt-2">
<label
htmlFor="edit-prompt"
className="text-sm font-medium text-gray-700"
className="text-sm font-medium font-inter text-gray-700"
>
Describe the changes you want to make:
</label>
@ -517,23 +549,25 @@ const EachSlide = ({
placeholder="Enter your prompt here... (e.g., 'Change the title color to blue', 'Add a border to the image', etc.)"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className="flex-1 min-h-[60px] max-h-[60px] resize-none"
className="flex-1 font-inter duration-300 h-[70px] border-blue-200 border-2 rounded-lg outline-none focus:border-blue-500 focus:ring-0 max-h-[70px] resize-none "
disabled={isUpdating}
/>
<Button
onClick={handleSave}
disabled={isUpdating || !prompt.trim()}
className="flex items-center gap-1 bg-green-600 hover:bg-green-700 px-4"
>
{isUpdating ? (
"Updating..."
) : (
<>
<SendHorizontal size={14} />
Update
</>
)}
</Button>
<div>
<Button
onClick={handleSave}
disabled={isUpdating || !prompt.trim()}
className="flex flex-col w-28 font-inter font-semibold items-center gap-1 h-full bg-green-600 hover:bg-green-700 px-4"
>
{isUpdating ? (
"Updating..."
) : (
<>
<SendHorizontal size={14} />
Update
</>
)}
</Button>
</div>
</div>
</div>
</div>
@ -662,19 +696,6 @@ const EachSlide = ({
>
Open in new tab
</Button>
{slide.processed && slide.html && !isEditMode && (
<div className="absolute top-2 z-20 sm:top-4 hidden md:block right-2 transition-transform">
<ToolTip content="Edit slide">
<div
onClick={handleEditClick}
className={`px-4 py-2 group-hover:scale-105 rounded-lg bg-[#5141e5] hover:shadow-md transition-all duration-300 cursor-pointer shadow-md `}
>
<Edit className="w-4 sm:w-5 h-4 sm:h-5 text-white" />
</div>
</ToolTip>
</div>
)}
</div>
</Card>
);

View file

@ -1,10 +1,16 @@
import React, { memo } from "react";
const SlideContent = memo(({ slide }: { slide: any }) => {
const cleanHtml = slide.html
.replace(/```html/g, "")
.replace(/```/g, "")
.replace(/<html>/g, "")
.replace(/<\/html>/g, "")
.replace(/html/g, "");
return (
<div
dangerouslySetInnerHTML={{
__html: slide.html,
__html: cleanHtml,
}}
/>
);

View file

@ -39,6 +39,7 @@ const CustomLayoutPage = () => {
const [isProcessingPptx, setIsProcessingPptx] = useState(false);
const [slides, setSlides] = useState<ProcessedSlide[]>([]);
const [isSavingLayout, setIsSavingLayout] = useState(false);
const [isLayoutSaved, setIsLayoutSaved] = useState(false);
// Warning before page unload
useEffect(() => {
@ -46,10 +47,11 @@ const CustomLayoutPage = () => {
e.preventDefault();
return "You have unsaved changes. Are you sure you want to leave?";
};
window.addEventListener("beforeunload", handleBeforeUnload);
if (slides.length > 0 && !isLayoutSaved) {
window.addEventListener("beforeunload", handleBeforeUnload);
}
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, []);
}, [slides, isLayoutSaved]);
// Save layout functionality
const saveLayout = useCallback(async () => {
@ -134,6 +136,7 @@ const CustomLayoutPage = () => {
);
toast.success(`Layout saved successfully`);
setIsLayoutSaved(true);
} catch (error) {
console.error("Error saving layout:", error);
toast.error("Failed to save layout", {
@ -181,6 +184,7 @@ const CustomLayoutPage = () => {
// Process individual slide to HTML
const processSlideToHtml = useCallback(
async (slide: SlideData, index: number) => {
setIsLayoutSaved(false);
console.log(
`Starting to process slide ${slide.slide_number} at index ${index}`
);
@ -354,6 +358,7 @@ const CustomLayoutPage = () => {
// Retry failed slide
const retrySlide = useCallback(
(index: number) => {
setIsLayoutSaved(false);
const slide = slides[index];
if (slide) {
processSlideToHtml(slide, index);
@ -365,6 +370,7 @@ const CustomLayoutPage = () => {
// Mark slide as modified when it's updated
const handleSlideUpdate = useCallback(
(index: number, updatedSlideData: any) => {
setIsLayoutSaved(false);
setSlides((prevSlides) =>
prevSlides.map((s, i) =>
i === index
@ -389,9 +395,9 @@ const CustomLayoutPage = () => {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-4">
<div className="max-w-[1440px] aspect-video mx-auto px-6 py-8">
<div className="max-w-[1440px] aspect-video mx-auto px-6 ">
{/* Header */}
<div className="text-center space-y-2">
<div className="text-center space-y-2 mb-6">
<h1 className="text-4xl font-bold text-gray-900">
Custom Layout Processor
</h1>
@ -415,18 +421,18 @@ const CustomLayoutPage = () => {
</CardHeader>
<CardContent className="space-y-4">
{!selectedFile ? (
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
<div className="border-2 relative border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<Label htmlFor="file-upload" className="cursor-pointer">
<span className="text-lg font-medium text-gray-700">
Click to upload a PPTX file
</span>
<Input
<input
id="file-upload"
type="file"
accept=".pptx"
onChange={handleFileSelect}
className="hidden"
className="opacity-0 w-full h-full cursor-pointer absolute top-0 left-0 z-10"
/>
</Label>
<p className="text-sm text-gray-500 mt-2">
@ -520,11 +526,11 @@ const CustomLayoutPage = () => {
{/* Floating Save Layout Button */}
{slides.length > 0 && slides.some((s) => s.processed) && (
<div className="fixed bottom-6 right-6 z-50">
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50">
<Button
onClick={saveLayout}
disabled={isSavingLayout}
className="bg-green-600 hover:bg-green-700 text-white shadow-lg hover:shadow-xl transition-all duration-200 px-6 py-3 text-lg"
className="bg-green-600 hover:bg-green-700 text-white shadow-lg hover:shadow-xl transition-all duration-200 px-10 py-3 text-lg"
size="lg"
>
{isSavingLayout ? (