mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-10 08:27:40 +00:00
refactor(prd-editor): modularize PRD editor with typed feature modules
Break the legacy PRDEditor.jsx monolith into a feature-based TypeScript architecture under src/components/prd-editor while keeping behavior parity and readability. Key changes: - Replace PRDEditor.jsx with a typed orchestrator component and a compatibility export bridge at src/components/PRDEditor.tsx. - Split responsibilities into dedicated hooks: document loading/init, existing PRD registry fetching, save workflow with overwrite detection, and keyboard shortcuts. - Split UI into focused view components: header, editor/preview body, footer stats, loading state, generate-tasks modal, and overwrite-confirm modal. - Move filename concerns into utility helpers (sanitize, extension handling, default naming) and centralize template/constants. - Keep component-local state close to the UI that owns it (workspace controls/modal toggles), while shared workflow state remains in the feature container. - Reuse the existing MarkdownPreview component for safer markdown rendering instead of ad-hoc HTML conversion. - Update TaskMasterPanel integration to consume typed PRDEditor directly (remove any-cast) and pass isExisting metadata for correct overwrite behavior. - Keep all new/changed files below 300 lines and add targeted comments where behavior needs clarification. Validation: - npm run typecheck - npm run build
This commit is contained in:
114
src/components/prd-editor/view/PrdEditorWorkspace.tsx
Normal file
114
src/components/prd-editor/view/PrdEditorWorkspace.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useState } from 'react';
|
||||
import { cn } from '../../../lib/utils';
|
||||
import { ensurePrdExtension } from '../utils/fileName';
|
||||
import GenerateTasksModal from './GenerateTasksModal';
|
||||
import PrdEditorBody from './PrdEditorBody';
|
||||
import PrdEditorFooter from './PrdEditorFooter';
|
||||
import PrdEditorHeader from './PrdEditorHeader';
|
||||
|
||||
type PrdEditorWorkspaceProps = {
|
||||
content: string;
|
||||
onContentChange: (nextContent: string) => void;
|
||||
fileName: string;
|
||||
onFileNameChange: (nextFileName: string) => void;
|
||||
isNewFile: boolean;
|
||||
saving: boolean;
|
||||
saveSuccess: boolean;
|
||||
onSave: () => void;
|
||||
onDownload: () => void;
|
||||
onClose: () => void;
|
||||
loadError: string | null;
|
||||
};
|
||||
|
||||
export default function PrdEditorWorkspace({
|
||||
content,
|
||||
onContentChange,
|
||||
fileName,
|
||||
onFileNameChange,
|
||||
isNewFile,
|
||||
saving,
|
||||
saveSuccess,
|
||||
onSave,
|
||||
onDownload,
|
||||
onClose,
|
||||
loadError,
|
||||
}: PrdEditorWorkspaceProps) {
|
||||
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
||||
const [isDarkMode, setIsDarkMode] = useState<boolean>(true);
|
||||
const [previewMode, setPreviewMode] = useState<boolean>(false);
|
||||
const [wordWrap, setWordWrap] = useState<boolean>(true);
|
||||
const [showGenerateModal, setShowGenerateModal] = useState<boolean>(false);
|
||||
|
||||
const handleOpenGenerateTasks = () => {
|
||||
if (!content.trim()) {
|
||||
alert('Please add content to the PRD before generating tasks.');
|
||||
return;
|
||||
}
|
||||
|
||||
setShowGenerateModal(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 z-[200] md:bg-black/50 md:flex md:items-center md:justify-center',
|
||||
isFullscreen ? 'md:p-0' : 'md:p-4',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'bg-white dark:bg-gray-900 shadow-2xl flex flex-col',
|
||||
'w-full h-full md:rounded-lg md:shadow-2xl',
|
||||
isFullscreen
|
||||
? 'md:w-full md:h-full md:rounded-none'
|
||||
: 'md:w-full md:max-w-6xl md:h-[85vh] md:max-h-[85vh]',
|
||||
)}
|
||||
>
|
||||
{loadError && (
|
||||
<div className="px-4 py-3 border-b border-yellow-200 dark:border-yellow-800 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-200 text-sm">
|
||||
{loadError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PrdEditorHeader
|
||||
fileName={fileName}
|
||||
onFileNameChange={onFileNameChange}
|
||||
isNewFile={isNewFile}
|
||||
previewMode={previewMode}
|
||||
onTogglePreview={() => setPreviewMode((current) => !current)}
|
||||
wordWrap={wordWrap}
|
||||
onToggleWordWrap={() => setWordWrap((current) => !current)}
|
||||
isDarkMode={isDarkMode}
|
||||
onToggleTheme={() => setIsDarkMode((current) => !current)}
|
||||
onDownload={onDownload}
|
||||
onOpenGenerateTasks={handleOpenGenerateTasks}
|
||||
canGenerateTasks={Boolean(content.trim())}
|
||||
onSave={onSave}
|
||||
saving={saving}
|
||||
saveSuccess={saveSuccess}
|
||||
isFullscreen={isFullscreen}
|
||||
onToggleFullscreen={() => setIsFullscreen((current) => !current)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<PrdEditorBody
|
||||
content={content}
|
||||
onContentChange={onContentChange}
|
||||
previewMode={previewMode}
|
||||
isDarkMode={isDarkMode}
|
||||
wordWrap={wordWrap}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PrdEditorFooter content={content} />
|
||||
</div>
|
||||
|
||||
<GenerateTasksModal
|
||||
isOpen={showGenerateModal}
|
||||
fileName={ensurePrdExtension(fileName || 'prd')}
|
||||
onClose={() => setShowGenerateModal(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user