Merge pull request #107 from presenton/feat/select_group_data_preview

feat(Nextjs): Default data previewd in layout group selection
This commit is contained in:
Shiva Raj Badu 2025-07-20 21:48:58 +05:45 committed by GitHub
commit 0b13a5dac2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 104 deletions

View file

@ -0,0 +1,65 @@
import { useGroupLayoutLoader } from '@/app/layout-preview/hooks/useGroupLayoutLoader';
import { CheckCircle } from 'lucide-react';
import React from 'react';
import { LayoutGroup } from "../types/index";
interface GroupLayoutsProps {
group: LayoutGroup;
onSelectLayoutGroup: (group: LayoutGroup) => void;
selectedLayoutGroup: LayoutGroup | null;
}
const GroupLayouts: React.FC<GroupLayoutsProps> = ({ group, onSelectLayoutGroup, selectedLayoutGroup }) => {
const { layoutGroup } = useGroupLayoutLoader(group.id);
return (
<div
onClick={() => onSelectLayoutGroup(group)}
className={`relative p-4 rounded-lg border cursor-pointer transition-all duration-200 ${selectedLayoutGroup?.id === group.id
? 'border-blue-500 bg-blue-50 shadow-md'
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'
}`}
>
{selectedLayoutGroup?.id === group.id && (
<div className="absolute top-3 right-3">
<CheckCircle className="w-5 h-5 text-blue-500" />
</div>
)}
<div className="mb-3">
<h6 className="text-base font-medium text-gray-900 mb-1">
{group.name}
</h6>
<p className="text-sm text-gray-600">
{group.description}
</p>
</div>
{/* Layout previews */}
<div className="grid grid-cols-2 gap-2 mb-3">
{layoutGroup && layoutGroup?.layouts.slice(0, 4).map((layout: any, index: number) => {
const { component: LayoutComponent, sampleData, layoutId } = layout
return (
<div key={`${layoutGroup?.groupName}-${index}`} className=" relative cursor-pointer overflow-hidden aspect-video">
<div className="absolute cursor-pointer bg-transparent z-40 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%]">
<LayoutComponent data={sampleData} />
</div>
</div>
)
})}
</div>
<div className="flex items-center justify-between text-sm text-gray-500">
<span>{layoutGroup?.layouts.length} layouts</span>
<span className={`px-2 py-1 rounded text-xs ${group.ordered
? 'bg-gray-100 text-gray-700'
: 'bg-blue-100 text-blue-700'
}`}>
{group.ordered ? 'Structured' : 'Flexible'}
</span>
</div>
</div>
);
};
export default GroupLayouts;

View file

@ -1,17 +1,9 @@
"use client";
import React, { useEffect } from "react";
import { useLayout } from "../../context/LayoutContext";
import { CheckCircle } from "lucide-react";
interface LayoutGroup {
id: string;
name: string;
description: string;
ordered: boolean;
isDefault?: boolean;
slides: string[];
}
import GroupLayouts from "./GroupLayouts";
import { LayoutGroup } from "../types/index";
interface LayoutSelectionProps {
selectedLayoutGroup: LayoutGroup | null;
onSelectLayoutGroup: (group: LayoutGroup) => void;
@ -25,17 +17,15 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
getLayoutsByGroup,
getGroupSetting,
getAllGroups,
getLayout,
loading
} = useLayout();
const layoutGroups: LayoutGroup[] = React.useMemo(() => {
const groups = getAllGroups();
if (groups.length === 0) return [];
const Groups: LayoutGroup[] = groups.map(groupName => {
const layouts = getLayoutsByGroup(groupName);
const settings = getGroupSetting(groupName);
return {
id: groupName,
@ -43,7 +33,6 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
description: settings?.description || `${groupName} presentation layouts`,
ordered: settings?.ordered || false,
isDefault: settings?.isDefault || false,
slides: layouts.map(layout => layout.id)
};
});
@ -63,32 +52,6 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
}
}, [layoutGroups, selectedLayoutGroup, onSelectLayoutGroup]);
const renderLayoutPreview = (layoutId: string) => {
const Layout = getLayout(layoutId);
if (!Layout) {
return (
<div className="w-full h-16 bg-gray-100 rounded flex items-center justify-center">
<span className="text-gray-400 text-xs">Preview unavailable</span>
</div>
);
}
// Sample data for preview
const sampleData = {
title: "Sample Title",
description: "This is a preview of the layout",
subtitle: "Sample subtitle",
};
return (
<div className="w-full h-16 overflow-hidden rounded bg-white border">
<div className="transform scale-[0.12] origin-top-left w-[833%] h-[833%]">
<Layout data={sampleData} />
</div>
</div>
);
};
if (loading) {
return (
<div className="space-y-6">
@ -124,52 +87,24 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
);
}
const handleLayoutGroupSelection = (group: LayoutGroup) => {
const slides = getLayoutsByGroup(group.id);
onSelectLayoutGroup({
...group,
slides: slides,
});
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{layoutGroups.map((group) => (
<div
<GroupLayouts
key={group.id}
onClick={() => onSelectLayoutGroup(group)}
className={`relative p-4 rounded-lg border cursor-pointer transition-all duration-200 ${selectedLayoutGroup?.id === group.id
? 'border-blue-500 bg-blue-50 shadow-md'
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'
}`}
>
{selectedLayoutGroup?.id === group.id && (
<div className="absolute top-3 right-3">
<CheckCircle className="w-5 h-5 text-blue-500" />
</div>
)}
<div className="mb-3">
<h6 className="text-base font-medium text-gray-900 mb-1">
{group.name}
</h6>
<p className="text-sm text-gray-600">
{group.description}
</p>
</div>
{/* Layout previews */}
<div className="grid grid-cols-3 gap-2 mb-3">
{group.slides.slice(0, 6).map((layoutId, index) => (
<div key={index} className="aspect-video">
{renderLayoutPreview(layoutId)}
</div>
))}
</div>
<div className="flex items-center justify-between text-sm text-gray-500">
<span>{group.slides.length} layouts</span>
<span className={`px-2 py-1 rounded text-xs ${group.ordered
? 'bg-gray-100 text-gray-700'
: 'bg-blue-100 text-blue-700'
}`}>
{group.ordered ? 'Structured' : 'Flexible'}
</span>
</div>
</div>
group={group}
onSelectLayoutGroup={handleLayoutGroupSelection}
selectedLayoutGroup={selectedLayoutGroup}
/>
))}
</div>
</div>

View file

@ -24,7 +24,6 @@ const OutlinePage: React.FC = () => {
const [activeTab, setActiveTab] = useState<string>(TABS.OUTLINE);
const [selectedLayoutGroup, setSelectedLayoutGroup] = useState<LayoutGroup | null>(null);
// Custom hooks
const streamState = useOutlineStreaming(presentation_id);
const { handleDragEnd, handleAddSlide } = useOutlineManagement(outlines);
@ -39,6 +38,7 @@ const OutlinePage: React.FC = () => {
return <EmptyStateView />;
}
return (
<Wrapper>
<OverlayLoader

View file

@ -2,7 +2,7 @@ import { useState, useCallback } from "react";
import { useDispatch } from "react-redux";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { clearPresentationData, setPresentationData, SlideOutline } from "@/store/slices/presentationGeneration";
import { clearPresentationData, SlideOutline } from "@/store/slices/presentationGeneration";
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
import { useLayout } from "../../context/LayoutContext";
import { LayoutGroup, LoadingState, TABS } from "../types/index";
@ -46,22 +46,12 @@ export const usePresentationGeneration = (
const prepareLayoutData = useCallback(() => {
if (!selectedLayoutGroup) return null;
const groupLayoutSchemas = selectedLayoutGroup.slides
.map(slideId => {
const layout = getLayoutById(slideId);
return layout ? {
id: layout.id,
name: layout.name,
description: layout.description,
json_schema: layout.json_schema
} : null;
})
.filter(schema => schema !== null);
return {
name: selectedLayoutGroup.name,
ordered: selectedLayoutGroup.ordered,
slides: groupLayoutSchemas
slides: selectedLayoutGroup.slides
};
}, [selectedLayoutGroup, getLayoutById]);
@ -84,7 +74,6 @@ export const usePresentationGeneration = (
try {
const layoutData = prepareLayoutData();
if (!layoutData) return;
const response = await PresentationGenerationApi.presentationPrepare({
presentation_id: presentationId,
outlines: outlines,

View file

@ -4,7 +4,7 @@ export interface LayoutGroup {
description: string;
ordered: boolean;
isDefault?: boolean;
slides: string[];
slides?: any
}
export interface LoadingState {

View file

@ -67,7 +67,6 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
ordered: false,
isDefault: false
}
for (const fileName of targetGroupData.files) {
try {
const layoutName = fileName.replace('.tsx', '').replace('.ts', '')
@ -163,8 +162,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
}
const retry = () => {
// Clear cache for this group to force reload
layoutGroupCache.delete(groupSlug)
hasMountedRef.current = false
loadGroupLayouts()
}
@ -179,6 +177,6 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
layoutGroup,
loading,
error,
retry
retry,
}
}

View file

@ -37,11 +37,9 @@
"@tiptap/extension-underline": "^2.0.0",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"jsonrepair": "^3.12.0",
"lucide-react": "^0.447.0",
"marked": "^15.0.11",
@ -57,7 +55,6 @@
"tailwind-scrollbar-hide": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"tiptap-markdown": "^0.8.10",
"ws": "^8.18.0",
"uuid": "^11.1.0",
"zod": "^4.0.5"
},