fix(nextjs): TextEditor Not applying styles issue
This commit is contained in:
parent
d39b1aec5c
commit
04a67014eb
11 changed files with 278 additions and 210 deletions
|
|
@ -1,128 +1,140 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
import { useEditor, EditorContent, BubbleMenu } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
Underline as UnderlinedIcon,
|
||||
Strikethrough,
|
||||
Code,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline as UnderlinedIcon,
|
||||
Strikethrough,
|
||||
Code,
|
||||
} from "lucide-react";
|
||||
|
||||
interface TiptapTextProps {
|
||||
content: string;
|
||||
onContentChange?: (content: string) => void;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div';
|
||||
content: string;
|
||||
onContentChange?: (content: string) => void;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
tag?: "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "P" | "SPAN" | "DIV" | any;
|
||||
}
|
||||
|
||||
const TiptapText: React.FC<TiptapTextProps> = ({
|
||||
content,
|
||||
onContentChange,
|
||||
className = "",
|
||||
placeholder = "Enter text...",
|
||||
tag = 'div',
|
||||
content,
|
||||
onContentChange,
|
||||
className = "",
|
||||
placeholder = "Enter text...",
|
||||
tag = "p",
|
||||
}) => {
|
||||
const editor = useEditor({
|
||||
extensions: [StarterKit, Markdown, Underline],
|
||||
content: content || placeholder,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: `outline-none focus:outline-none transition-all duration-200 ${className}`,
|
||||
'data-placeholder': placeholder,
|
||||
},
|
||||
},
|
||||
onBlur: ({ editor }) => {
|
||||
const markdown = editor?.storage.markdown.getMarkdown();
|
||||
if (onContentChange) {
|
||||
onContentChange(markdown);
|
||||
}
|
||||
},
|
||||
editable: true,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
const editor = useEditor({
|
||||
extensions: [StarterKit, Markdown, Underline],
|
||||
content: content || placeholder,
|
||||
|
||||
// Update editor content when content prop changes
|
||||
useEffect(() => {
|
||||
if (editor && content !== editor.getText()) {
|
||||
editor.commands.setContent(content || placeholder);
|
||||
}
|
||||
}, [content, editor, placeholder]);
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: `outline-none focus:outline-none transition-all duration-200 ${className}`,
|
||||
"data-placeholder": placeholder,
|
||||
},
|
||||
},
|
||||
onBlur: ({ editor }) => {
|
||||
const markdown = editor?.storage.markdown.getMarkdown();
|
||||
if (onContentChange) {
|
||||
onContentChange(markdown);
|
||||
}
|
||||
},
|
||||
editable: true,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
if (!editor) {
|
||||
return <div className={className}>{content || placeholder}</div>;
|
||||
// Update editor content when content prop changes
|
||||
useEffect(() => {
|
||||
if (editor && content !== editor.getText()) {
|
||||
editor.commands.setContent(content || placeholder);
|
||||
}
|
||||
}, [content, editor, placeholder]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BubbleMenu editor={editor} className='z-50' tippyOptions={{ duration: 100 }}>
|
||||
<div style={{
|
||||
zIndex: 100
|
||||
}} className="flex text-black bg-white rounded-lg shadow-lg p-2 gap-1 border border-gray-200 z-50">
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleBold().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("bold") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Bold"
|
||||
>
|
||||
<Bold className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleItalic().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("italic") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Italic"
|
||||
>
|
||||
<Italic className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleUnderline().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("underline") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Underline"
|
||||
>
|
||||
<UnderlinedIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleStrike().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("strike") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Strikethrough"
|
||||
>
|
||||
<Strikethrough className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleCode().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${editor?.isActive("code") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Code"
|
||||
>
|
||||
<Code className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</BubbleMenu>
|
||||
if (!editor) {
|
||||
return <div className={className}>{content || placeholder}</div>;
|
||||
}
|
||||
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className={`tiptap-text-editor w-full`}
|
||||
style={{
|
||||
// Ensure the editor maintains the same visual appearance
|
||||
lineHeight: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontWeight: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
color: 'inherit',
|
||||
textAlign: 'inherit',
|
||||
return (
|
||||
<>
|
||||
<BubbleMenu
|
||||
editor={editor}
|
||||
className="z-50"
|
||||
tippyOptions={{ duration: 100 }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
zIndex: 100,
|
||||
}}
|
||||
className="flex text-black bg-white rounded-lg shadow-lg p-2 gap-1 border border-gray-200 z-50"
|
||||
>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleBold().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${
|
||||
editor?.isActive("bold") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Bold"
|
||||
>
|
||||
<Bold className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleItalic().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${
|
||||
editor?.isActive("italic") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Italic"
|
||||
>
|
||||
<Italic className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleUnderline().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${
|
||||
editor?.isActive("underline") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Underline"
|
||||
>
|
||||
<UnderlinedIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleStrike().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${
|
||||
editor?.isActive("strike") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Strikethrough"
|
||||
>
|
||||
<Strikethrough className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor?.chain().focus().toggleCode().run()}
|
||||
className={`p-1 rounded hover:bg-gray-100 transition-colors ${
|
||||
editor?.isActive("code") ? "bg-blue-100 text-blue-600" : ""
|
||||
}`}
|
||||
title="Code"
|
||||
>
|
||||
<Code className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</BubbleMenu>
|
||||
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<EditorContent
|
||||
editor={editor}
|
||||
className={`tiptap-text-editor w-full`}
|
||||
style={{
|
||||
// Ensure the editor maintains the same visual appearance
|
||||
lineHeight: "inherit",
|
||||
fontSize: "inherit",
|
||||
fontWeight: "inherit",
|
||||
fontFamily: "inherit",
|
||||
color: "inherit",
|
||||
textAlign: "inherit",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TiptapText;
|
||||
export default TiptapText;
|
||||
|
|
|
|||
|
|
@ -63,81 +63,16 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
|
|||
if (shouldSkipElement(htmlElement)) return;
|
||||
|
||||
// Get all computed styles to preserve them
|
||||
const computedStyles = window.getComputedStyle(htmlElement);
|
||||
const preservedStyles = {
|
||||
fontSize: computedStyles.fontSize,
|
||||
fontWeight: computedStyles.fontWeight,
|
||||
fontFamily: computedStyles.fontFamily,
|
||||
color: computedStyles.color,
|
||||
lineHeight: computedStyles.lineHeight,
|
||||
textAlign: computedStyles.textAlign,
|
||||
marginTop: computedStyles.marginTop,
|
||||
marginBottom: computedStyles.marginBottom,
|
||||
marginLeft: computedStyles.marginLeft,
|
||||
marginRight: computedStyles.marginRight,
|
||||
paddingTop: computedStyles.paddingTop,
|
||||
paddingBottom: computedStyles.paddingBottom,
|
||||
paddingLeft: computedStyles.paddingLeft,
|
||||
paddingRight: computedStyles.paddingRight,
|
||||
borderRadius: computedStyles.borderRadius,
|
||||
border: computedStyles.border,
|
||||
backgroundColor: computedStyles.backgroundColor,
|
||||
opacity: computedStyles.opacity,
|
||||
zIndex: computedStyles.zIndex,
|
||||
cursor: computedStyles.cursor,
|
||||
boxShadow: computedStyles.boxShadow,
|
||||
textShadow: computedStyles.textShadow,
|
||||
textDecoration: computedStyles.textDecoration,
|
||||
textTransform: computedStyles.textTransform,
|
||||
letterSpacing: computedStyles.letterSpacing,
|
||||
wordSpacing: computedStyles.wordSpacing,
|
||||
textOverflow: computedStyles.textOverflow,
|
||||
whiteSpace: computedStyles.whiteSpace,
|
||||
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 allClasses = Array.from(htmlElement.classList);
|
||||
const allStyles = htmlElement.getAttribute("style");
|
||||
|
||||
const dataPath = findDataPath(slideData, trimmedText);
|
||||
|
||||
// Create a container for the TiptapText
|
||||
const tiptapContainer = document.createElement("div");
|
||||
tiptapContainer.className = htmlElement.className;
|
||||
tiptapContainer.style.cssText = allStyles || "";
|
||||
tiptapContainer.className = Array.from(allClasses).join(" ");
|
||||
|
||||
// Apply preserved styles
|
||||
Object.entries(preservedStyles).forEach(([property, value]) => {
|
||||
if (value && value !== "auto") {
|
||||
tiptapContainer.style.setProperty(
|
||||
property.replace(/([A-Z])/g, "-$1").toLowerCase(),
|
||||
value
|
||||
);
|
||||
}
|
||||
});
|
||||
// Replace the element
|
||||
htmlElement.parentNode?.replaceChild(tiptapContainer, htmlElement);
|
||||
// Mark as processed
|
||||
|
|
@ -147,6 +82,7 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
|
|||
root.render(
|
||||
<TiptapText
|
||||
content={trimmedText}
|
||||
tag={htmlElement.tagName}
|
||||
onContentChange={(content: string) => {
|
||||
if (dataPath && onContentChange) {
|
||||
onContentChange(content, dataPath.path, slideIndex);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
|
|||
};
|
||||
`
|
||||
);
|
||||
// globalThis.z = z;
|
||||
|
||||
return factory(React, z);
|
||||
};
|
||||
|
||||
|
|
@ -124,7 +124,6 @@ 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[] = [];
|
||||
|
|
@ -278,7 +277,6 @@ export const LayoutProvider: React.FC<{
|
|||
await layoutResponse.json();
|
||||
|
||||
if (!groupedLayoutsData || groupedLayoutsData.length === 0) {
|
||||
console.warn("⚠️ API returned empty data");
|
||||
setError("No layout groups found");
|
||||
return;
|
||||
}
|
||||
|
|
@ -344,11 +342,10 @@ export const LayoutProvider: React.FC<{
|
|||
"/api/v1/ppt/layout-management/summary"
|
||||
);
|
||||
const customGroupData = await customGroupResponse.json();
|
||||
console.log("🔍 customGroupData", customGroupData);
|
||||
|
||||
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)) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export const useGroupLayouts = () => {
|
|||
if (layout) {
|
||||
return getLayout(layoutId);
|
||||
}
|
||||
console.warn(`Layout ${layoutId} not found in group ${groupName}`);
|
||||
return null;
|
||||
};
|
||||
}, [getLayoutByIdAndGroup, getLayout]);
|
||||
|
|
|
|||
|
|
@ -107,16 +107,15 @@ const SlideContent = ({ slide, index, presentationId }: SlideContentProps) => {
|
|||
if (isStreaming || loading) {
|
||||
return;
|
||||
}
|
||||
if (slide.layout_group.includes("custom")) {
|
||||
const existingScript = document.querySelector(
|
||||
'script[src*="tailwindcss.com"]'
|
||||
);
|
||||
if (!existingScript) {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://cdn.tailwindcss.com";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
const existingScript = document.querySelector(
|
||||
'script[src*="tailwindcss.com"]'
|
||||
);
|
||||
if (!existingScript) {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://cdn.tailwindcss.com";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}, [slide, isStreaming, loading]);
|
||||
|
||||
|
|
|
|||
8
servers/nextjs/app/api/has-anthropic-key/route.ts
Normal file
8
servers/nextjs/app/api/has-anthropic-key/route.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const hasKey = process.env.ANTHROPIC_API_KEY !== "";
|
||||
return NextResponse.json({ hasKey });
|
||||
}
|
||||
|
|
@ -25,14 +25,14 @@ const EachSlide = ({
|
|||
retrySlide,
|
||||
setSlides,
|
||||
onSlideUpdate,
|
||||
isProcessingPptx,
|
||||
isProcessing,
|
||||
}: {
|
||||
slide: any;
|
||||
index: number;
|
||||
retrySlide: (index: number) => void;
|
||||
setSlides: React.Dispatch<React.SetStateAction<any[]>>;
|
||||
onSlideUpdate?: (updatedSlideData: any) => void;
|
||||
isProcessingPptx: boolean;
|
||||
isProcessing: boolean;
|
||||
}) => {
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
|
|
@ -418,14 +418,19 @@ const EachSlide = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{!isProcessingPptx && slide.processed && (
|
||||
{slide.processed && (
|
||||
<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 `}
|
||||
disabled={isProcessing || !slide.processed}
|
||||
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 ${
|
||||
isProcessing || !slide.processed
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Edit className="w-4 sm:w-5 h-4 sm:h-5 text-white" />
|
||||
<span className="text-white">Edit Slide</span>
|
||||
|
|
@ -437,19 +442,26 @@ const EachSlide = ({
|
|||
<ToolTip content="Re-Design this slide">
|
||||
<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"
|
||||
disabled={isProcessing || !slide.processed}
|
||||
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 ${
|
||||
isProcessing || !slide.processed
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Repeat2 className="w-4 sm:w-5 h-4 sm:h-5 text-white" />
|
||||
<span className="text-white">Re-Design</span>
|
||||
<span className="text-white">Re-Construct</span>
|
||||
</button>
|
||||
</ToolTip>
|
||||
</div>
|
||||
<div>
|
||||
<ToolTip content="Delete Slide">
|
||||
<button
|
||||
disabled={isProcessing}
|
||||
onClick={handleDeleteSlide}
|
||||
className="px-4 py-2 flex gap-2 text-sm items-center group-hover:scale-105 rounded-lg hover:shadow-md transition-all duration-300 cursor-pointer shadow-md"
|
||||
className={`px-4 py-2 flex gap-2 text-sm items-center group-hover:scale-105 rounded-lg hover:shadow-md transition-all duration-300 cursor-pointer shadow-md ${
|
||||
isProcessing ? "opacity-50 cursor-not-allowed" : ""
|
||||
}`}
|
||||
>
|
||||
<Trash className="w-4 sm:w-5 h-4 sm:h-5 text-red-500" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { toast } from "sonner";
|
||||
import { Upload, FileText, X, Loader2 } from "lucide-react";
|
||||
import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler";
|
||||
|
|
@ -60,8 +59,17 @@ const CustomLayoutPage = () => {
|
|||
const [isLayoutSaved, setIsLayoutSaved] = useState(false);
|
||||
const [UploadedFonts, setUploadedFonts] = useState<UploadedFont[]>([]);
|
||||
const [fontsData, setFontsData] = useState<FontData | null>(null);
|
||||
const [hasAnthropicKey, setHasAnthropicKey] = useState(false);
|
||||
const [isAnthropicKeyLoading, setIsAnthropicKeyLoading] = useState(true);
|
||||
|
||||
console.log(slides);
|
||||
useEffect(() => {
|
||||
fetch("/api/has-anthropic-key")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setHasAnthropicKey(data.hasKey);
|
||||
setIsAnthropicKeyLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Load uploaded fonts dynamically
|
||||
useEffect(() => {
|
||||
|
|
@ -117,6 +125,9 @@ const CustomLayoutPage = () => {
|
|||
}
|
||||
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
}, [slides, isLayoutSaved]);
|
||||
|
||||
// If User does not put Anthropic Key, Can't process the layout
|
||||
|
||||
// Font management functions
|
||||
const uploadFont = useCallback(
|
||||
async (fontName: string, file: File): Promise<string | null> => {
|
||||
|
|
@ -563,6 +574,36 @@ const CustomLayoutPage = () => {
|
|||
(slide) => slide.processed || slide.error
|
||||
).length;
|
||||
|
||||
if (isAnthropicKeyLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 ">
|
||||
<Header />
|
||||
<div className="max-w-[1440px] min-h-screen flex items-center justify-center aspect-video mx-auto px-6 ">
|
||||
<div className="text-center space-y-2 my-6 bg-white p-6 rounded-lg shadow-md">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-blue-600 mx-auto" />
|
||||
<p>Checking Anthropic Key...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!hasAnthropicKey) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 ">
|
||||
<Header />
|
||||
<div className="max-w-[1440px] min-h-screen flex items-center justify-center aspect-video mx-auto px-6 ">
|
||||
<div className="text-center space-y-2 my-6 bg-white p-6 rounded-lg shadow-md">
|
||||
<h1 className="text-4xl font-bold text-gray-900">
|
||||
Please put Anthropic Key To Process The Layout
|
||||
</h1>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
It Only works on Anthropic(Claude-4).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 ">
|
||||
<Header />
|
||||
|
|
@ -680,7 +721,7 @@ const CustomLayoutPage = () => {
|
|||
key={index}
|
||||
slide={slide}
|
||||
index={index}
|
||||
isProcessingPptx={isProcessingPptx}
|
||||
isProcessing={slides.some((s) => s.processing)}
|
||||
retrySlide={retrySlide}
|
||||
setSlides={setSlides}
|
||||
onSlideUpdate={(updatedSlideData) =>
|
||||
|
|
@ -696,7 +737,7 @@ const CustomLayoutPage = () => {
|
|||
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50">
|
||||
<Button
|
||||
onClick={saveLayout}
|
||||
disabled={isSavingLayout || isProcessingPptx}
|
||||
disabled={isSavingLayout || slides.some((s) => s.processing)}
|
||||
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"
|
||||
>
|
||||
|
|
|
|||
67
servers/nextjs/app/layout-preview/CustomLayout.tsx
Normal file
67
servers/nextjs/app/layout-preview/CustomLayout.tsx
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,7 @@ import LoadingStates from "./components/LoadingStates";
|
|||
import { Card } from "@/components/ui/card";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Header from "@/components/Header";
|
||||
import CustomLayout from "./CustomLayout";
|
||||
|
||||
const LayoutPreview = () => {
|
||||
const {
|
||||
|
|
@ -127,7 +128,7 @@ const LayoutPreview = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <CustomLayouts /> */}
|
||||
{/* <CustomLayout /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,11 +39,9 @@
|
|||
"@tiptap/extension-underline": "^2.0.0",
|
||||
"@tiptap/react": "^2.11.5",
|
||||
"@tiptap/starter-kit": "^2.11.5",
|
||||
"@types/fabric": "^5.3.10",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"fabric": "^6.7.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsonrepair": "^3.12.0",
|
||||
"lucide-react": "^0.447.0",
|
||||
|
|
@ -54,9 +52,7 @@
|
|||
"puppeteer": "^24.13.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-jsx-parser": "^2.4.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-sketch-canvas": "^6.2.0",
|
||||
"recharts": "^2.15.4",
|
||||
"sharp": "^0.34.3",
|
||||
"sonner": "^2.0.6",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue