chore(nextjs)
This commit is contained in:
parent
03afc2ee87
commit
3847336af6
17 changed files with 247 additions and 151 deletions
|
|
@ -12,13 +12,23 @@ interface LayoutInfo {
|
|||
group: string;
|
||||
}
|
||||
|
||||
interface GroupSetting {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ordered: boolean;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
interface GroupedLayoutsResponse {
|
||||
group: string;
|
||||
files: string[];
|
||||
settings: GroupSetting | null;
|
||||
}
|
||||
|
||||
interface LayoutContextType {
|
||||
layoutSchema: LayoutInfo[] | null;
|
||||
groupSettings: Record<string, GroupSetting>;
|
||||
idMapFileNames: Record<string, string>;
|
||||
idMapSchema: Record<string, z.ZodSchema>;
|
||||
idMapGroups: Record<string, string>;
|
||||
|
|
@ -37,6 +47,7 @@ const layoutCache = new Map<string, React.ComponentType<{ data: any }>>();
|
|||
|
||||
export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [layoutSchema, setLayoutSchema] = useState<LayoutInfo[] | null>(null);
|
||||
const [groupSettings, setGroupSettings] = useState<Record<string, GroupSetting>>({});
|
||||
const [idMapFileNames, setIdMapFileNames] = useState<Record<string, string>>({});
|
||||
const [idMapSchema, setIdMapSchema] = useState<Record<string, z.ZodSchema>>({});
|
||||
const [idMapGroups, setIdMapGroups] = useState<Record<string, string>>({});
|
||||
|
|
@ -49,8 +60,23 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
const idMapFileNames: Record<string, string> = {};
|
||||
const idMapSchema: Record<string, z.ZodSchema> = {};
|
||||
const idMapGroups: Record<string, string> = {};
|
||||
const groupSettings: Record<string, GroupSetting> = {};
|
||||
|
||||
for (const groupData of groupedLayoutsData) {
|
||||
// Store group settings
|
||||
if (groupData.settings) {
|
||||
groupSettings[groupData.group] = groupData.settings;
|
||||
} else {
|
||||
// Provide default settings if not available
|
||||
groupSettings[groupData.group] = {
|
||||
id: groupData.group,
|
||||
name: groupData.group.charAt(0).toUpperCase() + groupData.group.slice(1),
|
||||
description: `${groupData.group} presentation layouts`,
|
||||
ordered: false,
|
||||
isDefault: false
|
||||
};
|
||||
}
|
||||
|
||||
for (const fileName of groupData.files) {
|
||||
try {
|
||||
const file = fileName.replace('.tsx', '').replace('.ts', '');
|
||||
|
|
@ -102,7 +128,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
}
|
||||
}
|
||||
|
||||
return { layouts, idMapFileNames, idMapSchema, idMapGroups };
|
||||
return { layouts, idMapFileNames, idMapSchema, idMapGroups, groupSettings };
|
||||
};
|
||||
|
||||
const loadLayouts = async () => {
|
||||
|
|
@ -119,6 +145,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
const response = await extractSchema(groupedLayoutsData);
|
||||
|
||||
setLayoutSchema(response?.layouts || []);
|
||||
setGroupSettings(response?.groupSettings || {});
|
||||
setIdMapFileNames(response?.idMapFileNames || {});
|
||||
setIdMapSchema(response?.idMapSchema || {});
|
||||
setIdMapGroups(response?.idMapGroups || {});
|
||||
|
|
@ -200,6 +227,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
|
||||
const contextValue: LayoutContextType = {
|
||||
layoutSchema,
|
||||
groupSettings,
|
||||
idMapFileNames,
|
||||
idMapSchema,
|
||||
idMapGroups,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
'use client'
|
||||
import React, { useMemo } from 'react';
|
||||
import { useLayout } from '../context/LayoutContext';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@/store/store';
|
||||
|
||||
interface LayoutInfo {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
json_schema: any;
|
||||
group: string;
|
||||
}
|
||||
|
||||
export const useGroupLayouts = () => {
|
||||
const { layoutSchema, getLayout, loading } = useLayout();
|
||||
const { presentationData } = useSelector((state: RootState) => state.presentationGeneration);
|
||||
|
||||
// Get the selected group name from presentation data
|
||||
const selectedGroupName = presentationData?.layouts?.name?.toLowerCase() ?? null;
|
||||
|
||||
// Filter layouts to only include those from the selected group
|
||||
const groupLayouts = useMemo(() => {
|
||||
if (!layoutSchema || !selectedGroupName) return [];
|
||||
|
||||
return layoutSchema.filter(layout => layout.group === selectedGroupName);
|
||||
}, [layoutSchema, selectedGroupName]);
|
||||
|
||||
// Get group-specific layout component with validation
|
||||
const getGroupLayout = useMemo(() => {
|
||||
return (layoutId: string) => {
|
||||
// First check if the layout exists in the current group
|
||||
const groupLayout = groupLayouts.find(layout => layout.id === layoutId);
|
||||
if (groupLayout) {
|
||||
return getLayout(layoutId);
|
||||
}
|
||||
|
||||
// If layout not found in group, return null
|
||||
console.warn(`Layout ${layoutId} not found in group ${selectedGroupName}`);
|
||||
return null;
|
||||
};
|
||||
}, [groupLayouts, selectedGroupName, getLayout]);
|
||||
|
||||
// Render slide content with group validation
|
||||
const renderSlideContent = useMemo(() => {
|
||||
return (slide: any) => {
|
||||
const Layout = getGroupLayout(slide.layout);
|
||||
if (!Layout) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full bg-gray-100 rounded-lg">
|
||||
<p className="text-gray-600 text-center text-sm">
|
||||
Layout "{slide.layout}" not found in "{selectedGroupName}" group
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <Layout data={slide.content} />;
|
||||
};
|
||||
}, [getGroupLayout, selectedGroupName]);
|
||||
|
||||
return {
|
||||
groupLayouts,
|
||||
selectedGroupName,
|
||||
getGroupLayout,
|
||||
renderSlideContent,
|
||||
loading
|
||||
};
|
||||
};
|
||||
|
|
@ -21,9 +21,9 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
selectedLayoutGroup,
|
||||
onSelectLayoutGroup
|
||||
}) => {
|
||||
const { layoutSchema, getLayout, loading } = useLayout();
|
||||
const { layoutSchema, groupSettings, getLayout, loading } = useLayout();
|
||||
|
||||
// Create layout groups from the loaded layout schema
|
||||
// Convert layoutSchema to grouped format using actual group settings
|
||||
const layoutGroups: LayoutGroup[] = React.useMemo(() => {
|
||||
if (!layoutSchema || layoutSchema.length === 0) return [];
|
||||
|
||||
|
|
@ -37,27 +37,29 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
groupMap.get(groupName)?.push(layout);
|
||||
});
|
||||
|
||||
// Convert to LayoutGroup format
|
||||
// Convert to LayoutGroup format using actual group settings
|
||||
const groups: LayoutGroup[] = [];
|
||||
groupMap.forEach((layouts, groupName) => {
|
||||
const settings = groupSettings[groupName];
|
||||
|
||||
const group: LayoutGroup = {
|
||||
id: groupName,
|
||||
name: groupName.charAt(0).toUpperCase() + groupName.slice(1),
|
||||
description: getGroupDescription(groupName),
|
||||
ordered: getGroupOrdered(groupName),
|
||||
isDefault: groupName === 'professional',
|
||||
slides: layouts.map(layout => layout.id)
|
||||
id: settings?.id || groupName,
|
||||
name: settings?.name || groupName.charAt(0).toUpperCase() + groupName.slice(1),
|
||||
description: settings?.description || `${groupName} presentation layouts`,
|
||||
ordered: settings?.ordered || false,
|
||||
isDefault: settings?.isDefault || false,
|
||||
slides: layouts.map((layout: any) => layout.id)
|
||||
};
|
||||
groups.push(group);
|
||||
});
|
||||
|
||||
// Sort groups to put default first
|
||||
// Sort groups to put default first, then by name
|
||||
return groups.sort((a, b) => {
|
||||
if (a.isDefault) return -1;
|
||||
if (b.isDefault) return 1;
|
||||
if (a.isDefault && !b.isDefault) return -1;
|
||||
if (!a.isDefault && b.isDefault) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}, [layoutSchema]);
|
||||
}, [layoutSchema, groupSettings]);
|
||||
|
||||
// Auto-select first group when groups are loaded
|
||||
useEffect(() => {
|
||||
|
|
@ -67,22 +69,6 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
}
|
||||
}, [layoutGroups, selectedLayoutGroup, onSelectLayoutGroup]);
|
||||
|
||||
const getGroupDescription = (groupName: string): string => {
|
||||
const descriptions: Record<string, string> = {
|
||||
professional: 'Clean, corporate designs perfect for business presentations',
|
||||
modern: 'Contemporary designs with clean lines and sophisticated layouts',
|
||||
default: 'Standard layouts suitable for general presentations',
|
||||
creative: 'Vibrant, artistic layouts for innovative presentations',
|
||||
minimal: 'Simple, focused layouts that emphasize content'
|
||||
};
|
||||
return descriptions[groupName] || `${groupName} presentation layouts`;
|
||||
};
|
||||
|
||||
const getGroupOrdered = (groupName: string): boolean => {
|
||||
const orderedGroups = ['professional', 'modern'];
|
||||
return orderedGroups.includes(groupName);
|
||||
};
|
||||
|
||||
const renderLayoutPreview = (layoutId: string) => {
|
||||
const Layout = getLayout(layoutId);
|
||||
if (!Layout) {
|
||||
|
|
@ -112,14 +98,6 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h5 className="text-lg font-medium mb-2">
|
||||
Loading Layout Styles...
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Please wait while we load the available presentation styles.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="p-4 rounded-lg border border-gray-200 bg-gray-50 animate-pulse">
|
||||
|
|
@ -140,8 +118,8 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
if (layoutGroups.length === 0) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h5 className="text-lg font-medium mb-2">
|
||||
<div className="text-center py-8">
|
||||
<h5 className="text-lg font-medium mb-2 text-gray-700">
|
||||
No Layout Styles Available
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
|
|
@ -154,15 +132,6 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h5 className="text-lg font-medium mb-2">
|
||||
Select Your Presentation Style
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Choose a layout group that best fits your presentation style and content.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{layoutGroups.map((group) => (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Wrapper from "@/components/Wrapper";
|
|||
import { jsonrepair } from "jsonrepair";
|
||||
import OutlineContent from "./OutlineContent";
|
||||
import LayoutSelection from "./LayoutSelection";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
||||
interface LayoutGroup {
|
||||
id: string;
|
||||
|
|
@ -31,6 +32,7 @@ interface LayoutGroup {
|
|||
const OutlinePage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const router = useRouter();
|
||||
const { layoutSchema } = useLayout();
|
||||
|
||||
const { presentation_id, outlines } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
|
|
@ -190,13 +192,27 @@ const OutlinePage = () => {
|
|||
});
|
||||
|
||||
try {
|
||||
// Prepare layout data in the expected format
|
||||
// Collect the actual schemas for layouts in the selected group
|
||||
const groupLayoutSchemas = selectedLayoutGroup.slides
|
||||
.map(slideId => {
|
||||
const layout = layoutSchema?.find(l => l.id === slideId);
|
||||
return layout ? {
|
||||
id: layout.id,
|
||||
name: layout.name,
|
||||
description: layout.description,
|
||||
json_schema: layout.json_schema
|
||||
} : null;
|
||||
})
|
||||
.filter(schema => schema !== null);
|
||||
|
||||
// Prepare layout data in the expected format with schemas
|
||||
const layoutData = {
|
||||
name: selectedLayoutGroup.name,
|
||||
ordered: selectedLayoutGroup.ordered,
|
||||
slides: selectedLayoutGroup.slides
|
||||
slides: groupLayoutSchemas
|
||||
};
|
||||
|
||||
console.log("layoutData", layoutData);
|
||||
const response = await PresentationGenerationApi.presentationPrepare({
|
||||
presentation_id: presentation_id,
|
||||
outlines: outlines,
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ import {
|
|||
} from "@/store/slices/presentationGeneration";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import { setThemeColors, ThemeColors } from "../../store/themeSlice";
|
||||
import { ThemeType } from "../../upload/type";
|
||||
|
||||
import LoadingState from "../../components/LoadingState";
|
||||
import Header from "../components/Header";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
|
@ -135,7 +134,6 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
);
|
||||
|
||||
evtSource.onopen = () => {
|
||||
setColorsVariables(currentColors, currentTheme);
|
||||
};
|
||||
|
||||
evtSource.addEventListener("response", (event) => {
|
||||
|
|
@ -157,7 +155,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
partialData.slides.splice(-1);
|
||||
dispatch(
|
||||
setPresentationData({
|
||||
presentation: null,
|
||||
...partialData,
|
||||
slides: partialData.slides,
|
||||
})
|
||||
);
|
||||
|
|
@ -174,18 +172,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
|
||||
dispatch(setPresentationData(data.presentation));
|
||||
dispatch(setStreaming(false));
|
||||
if (data.presentation.theme) {
|
||||
dispatch(
|
||||
setThemeColors({
|
||||
...data.presentation.presentation.theme.colors,
|
||||
theme: data.presentation.presentation.theme.name as ThemeType,
|
||||
})
|
||||
);
|
||||
setColorsVariables(
|
||||
data.presentation.presentation.theme.colors,
|
||||
data.presentation.presentation.theme.name as ThemeType
|
||||
);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
evtSource.close();
|
||||
|
|
@ -200,18 +187,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
accumulatedChunks = "";
|
||||
} else if (data.type === "closing") {
|
||||
dispatch(setPresentationData(data.presentation));
|
||||
if (data.presentation.theme) {
|
||||
dispatch(
|
||||
setThemeColors({
|
||||
...data.presentation.presentation.theme.colors,
|
||||
theme: data.presentation.presentation.theme.name as ThemeType,
|
||||
})
|
||||
);
|
||||
setColorsVariables(
|
||||
data.presentation.presentation.theme.colors,
|
||||
data.presentation.presentation.theme.name as ThemeType
|
||||
);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
dispatch(setStreaming(false));
|
||||
evtSource.close();
|
||||
|
|
@ -275,13 +251,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const setColorsVariables = (colors: ThemeColors, theme: ThemeType) => {
|
||||
const root = document.documentElement;
|
||||
Object.entries(colors).forEach(([key, value]) => {
|
||||
const cssKey = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
||||
root.style.setProperty(`--${theme}-${cssKey}`, value);
|
||||
});
|
||||
};
|
||||
|
||||
// Function to toggle fullscreen
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
|
|
@ -316,21 +286,21 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
);
|
||||
};
|
||||
|
||||
if (isPresentMode) {
|
||||
return (
|
||||
<PresentationMode
|
||||
// if (isPresentMode) {
|
||||
// return (
|
||||
// <PresentationMode
|
||||
|
||||
slides={presentationData?.slides!}
|
||||
currentSlide={currentSlide}
|
||||
currentTheme={currentTheme}
|
||||
isFullscreen={isFullscreen}
|
||||
onFullscreenToggle={toggleFullscreen}
|
||||
onExit={handlePresentExit}
|
||||
onSlideChange={handleSlideChange}
|
||||
language={presentationData?.presentation?.language || "English"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// slides={presentationData?.slides!}
|
||||
// currentSlide={currentSlide}
|
||||
// currentTheme={currentTheme}
|
||||
// isFullscreen={isFullscreen}
|
||||
// onFullscreenToggle={toggleFullscreen}
|
||||
// onExit={handlePresentExit}
|
||||
// onSlideChange={handleSlideChange}
|
||||
// language={presentationData?.presentation?.language || "English"}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
// Regular view
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
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";
|
||||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { setPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { SortableSlide } from "./SortableSlide";
|
||||
import { SortableListItem } from "./SortableListItem";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
import { useGroupLayouts } from "../../hooks/useGroupLayouts";
|
||||
|
||||
interface SidePanelProps {
|
||||
selectedSlide: number;
|
||||
|
|
@ -50,18 +50,9 @@ const SidePanel = ({
|
|||
);
|
||||
console.log('presentationData', presentationData)
|
||||
const dispatch = useDispatch();
|
||||
const { getLayout } = useLayout();
|
||||
|
||||
// Memoized slide renderer using layout cache
|
||||
const renderSlideContent = useMemo(() => {
|
||||
return (slide: any) => {
|
||||
const Layout = getLayout(slide.layout);
|
||||
if (!Layout) {
|
||||
return <div>Layout not found</div>;
|
||||
}
|
||||
return <Layout data={slide.content} />;
|
||||
};
|
||||
}, [getLayout]);
|
||||
// Use the centralized group layouts hook
|
||||
const { renderSlideContent } = useGroupLayouts();
|
||||
|
||||
useEffect(() => {
|
||||
if (window.innerWidth < 768) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { addSlide, updateSlide } from "@/store/slices/presentationGeneration";
|
||||
import NewSlide from "../../components/slide_layouts/NewSlide";
|
||||
import { getEmptySlideContent } from "../../utils/NewSlideContent";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
import { useGroupLayouts } from "../../hooks/useGroupLayouts";
|
||||
|
||||
interface SlideContentProps {
|
||||
slide: any;
|
||||
|
|
@ -37,18 +37,24 @@ const SlideContent = ({
|
|||
const { presentationData, isStreaming } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
);
|
||||
const { getLayout, loading } = useLayout();
|
||||
|
||||
// Use the centralized group layouts hook
|
||||
const { groupLayouts, getGroupLayout, loading } = useGroupLayouts();
|
||||
|
||||
// Memoized layout component to prevent re-renders
|
||||
const LayoutComponent = useMemo(() => {
|
||||
const Layout = getLayout(slide.layout);
|
||||
const Layout = getGroupLayout(slide.layout);
|
||||
if (!Layout) {
|
||||
return () => <div className="flex flex-col items-center justify-center h-full">
|
||||
Layout not found
|
||||
</div>;
|
||||
return () => (
|
||||
<div className="flex flex-col items-center justify-center h-full bg-gray-100 rounded-lg">
|
||||
<p className="text-gray-600 text-center">
|
||||
Layout "{slide.layout}" not found in current group
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return Layout;
|
||||
}, [slide.layout, getLayout]);
|
||||
}, [slide.layout, getGroupLayout]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const element = document.getElementById(
|
||||
|
|
@ -96,7 +102,7 @@ const SlideContent = ({
|
|||
const newSlide: Slide = getEmptySlideContent(
|
||||
type,
|
||||
index + 1,
|
||||
presentationData?.presentation!.id!
|
||||
presentationData?.id!
|
||||
);
|
||||
|
||||
dispatch(addSlide({ slide: newSlide, index: index + 1 }));
|
||||
|
|
@ -172,6 +178,7 @@ const SlideContent = ({
|
|||
<NewSlide
|
||||
onSelectLayout={(type) => handleNewSlide(type, slide.index)}
|
||||
setShowNewSlideSelection={setShowNewSlideSelection}
|
||||
|
||||
/>
|
||||
)}
|
||||
{!isStreaming && (
|
||||
|
|
|
|||
|
|
@ -408,6 +408,7 @@ export class PresentationGenerationApi {
|
|||
n_slides,
|
||||
file_paths,
|
||||
language,
|
||||
|
||||
|
||||
}: {
|
||||
prompt: string;
|
||||
|
|
@ -427,6 +428,8 @@ export class PresentationGenerationApi {
|
|||
n_slides,
|
||||
file_paths,
|
||||
language,
|
||||
|
||||
|
||||
}),
|
||||
cache: "no-cache",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ const UploadPage = () => {
|
|||
const dispatch = useDispatch();
|
||||
const { toast } = useToast();
|
||||
|
||||
|
||||
// State management
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [config, setConfig] = useState<PresentationConfig>({
|
||||
|
|
@ -87,9 +86,6 @@ const UploadPage = () => {
|
|||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
@ -165,7 +161,6 @@ const UploadPage = () => {
|
|||
n_slides: config?.slides ? parseInt(config.slides) : null,
|
||||
file_paths: [],
|
||||
language: config?.language ?? "",
|
||||
|
||||
});
|
||||
|
||||
dispatch(setPresentationId(createResponse.id));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import { NextResponse } from 'next/server'
|
|||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
interface GroupSetting {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ordered: boolean;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get the path to the presentation-layouts directory
|
||||
|
|
@ -15,9 +23,9 @@ export async function GET() {
|
|||
.filter(item => item.isDirectory())
|
||||
.map(dir => dir.name)
|
||||
|
||||
const allLayouts: { group: string; files: string[] }[] = []
|
||||
const allLayouts: { group: string; files: string[]; settings: GroupSetting | null }[] = []
|
||||
|
||||
// Scan each group directory for layout files
|
||||
// Scan each group directory for layout files and settings
|
||||
for (const groupName of groupDirectories) {
|
||||
try {
|
||||
const groupPath = path.join(layoutsDirectory, groupName)
|
||||
|
|
@ -32,10 +40,29 @@ export async function GET() {
|
|||
file !== 'setting.json'
|
||||
)
|
||||
|
||||
// Read settings.json if it exists
|
||||
let settings: GroupSetting | null = null
|
||||
const settingsPath = path.join(groupPath, 'setting.json')
|
||||
try {
|
||||
const settingsContent = await fs.readFile(settingsPath, 'utf-8')
|
||||
settings = JSON.parse(settingsContent) as GroupSetting
|
||||
} catch (settingsError) {
|
||||
console.warn(`No settings.json found for group ${groupName} or invalid JSON`)
|
||||
// Provide default settings if setting.json is missing or invalid
|
||||
settings = {
|
||||
id: groupName,
|
||||
name: groupName.charAt(0).toUpperCase() + groupName.slice(1),
|
||||
description: `${groupName} presentation layouts`,
|
||||
ordered: false,
|
||||
isDefault: false
|
||||
}
|
||||
}
|
||||
|
||||
if (layoutFiles.length > 0) {
|
||||
allLayouts.push({
|
||||
group: groupName,
|
||||
files: layoutFiles
|
||||
files: layoutFiles,
|
||||
settings: settings
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse } from '../types'
|
||||
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
|
||||
interface UseLayoutLoaderReturn {
|
||||
|
|
@ -40,6 +40,15 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
|
|||
for (const groupData of groupedLayoutsData) {
|
||||
const groupLayouts: LayoutInfo[] = []
|
||||
|
||||
// Use settings from setting.json or provide defaults
|
||||
const groupSettings: GroupSetting = groupData.settings || {
|
||||
id: groupData.group,
|
||||
name: groupData.group.charAt(0).toUpperCase() + groupData.group.slice(1),
|
||||
description: `${groupData.group} presentation layouts`,
|
||||
ordered: false,
|
||||
isDefault: false
|
||||
}
|
||||
|
||||
for (const fileName of groupData.files) {
|
||||
try {
|
||||
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
|
||||
|
|
@ -115,11 +124,19 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
|
|||
if (groupLayouts.length > 0) {
|
||||
loadedGroups.push({
|
||||
group: groupData.group,
|
||||
layouts: groupLayouts
|
||||
layouts: groupLayouts,
|
||||
settings: groupSettings
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort groups to put default first, then by name
|
||||
loadedGroups.sort((a, b) => {
|
||||
if (a.settings.isDefault && !b.settings.isDefault) return -1
|
||||
if (!a.settings.isDefault && b.settings.isDefault) return 1
|
||||
return a.settings.name.localeCompare(b.settings.name)
|
||||
})
|
||||
|
||||
if (allLayouts.length === 0) {
|
||||
toast({
|
||||
title: 'No valid layouts found',
|
||||
|
|
|
|||
|
|
@ -11,14 +11,24 @@ export interface LayoutInfo {
|
|||
group: string
|
||||
}
|
||||
|
||||
export interface GroupSetting {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ordered: boolean;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface LayoutGroup {
|
||||
group: string
|
||||
layouts: LayoutInfo[]
|
||||
settings: GroupSetting
|
||||
}
|
||||
|
||||
export interface GroupedLayoutsResponse {
|
||||
group: string
|
||||
files: string[]
|
||||
settings: GroupSetting | null
|
||||
}
|
||||
|
||||
export interface LoadingState {
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"id": "default",
|
||||
"name": "Default",
|
||||
"description": "Default layout for presentations",
|
||||
"ordered": true
|
||||
"ordered": true,
|
||||
"isDefault": false
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ const CardSlideLayout: React.FC<CardSlideLayoutProps> = ({ data: slideData }) =>
|
|||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"id": "modern",
|
||||
"name": "Modern",
|
||||
"description": "Contemporary designs with clean lines and sophisticated layouts",
|
||||
"ordered": true
|
||||
"ordered": true,
|
||||
"isDefault": true
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
|
|||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
className="relative w-full aspect-[16/9] bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `url("${slideData.backgroundImage.__image_url__}")`,
|
||||
backgroundSize: 'cover',
|
||||
|
|
|
|||
|
|
@ -36,22 +36,15 @@ export interface Chart {
|
|||
};
|
||||
}
|
||||
export interface PresentationData {
|
||||
presentation: {
|
||||
created_at: string;
|
||||
data: string | null;
|
||||
file: string;
|
||||
id: string;
|
||||
user_id: string;
|
||||
n_slides: number;
|
||||
prompt: string;
|
||||
summary: string | null;
|
||||
theme: string | null;
|
||||
title: string;
|
||||
titles: string[];
|
||||
|
||||
thumbnail: string | null;
|
||||
language: string;
|
||||
} | null;
|
||||
id: string;
|
||||
language: string;
|
||||
layouts: {
|
||||
name: string;
|
||||
ordered: boolean;
|
||||
slides: any[];
|
||||
};
|
||||
n_slides: number;
|
||||
title: string;
|
||||
slides: any;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue