feat: Presentation page redesign
This commit is contained in:
parent
9e54c6bb9e
commit
7abb446d32
8 changed files with 191 additions and 357 deletions
|
|
@ -31,7 +31,7 @@ export const PresentationCard = ({
|
|||
// useFontLoader(presentation.fonts || []);
|
||||
const handlePreview = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
router.push(`/presentation?id=${id}&type=${presentation.type}`);
|
||||
router.push(`/presentation?id=${id}&type=standard`);
|
||||
};
|
||||
|
||||
const handleDelete = async (e: React.MouseEvent) => {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ import { useRouter } from "next/navigation";
|
|||
import { toast } from "sonner";
|
||||
import { clearPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import { Template, LoadingState, TABS } from "../types/index";
|
||||
import { MixpanelEvent, trackEvent } from "@/utils/mixpanel";
|
||||
import { TemplateLayoutsWithSettings } from "@/app/presentation-templates";
|
||||
import { LoadingState, TABS } from "../types/index";
|
||||
import { getCustomTemplateDetails } from "@/app/hooks/useCustomTemplates";
|
||||
import { TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils";
|
||||
|
||||
const DEFAULT_LOADING_STATE: LoadingState = {
|
||||
message: "",
|
||||
|
|
@ -129,7 +128,7 @@ export const usePresentationGeneration = (
|
|||
layout = {
|
||||
name: selectedTemplate.id,
|
||||
ordered: false,
|
||||
slides: selectedTemplate.layouts.map((layoutItem) => ({
|
||||
slides: selectedTemplate.layouts.map((layoutItem: any) => ({
|
||||
id: layoutItem.layoutId,
|
||||
name: layoutItem.layoutName,
|
||||
description: layoutItem.layoutDescription,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import {
|
|||
Loader2,
|
||||
Redo2,
|
||||
Undo2,
|
||||
RotateCcw,
|
||||
ArrowRightFromLine,
|
||||
|
||||
} from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
|
|
@ -37,17 +39,20 @@ import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo";
|
|||
import ToolTip from "@/components/ToolTip";
|
||||
import { clearPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { clearHistory } from "@/store/slices/undoRedoSlice";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const Header = ({
|
||||
const PresentationHeader = ({
|
||||
presentation_id,
|
||||
isPresentationSaving,
|
||||
currentSlide,
|
||||
}: {
|
||||
presentation_id: string;
|
||||
isPresentationSaving: boolean;
|
||||
currentSlide?: number;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [showLoader, setShowLoader] = useState(false);
|
||||
const router = useRouter();
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
|
@ -68,8 +73,7 @@ const Header = ({
|
|||
if (isStreaming) return;
|
||||
|
||||
try {
|
||||
setOpen(false);
|
||||
setShowLoader(true);
|
||||
setIsExporting(true);
|
||||
// Save the presentation data before exporting
|
||||
trackEvent(MixpanelEvent.Header_UpdatePresentationContent_API_Call);
|
||||
await PresentationGenerationApi.updatePresentationContent(presentationData);
|
||||
|
|
@ -88,13 +92,12 @@ const Header = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Export failed:", error);
|
||||
setShowLoader(false);
|
||||
toast.error("Having trouble exporting!", {
|
||||
description:
|
||||
"We are having trouble exporting your presentation. Please try again.",
|
||||
});
|
||||
} finally {
|
||||
setShowLoader(false);
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -102,8 +105,7 @@ const Header = ({
|
|||
if (isStreaming) return;
|
||||
|
||||
try {
|
||||
setOpen(false);
|
||||
setShowLoader(true);
|
||||
setIsExporting(true);
|
||||
// Save the presentation data before exporting
|
||||
trackEvent(MixpanelEvent.Header_UpdatePresentationContent_API_Call);
|
||||
await PresentationGenerationApi.updatePresentationContent(presentationData);
|
||||
|
|
@ -132,7 +134,7 @@ const Header = ({
|
|||
"We are having trouble exporting your presentation. Please try again.",
|
||||
});
|
||||
} finally {
|
||||
setShowLoader(false);
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
const handleReGenerate = () => {
|
||||
|
|
@ -182,117 +184,79 @@ const Header = ({
|
|||
</div>
|
||||
);
|
||||
|
||||
const MenuItems = ({ mobile }: { mobile: boolean }) => (
|
||||
<div className="flex flex-col lg:flex-row items-center gap-4">
|
||||
{/* undo redo */}
|
||||
<button onClick={handleReGenerate} disabled={isStreaming || !presentationData} className="text-white disabled:opacity-50" >
|
||||
|
||||
Re-Generate
|
||||
</button>
|
||||
<div className="flex items-center gap-2 ">
|
||||
<ToolTip content="Undo">
|
||||
<button disabled={!canUndo} className="text-white disabled:opacity-50" onClick={() => {
|
||||
onUndo();
|
||||
}}>
|
||||
|
||||
<Undo2 className="w-6 h-6 " />
|
||||
|
||||
</button>
|
||||
</ToolTip>
|
||||
<ToolTip content="Redo">
|
||||
|
||||
<button disabled={!canRedo} className="text-white disabled:opacity-50" onClick={() => {
|
||||
onRedo();
|
||||
}}>
|
||||
<Redo2 className="w-6 h-6 " />
|
||||
|
||||
</button>
|
||||
</ToolTip>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Present Button */}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const to = `?id=${presentation_id}&mode=present&slide=${currentSlide || 0}`;
|
||||
trackEvent(MixpanelEvent.Navigation, { from: pathname, to });
|
||||
router.push(to);
|
||||
}}
|
||||
variant="ghost"
|
||||
className="border border-white font-bold text-white rounded-[32px] transition-all duration-300 group"
|
||||
>
|
||||
<Play className="w-4 h-4 mr-1 stroke-white group-hover:stroke-black" />
|
||||
Present
|
||||
</Button>
|
||||
|
||||
{/* Desktop Export Button with Popover */}
|
||||
|
||||
<div style={{
|
||||
zIndex: 100
|
||||
}} className="hidden lg:block relative ">
|
||||
<Popover open={open} onOpenChange={setOpen} >
|
||||
<PopoverTrigger asChild>
|
||||
<Button className={`border py-5 text-[#5146E5] font-bold rounded-[32px] transition-all duration-500 hover:border hover:bg-[#5146E5] hover:text-white w-full ${mobile ? "" : "bg-white"}`}>
|
||||
<SquareArrowOutUpRight className="w-4 h-4 mr-1" />
|
||||
Export
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-[250px] space-y-2 py-3 px-2 ">
|
||||
<ExportOptions mobile={false} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
{/* Mobile Export Section */}
|
||||
<div className="lg:hidden flex flex-col w-full">
|
||||
<ExportOptions mobile={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlayLoader
|
||||
show={showLoader}
|
||||
text="Exporting presentation..."
|
||||
showProgress={true}
|
||||
duration={40}
|
||||
/>
|
||||
<div
|
||||
<div className="py-7 sticky top-0 bg-white z-50 mb-[17px] pr-[25px] flex justify-between items-center">
|
||||
<h2 className="text-[28px] text-[#101323] w-[600px] truncate">{presentationData?.title || "Presentation"}</h2>
|
||||
<div className="flex items-center gap-2.5">
|
||||
|
||||
className="bg-[#5146E5] w-full shadow-lg sticky top-0 ">
|
||||
{isPresentationSaving && <div className="flex items-center gap-2">
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||
</div>}
|
||||
|
||||
<Announcement />
|
||||
<Wrapper className="flex items-center justify-between py-1">
|
||||
<Link href="/dashboard" className="min-w-[162px]">
|
||||
<img
|
||||
className="h-16"
|
||||
src="/logo-white.png"
|
||||
alt="Presentation logo"
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 bg-[#F6F6F9] px-3.5 h-[38px] border border-[#EDECEC] rounded-[80px]">
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div className="hidden lg:flex items-center gap-4 2xl:gap-6">
|
||||
{isStreaming && (
|
||||
<Loader2 className="animate-spin text-white font-bold w-6 h-6" />
|
||||
)}
|
||||
<ToolTip content="Regenerate Presentation">
|
||||
<button onClick={handleReGenerate} className="group">
|
||||
<RotateCcw className="w-3.5 h-3.5 text-[#101323] group-hover:text-[#5141e5] duration-300" />
|
||||
</button>
|
||||
</ToolTip>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<ToolTip content="Undo">
|
||||
<button disabled={!canUndo} className=" disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer group" onClick={() => {
|
||||
onUndo();
|
||||
}}>
|
||||
|
||||
<Undo2 className="w-3.5 h-3.5 text-[#101323] group-hover:text-[#5141e5] duration-300" />
|
||||
|
||||
<MenuItems mobile={false} />
|
||||
<HeaderNav />
|
||||
</button>
|
||||
</ToolTip>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<ToolTip content="Redo">
|
||||
|
||||
<button disabled={!canRedo} className=" disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer group" onClick={() => {
|
||||
|
||||
onRedo();
|
||||
}}>
|
||||
<Redo2 className="w-3.5 h-3.5 text-[#101323] group-hover:text-[#5141e5] duration-300" />
|
||||
|
||||
</button>
|
||||
</ToolTip>
|
||||
<Separator orientation="vertical" className="h-4 w-[2px]" />
|
||||
<ToolTip content="Present">
|
||||
<button
|
||||
onClick={() => {
|
||||
const to = `?id=${presentation_id}&mode=present&slide=${currentSlide || 0}`;
|
||||
trackEvent(MixpanelEvent.Navigation, { from: pathname, to });
|
||||
router.push(to);
|
||||
}}
|
||||
disabled={!presentationData?.slides || presentationData?.slides.length === 0} className="cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed group">
|
||||
<Play className="w-3.5 h-3.5 text-[#101323] group-hover:text-[#5141e5] duration-300" />
|
||||
</button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div className="lg:hidden flex items-center gap-4">
|
||||
<HeaderNav />
|
||||
|
||||
</div>
|
||||
</Wrapper>
|
||||
|
||||
<Popover open={open} onOpenChange={setOpen} >
|
||||
<PopoverTrigger asChild>
|
||||
<button className="flex items-center gap-[7px] px-[18px] py-[11px] rounded-[53px] text-sm font-semibold text-[#101323]"
|
||||
style={{
|
||||
background: "linear-gradient(270deg, #D5CAFC 2.4%, #E3D2EB 27.88%, #F4DCD3 69.23%, #FDE4C2 100%)",
|
||||
}}
|
||||
disabled={isExporting}
|
||||
>
|
||||
{isExporting ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : "Export"} <ArrowRightFromLine />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-[250px] space-y-2 py-3 px-2 ">
|
||||
<ExportOptions mobile={false} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
export default PresentationHeader;
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import PresentationMode from "../../components/PresentationMode";
|
||||
import SidePanel from "./SidePanel";
|
||||
import SlideContent from "./SlideContent";
|
||||
import Header from "./Header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
||||
import { AlertCircle, Loader2 } from "lucide-react";
|
||||
import Help from "./Help";
|
||||
import {
|
||||
usePresentationStreaming,
|
||||
usePresentationData,
|
||||
|
|
@ -21,8 +19,9 @@ import {
|
|||
import { PresentationPageProps } from "../types";
|
||||
import LoadingState from "./LoadingState";
|
||||
|
||||
import { useFontLoader } from "../../hooks/useFontLoader";
|
||||
import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo";
|
||||
import PresentationHeader from "./PresentationHeader";
|
||||
|
||||
const PresentationPage: React.FC<PresentationPageProps> = ({
|
||||
presentation_id,
|
||||
}) => {
|
||||
|
|
@ -32,7 +31,6 @@ const PresentationPage: React.FC<PresentationPageProps> = ({
|
|||
const [selectedSlide, setSelectedSlide] = useState(0);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [isMobilePanelOpen, setIsMobilePanelOpen] = useState(false);
|
||||
|
||||
|
||||
const { presentationData, isStreaming } = useSelector(
|
||||
|
|
@ -123,63 +121,64 @@ const PresentationPage: React.FC<PresentationPageProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden flex-col">
|
||||
<div className="fixed right-6 top-[5.2rem] z-50">
|
||||
{isSaving && <Loader2 className="w-6 h-6 animate-spin text-blue-500" />}
|
||||
</div>
|
||||
|
||||
<Header presentation_id={presentation_id} currentSlide={selectedSlide} />
|
||||
<Help />
|
||||
|
||||
<div className="h-screen overflow-hidden ">
|
||||
<div
|
||||
style={{
|
||||
background: "#c8c7c9",
|
||||
background: "#ffffff",
|
||||
}}
|
||||
className="flex flex-1 relative pt-6"
|
||||
className="flex gap-6 relative "
|
||||
>
|
||||
<SidePanel
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={handleSlideClick}
|
||||
loading={loading}
|
||||
isMobilePanelOpen={isMobilePanelOpen}
|
||||
setIsMobilePanelOpen={setIsMobilePanelOpen}
|
||||
/>
|
||||
<div className="w-[200px]">
|
||||
<SidePanel
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={handleSlideClick}
|
||||
loading={loading}
|
||||
|
||||
<div className="flex-1 h-[calc(100vh-100px)] overflow-y-auto">
|
||||
/>
|
||||
</div>
|
||||
<div className=" w-full h-[calc(100vh-20px)] overflow-y-auto">
|
||||
<PresentationHeader presentation_id={presentation_id} isPresentationSaving={isSaving} currentSlide={selectedSlide} />
|
||||
<div
|
||||
id="presentation-slides-wrapper"
|
||||
className="mx-auto flex flex-col items-center overflow-hidden justify-center p-2 sm:p-6 pt-0"
|
||||
style={{
|
||||
background: "rgba(255, 255, 255, 0.10)",
|
||||
boxShadow: "0 0 20.01px 0 rgba(122, 90, 248, 0.16) inset",
|
||||
}}
|
||||
className="p-6 rounded-[20px] flex flex-col items-center overflow-hidden justify-center border border-[#EDECEC] "
|
||||
>
|
||||
{!presentationData ||
|
||||
loading ||
|
||||
!presentationData?.slides ||
|
||||
presentationData?.slides.length === 0 ? (
|
||||
<div className="relative w-full h-[calc(100vh-120px)] mx-auto">
|
||||
<div className="">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={index}
|
||||
className="aspect-video bg-gray-400 my-4 w-full mx-auto max-w-[1280px]"
|
||||
/>
|
||||
))}
|
||||
<div className="w-full max-w-[1280px] h-full">
|
||||
|
||||
{!presentationData ||
|
||||
loading ||
|
||||
!presentationData?.slides ||
|
||||
presentationData?.slides.length === 0 ? (
|
||||
<div className="relative w-full h-[calc(100vh-120px)] mx-auto">
|
||||
<div className="">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={index}
|
||||
className="aspect-video bg-gray-400 my-4 w-full mx-auto "
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{stream && <LoadingState />}
|
||||
</div>
|
||||
{stream && <LoadingState />}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{presentationData &&
|
||||
presentationData.slides &&
|
||||
presentationData.slides.length > 0 &&
|
||||
presentationData.slides.map((slide: any, index: number) => (
|
||||
<SlideContent
|
||||
key={`${slide.type}-${index}-${slide.index}`}
|
||||
slide={slide}
|
||||
index={index}
|
||||
presentationId={presentation_id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
) : (
|
||||
<>
|
||||
{presentationData &&
|
||||
presentationData.slides &&
|
||||
presentationData.slides.length > 0 &&
|
||||
presentationData.slides.map((slide: any, index: number) => (
|
||||
<SlideContent
|
||||
key={`${slide.type}-${index}-${slide.index}`}
|
||||
slide={slide}
|
||||
index={index}
|
||||
presentationId={presentation_id}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { LayoutList, ListTree, PanelRightOpen, X } from "lucide-react";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React, { } from "react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import {
|
||||
|
|
@ -21,26 +19,25 @@ import {
|
|||
} from "@dnd-kit/sortable";
|
||||
import { setPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { SortableSlide } from "./SortableSlide";
|
||||
import { SortableListItem } from "./SortableListItem";
|
||||
import SlideScale from "../../components/PresentationRender";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface SidePanelProps {
|
||||
selectedSlide: number;
|
||||
onSlideClick: (index: number) => void;
|
||||
isMobilePanelOpen: boolean;
|
||||
setIsMobilePanelOpen: (value: boolean) => void;
|
||||
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const SidePanel = ({
|
||||
selectedSlide,
|
||||
onSlideClick,
|
||||
isMobilePanelOpen,
|
||||
setIsMobilePanelOpen,
|
||||
|
||||
loading,
|
||||
}: SidePanelProps) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [active, setActive] = useState<"list" | "grid">("grid");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { presentationData, isStreaming } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
|
|
@ -50,11 +47,7 @@ const SidePanel = ({
|
|||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth < 768) {
|
||||
setIsOpen(isMobilePanelOpen);
|
||||
}
|
||||
}, [isMobilePanelOpen]);
|
||||
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
|
|
@ -67,12 +60,7 @@ const SidePanel = ({
|
|||
})
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
if (window.innerWidth < 768) {
|
||||
setIsMobilePanelOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleDragEnd = (event: any) => {
|
||||
const { active, over } = event;
|
||||
|
|
@ -119,196 +107,80 @@ const SidePanel = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop Toggle Button - Always visible when panel is closed */}
|
||||
{!isOpen && (
|
||||
<div className="hidden xl:block fixed left-4 top-1/2 -translate-y-1/2 z-50">
|
||||
<ToolTip content="Open Panel">
|
||||
<Button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="bg-white hover:bg-gray-50 shadow-lg"
|
||||
>
|
||||
<PanelRightOpen className="text-black" size={20} />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-[#F6F6F9] pt-8 px-4 w-[200px]">
|
||||
|
||||
{/* Mobile Toggle Button */}
|
||||
{!isMobilePanelOpen && (
|
||||
<div className="xl:hidden fixed left-4 bottom-4 z-50">
|
||||
<ToolTip content="Show Panel">
|
||||
<Button
|
||||
onClick={() => setIsMobilePanelOpen(true)}
|
||||
className="bg-[#5146E5] text-white p-3 rounded-full shadow-lg"
|
||||
>
|
||||
<PanelRightOpen className="text-white" size={20} />
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
<img onClick={() => {
|
||||
router.push("/dashboard");
|
||||
}} src="/logo-with-bg.png" alt="" className="w-10 h-10 cursor-pointer object-contain" />
|
||||
|
||||
<Separator orientation="horizontal" className="my-6 " />
|
||||
<div
|
||||
className={`
|
||||
fixed xl:relative h-full z-50 xl:z-auto
|
||||
transition-all duration-300 ease-in-out
|
||||
${isOpen ? "ml-0" : "-ml-[300px]"}
|
||||
${isMobilePanelOpen
|
||||
? "translate-x-0"
|
||||
: "-translate-x-full xl:translate-x-0"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
|
||||
className="min-w-[300px] bg-white max-w-[300px] h-[calc(100vh-120px)] rounded-[20px] hide-scrollbar overflow-hidden slide-theme shadow-xl"
|
||||
className="w-full h-[calc(100vh-120px)] hide-scrollbar overflow-hidden slide-theme "
|
||||
>
|
||||
<div
|
||||
className="sticky top-0 z-40 px-6 py-4"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center justify-start gap-4">
|
||||
<ToolTip content="Image Preview">
|
||||
<Button
|
||||
className={`${active === "grid"
|
||||
? "bg-[#5141e5] hover:bg-[#4638c7]"
|
||||
: "bg-white hover:bg-white"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!isStreaming) {
|
||||
setActive("grid")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LayoutList
|
||||
className={`${active === "grid" ? "text-white" : "text-black"
|
||||
}`}
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
<ToolTip content="List Preview">
|
||||
<Button
|
||||
className={`${active === "list"
|
||||
? "bg-[#5141e5] hover:bg-[#4638c7]"
|
||||
: "bg-white hover:bg-white"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!isStreaming) {
|
||||
setActive("list")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListTree
|
||||
className={`${active === "list" ? "text-white" : "text-black"
|
||||
}`}
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
<X
|
||||
onClick={handleClose}
|
||||
className="text-[#6c7081] cursor-pointer hover:text-gray-600"
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xl font-normal pb-3.5 text-[#000000]">Slides</p>
|
||||
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
{/* List Preview */}
|
||||
{active === "list" && (
|
||||
<div className="p-4 overflow-y-auto hide-scrollbar h-[calc(100%-100px)]">
|
||||
{isStreaming ? (
|
||||
presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${slide.type}-${slide.id}`}
|
||||
className={`p-3 cursor-pointer rounded-lg slide-box`}
|
||||
>
|
||||
<span className="font-medium slide-title">
|
||||
Slide {index + 1}
|
||||
</span>
|
||||
<p className="text-sm slide-description">
|
||||
{slide.content.title}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<SortableContext
|
||||
items={
|
||||
presentationData?.slides.map((slide: any) => slide.id!) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
<div className=" overflow-y-auto hide-scrollbar h-[calc(100%-140px)] space-y-3.5">
|
||||
{isStreaming ? (
|
||||
presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<div
|
||||
key={`${slide.id}-${index}`}
|
||||
onClick={() => onSlideClick(index)}
|
||||
className={` cursor-pointer ring-2 rounded-[12px] transition-all duration-200 ${selectedSlide === index ? ' ring-[#5141e5]' : 'ring-gray-200'
|
||||
}`}
|
||||
>
|
||||
<div className="space-y-2" id={`slide-${selectedSlide}`}>
|
||||
{presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<SortableListItem
|
||||
key={`${slide.id}-${index}`}
|
||||
slide={slide}
|
||||
index={index}
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={onSlideClick}
|
||||
/>
|
||||
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Grid Preview */}
|
||||
{active === "grid" && (
|
||||
<div className="p-4 overflow-y-auto hide-scrollbar h-[calc(100%-100px)] space-y-4">
|
||||
{isStreaming ? (
|
||||
presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<div
|
||||
key={`${slide.id}-${index}`}
|
||||
onClick={() => onSlideClick(index)}
|
||||
className={` cursor-pointer ring-2 p-1 rounded-md transition-all duration-200 ${selectedSlide === index ? ' ring-[#5141e5]' : 'ring-gray-200'
|
||||
}`}
|
||||
>
|
||||
<div className=" bg-white pointer-events-none relative overflow-hidden aspect-video">
|
||||
<div className="absolute bg-gray-100/5 z-50 top-0 left-0 w-full h-full" />
|
||||
<div className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]">
|
||||
<SlideScale slide={slide} />
|
||||
</div>
|
||||
<div className=" bg-white pointer-events-none relative overflow-hidden aspect-video">
|
||||
<div className="absolute bg-gray-100/5 z-50 top-0 left-0 w-full h-full" />
|
||||
<div className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]">
|
||||
<SlideScale slide={slide} />
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<SortableContext
|
||||
items={
|
||||
presentationData?.slides.map((slide: any) => slide.id || `${slide.index}`) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<SortableSlide
|
||||
key={`${slide.id}-${index}`}
|
||||
slide={slide}
|
||||
index={index}
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={onSlideClick}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<SortableContext
|
||||
items={
|
||||
presentationData?.slides.map((slide: any) => slide.id || `${slide.index}`) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{presentationData &&
|
||||
presentationData?.slides.map((slide: any, index: number) => (
|
||||
<SortableSlide
|
||||
key={`${slide.id}-${index}`}
|
||||
slide={slide}
|
||||
index={index}
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={onSlideClick}
|
||||
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
)}
|
||||
</div>
|
||||
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DndContext>
|
||||
|
||||
<button className=" pt-6 gap-2 flex flex-col py-2 duration-300 items-center justify-center rounded-lg cursor-pointer mx-auto">
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
<span className="text-[11px] font-normal text-[#000000]">Add Slide</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ const SlideContent = ({ slide, index, presentationId }: SlideContentProps) => {
|
|||
<>
|
||||
<div
|
||||
id={`slide-${slide.index}`}
|
||||
className=" w-full max-w-[1280px] main-slide flex items-center max-md:mb-4 justify-center relative"
|
||||
className=" w-full main-slide flex items-center max-md:mb-4 justify-center relative"
|
||||
>
|
||||
{isStreaming && (
|
||||
<Loader2 className="w-8 h-8 absolute right-2 top-2 z-30 text-blue-800 animate-spin" />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ interface SortableSlideProps {
|
|||
selectedSlide: number;
|
||||
onSlideClick: (index: any) => void;
|
||||
}
|
||||
const SCALE = 0.2;
|
||||
const SCALE = 0.125;
|
||||
|
||||
export function SortableSlide({ slide, index, selectedSlide, onSlideClick }: SortableSlideProps) {
|
||||
const searchParams = useSearchParams();
|
||||
|
|
@ -55,7 +55,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick }: Sor
|
|||
{...attributes}
|
||||
{...listeners}
|
||||
onClick={handleClick}
|
||||
className={` cursor-pointer border-[3px] relative p-1 shadow-lg rounded-md transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-gray-300'
|
||||
className={` cursor-pointer border relative p-1 rounded-[12px] transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-[#EDEEEF]'
|
||||
}`}
|
||||
>
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ const UploadPage = () => {
|
|||
onConfigChange={handleConfigChange}
|
||||
/>
|
||||
</div> */}
|
||||
<div className=" w-full mx-auto px-2 md:px-0 max-w-[720px] ">
|
||||
<div className=" w-full mx-auto px-2 md:px-0 max-w-[780px] ">
|
||||
|
||||
<div
|
||||
className='fixed z-0 md:-bottom-[36%] -bottom-[40%] left-0 w-full h-full'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue