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:
commit
0b13a5dac2
7 changed files with 88 additions and 104 deletions
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export interface LayoutGroup {
|
|||
description: string;
|
||||
ordered: boolean;
|
||||
isDefault?: boolean;
|
||||
slides: string[];
|
||||
slides?: any
|
||||
}
|
||||
|
||||
export interface LoadingState {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue