- Move SlideEditor to dedicated slide-editor directory structure - Break down monolithic 471-line component into smaller, focused modules: - SlideEditor.tsx (127 lines) - main component using composition - useSlideEditor.ts (235 lines) - custom hook for state management - ContentEditor.tsx - focused content editing component - SlidePreviewModal.tsx - modal for fullscreen preview - Consolidate CSS from 838+132 lines to 731 lines with: - Comprehensive CSS variables system for consistent theming - Remove duplicate .slide-preview-wrapper rules and conflicts - Clean aspect ratio handling with clear separation of contexts - Follow project standards: direct imports, error boundaries, under 200 lines per component - Maintain all existing functionality while improving code organization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
import React from 'react';
|
|
import { useParams, Link } from 'react-router-dom';
|
|
import { SlideEditorErrorBoundary } from './SlideEditorErrorBoundary.tsx';
|
|
import { LoadingState } from './LoadingState.tsx';
|
|
import { ErrorState } from './ErrorState.tsx';
|
|
import { LayoutSelection } from './LayoutSelection.tsx';
|
|
import { ContentEditor } from './ContentEditor.tsx';
|
|
import { SlidePreviewModal } from './SlidePreviewModal.tsx';
|
|
import { useSlideEditor } from './useSlideEditor.ts';
|
|
import './SlideEditor.css';
|
|
|
|
export const SlideEditor: React.FC = () => {
|
|
const { presentationId, slideId } = useParams<{
|
|
presentationId: string;
|
|
slideId: string;
|
|
}>();
|
|
|
|
const {
|
|
// Data
|
|
presentation,
|
|
theme,
|
|
selectedLayout,
|
|
slideContent,
|
|
slideNotes,
|
|
currentStep,
|
|
|
|
// States
|
|
loading,
|
|
error,
|
|
saving,
|
|
showPreview,
|
|
|
|
// Computed
|
|
isEditingExisting,
|
|
|
|
// Actions
|
|
updateSlotContent,
|
|
setSlideNotes,
|
|
setShowPreview,
|
|
selectLayout,
|
|
saveSlide,
|
|
cancelEditing,
|
|
} = useSlideEditor({ presentationId, slideId });
|
|
|
|
if (loading) {
|
|
return <LoadingState presentationId={presentationId} />;
|
|
}
|
|
|
|
if (error) {
|
|
return <ErrorState error={error} presentationId={presentationId} />;
|
|
}
|
|
|
|
if (!presentation || !theme) {
|
|
return <ErrorState error="Presentation or theme not found" presentationId={presentationId} />;
|
|
}
|
|
|
|
return (
|
|
<SlideEditorErrorBoundary presentationId={presentationId}>
|
|
<div className="slide-editor">
|
|
<header className="slide-editor-header">
|
|
<div className="editor-info">
|
|
<Link
|
|
to={`/presentations/${presentationId}/edit/slides/1`}
|
|
className="back-button"
|
|
>
|
|
← Back to Presentation
|
|
</Link>
|
|
<div className="editor-title">
|
|
<h1>{isEditingExisting ? 'Edit Slide' : 'New Slide'}</h1>
|
|
<span className="presentation-context">
|
|
in "{presentation.metadata.name}"
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="editor-actions">
|
|
{currentStep === 'content' && selectedLayout && (
|
|
<button
|
|
type="button"
|
|
className="action-button secondary"
|
|
onClick={() => setShowPreview(true)}
|
|
>
|
|
Full Preview
|
|
</button>
|
|
)}
|
|
</div>
|
|
</header>
|
|
|
|
<main className="slide-editor-content">
|
|
{currentStep === 'layout' && (
|
|
<LayoutSelection
|
|
theme={theme}
|
|
selectedLayout={selectedLayout}
|
|
onLayoutSelect={selectLayout}
|
|
/>
|
|
)}
|
|
|
|
{currentStep === 'content' && selectedLayout && (
|
|
<ContentEditor
|
|
presentation={presentation}
|
|
selectedLayout={selectedLayout}
|
|
slideContent={slideContent}
|
|
slideNotes={slideNotes}
|
|
isEditingExisting={isEditingExisting}
|
|
saving={saving}
|
|
onSlotContentChange={updateSlotContent}
|
|
onNotesChange={setSlideNotes}
|
|
onSave={saveSlide}
|
|
onCancel={cancelEditing}
|
|
/>
|
|
)}
|
|
</main>
|
|
|
|
{/* Preview Modal */}
|
|
{selectedLayout && presentation && (
|
|
<SlidePreviewModal
|
|
isOpen={showPreview}
|
|
onClose={() => setShowPreview(false)}
|
|
layout={selectedLayout}
|
|
content={slideContent}
|
|
aspectRatio={presentation.metadata.aspectRatio || '16:9'}
|
|
themeName={theme?.name || 'Unknown Theme'}
|
|
/>
|
|
)}
|
|
</div>
|
|
</SlideEditorErrorBoundary>
|
|
);
|
|
}; |