Merge branch 'pdf-pptx-layout' of https://github.com/presenton/presenton into pdf-pptx-layout
merge
This commit is contained in:
commit
e6f166a29c
3 changed files with 90 additions and 57 deletions
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue