2024-07-17 18:54:46 +00:00
|
|
|
import { useStore } from '@nanostores/react';
|
|
|
|
import { AnimatePresence, motion, type Variants } from 'framer-motion';
|
2024-07-18 21:07:04 +00:00
|
|
|
import { memo, useCallback, useEffect } from 'react';
|
2024-07-17 18:54:46 +00:00
|
|
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
2024-07-24 14:10:39 +00:00
|
|
|
import { toast } from 'react-toastify';
|
2024-07-17 18:54:46 +00:00
|
|
|
import { workbenchStore } from '../../lib/stores/workbench';
|
|
|
|
import { cubicEasingFn } from '../../utils/easings';
|
2024-07-18 21:07:04 +00:00
|
|
|
import { renderLogger } from '../../utils/logger';
|
2024-07-24 14:10:39 +00:00
|
|
|
import {
|
|
|
|
type OnChangeCallback as OnEditorChange,
|
|
|
|
type OnScrollCallback as OnEditorScroll,
|
2024-07-18 21:07:04 +00:00
|
|
|
} from '../editor/codemirror/CodeMirrorEditor';
|
2024-07-17 18:54:46 +00:00
|
|
|
import { IconButton } from '../ui/IconButton';
|
|
|
|
import { EditorPanel } from './EditorPanel';
|
|
|
|
import { Preview } from './Preview';
|
|
|
|
|
|
|
|
interface WorkspaceProps {
|
|
|
|
chatStarted?: boolean;
|
2024-07-18 21:07:04 +00:00
|
|
|
isStreaming?: boolean;
|
2024-07-17 18:54:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const workbenchVariants = {
|
|
|
|
closed: {
|
|
|
|
width: 0,
|
|
|
|
transition: {
|
|
|
|
duration: 0.2,
|
|
|
|
ease: cubicEasingFn,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
open: {
|
|
|
|
width: '100%',
|
|
|
|
transition: {
|
|
|
|
duration: 0.2,
|
|
|
|
ease: cubicEasingFn,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} satisfies Variants;
|
|
|
|
|
2024-07-18 21:07:04 +00:00
|
|
|
export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => {
|
|
|
|
renderLogger.trace('Workbench');
|
|
|
|
|
2024-07-17 18:54:46 +00:00
|
|
|
const showWorkbench = useStore(workbenchStore.showWorkbench);
|
2024-07-18 21:07:04 +00:00
|
|
|
const selectedFile = useStore(workbenchStore.selectedFile);
|
|
|
|
const currentDocument = useStore(workbenchStore.currentDocument);
|
2024-07-24 14:10:39 +00:00
|
|
|
const unsavedFiles = useStore(workbenchStore.unsavedFiles);
|
2024-07-18 21:07:04 +00:00
|
|
|
|
|
|
|
const files = useStore(workbenchStore.files);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
workbenchStore.setDocuments(files);
|
|
|
|
}, [files]);
|
|
|
|
|
|
|
|
const onEditorChange = useCallback<OnEditorChange>((update) => {
|
|
|
|
workbenchStore.setCurrentDocumentContent(update.content);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onEditorScroll = useCallback<OnEditorScroll>((position) => {
|
|
|
|
workbenchStore.setCurrentDocumentScrollPosition(position);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onFileSelect = useCallback((filePath: string | undefined) => {
|
|
|
|
workbenchStore.setSelectedFile(filePath);
|
|
|
|
}, []);
|
2024-07-17 18:54:46 +00:00
|
|
|
|
2024-07-24 14:10:39 +00:00
|
|
|
const onFileSave = useCallback(() => {
|
|
|
|
workbenchStore.saveCurrentDocument().catch(() => {
|
|
|
|
toast.error('Failed to update file content');
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const onFileReset = useCallback(() => {
|
|
|
|
workbenchStore.resetCurrentDocument();
|
|
|
|
}, []);
|
|
|
|
|
2024-07-17 18:54:46 +00:00
|
|
|
return (
|
|
|
|
chatStarted && (
|
|
|
|
<AnimatePresence>
|
|
|
|
{showWorkbench && (
|
|
|
|
<motion.div initial="closed" animate="open" exit="closed" variants={workbenchVariants}>
|
|
|
|
<div className="fixed top-[calc(var(--header-height)+1.5rem)] bottom-[calc(1.5rem-1px)] w-[50vw] mr-4 z-0">
|
|
|
|
<div className="flex flex-col bg-white border border-gray-200 shadow-sm rounded-lg overflow-hidden absolute inset-0 right-8">
|
|
|
|
<div className="px-3 py-2 border-b border-gray-200">
|
|
|
|
<IconButton
|
|
|
|
icon="i-ph:x-circle"
|
2024-07-24 14:10:39 +00:00
|
|
|
className="ml-auto -mr-1"
|
2024-07-17 18:54:46 +00:00
|
|
|
size="xxl"
|
|
|
|
onClick={() => {
|
|
|
|
workbenchStore.showWorkbench.set(false);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-hidden">
|
|
|
|
<PanelGroup direction="vertical">
|
|
|
|
<Panel defaultSize={50} minSize={20}>
|
2024-07-18 21:07:04 +00:00
|
|
|
<EditorPanel
|
|
|
|
editorDocument={currentDocument}
|
|
|
|
isStreaming={isStreaming}
|
|
|
|
selectedFile={selectedFile}
|
|
|
|
files={files}
|
2024-07-24 14:10:39 +00:00
|
|
|
unsavedFiles={unsavedFiles}
|
2024-07-18 21:07:04 +00:00
|
|
|
onFileSelect={onFileSelect}
|
|
|
|
onEditorScroll={onEditorScroll}
|
|
|
|
onEditorChange={onEditorChange}
|
2024-07-24 14:10:39 +00:00
|
|
|
onFileSave={onFileSave}
|
|
|
|
onFileReset={onFileReset}
|
2024-07-18 21:07:04 +00:00
|
|
|
/>
|
2024-07-17 18:54:46 +00:00
|
|
|
</Panel>
|
|
|
|
<PanelResizeHandle />
|
|
|
|
<Panel defaultSize={50} minSize={20}>
|
|
|
|
<Preview />
|
|
|
|
</Panel>
|
|
|
|
</PanelGroup>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
</AnimatePresence>
|
|
|
|
)
|
|
|
|
);
|
2024-07-18 21:07:04 +00:00
|
|
|
});
|