mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-07 23:17:37 +00:00
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
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|