From 7400a03ee87e0871226c0ef9f15f2bdc199d47a9 Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 14 Aug 2024 01:53:51 -0700 Subject: [PATCH] ERC x LFS: begin process --- src/common/livefile/store-live-file.ts | 8 +- .../livefile-patch/useLiveFilePatch.tsx | 98 ++++++++++++++----- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/common/livefile/store-live-file.ts b/src/common/livefile/store-live-file.ts index a78598c33..380cdfed6 100644 --- a/src/common/livefile/store-live-file.ts +++ b/src/common/livefile/store-live-file.ts @@ -33,7 +33,7 @@ interface LiveFileActions { // content updates contentClose: (fileId: LiveFileId) => Promise; - contentReloadFromDisk: (fileId: LiveFileId) => Promise; + contentReloadFromDisk: (fileId: LiveFileId) => Promise; contentWriteAndReload: (fileId: LiveFileId, content: string) => Promise; } @@ -149,11 +149,11 @@ export const useLiveFileStore = create()(persis })); }, - contentReloadFromDisk: async (fileId: LiveFileId) => { + contentReloadFromDisk: async (fileId: LiveFileId): Promise => { const liveFile = _get().liveFiles[fileId]; // Note: .isLoading will also coalesce multiple concurrent reloads into one, as only the first goes through basically - if (!liveFile || liveFile.isLoading || liveFile.isSaving) return; + if (!liveFile || liveFile.isLoading || liveFile.isSaving) return null; _set((state) => ({ liveFiles: { @@ -178,6 +178,7 @@ export const useLiveFileStore = create()(persis }, }, })); + return fileContent; } catch (error: any) { _set((state) => ({ liveFiles: { @@ -190,6 +191,7 @@ export const useLiveFileStore = create()(persis }, }, })); + return null; } }, diff --git a/src/modules/blocks/enhanced-code/livefile-patch/useLiveFilePatch.tsx b/src/modules/blocks/enhanced-code/livefile-patch/useLiveFilePatch.tsx index cdf06081a..5c2781340 100644 --- a/src/modules/blocks/enhanced-code/livefile-patch/useLiveFilePatch.tsx +++ b/src/modules/blocks/enhanced-code/livefile-patch/useLiveFilePatch.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { fileOpen } from 'browser-fs-access'; -import { Box, ColorPaletteProp, Sheet } from '@mui/joy'; +import { Box, Button, ColorPaletteProp, Sheet, Typography } from '@mui/joy'; import { useUXLabsStore } from '~/common/state/store-ux-labs'; @@ -15,11 +15,23 @@ import { isLiveFileSupported, liveFileCreateOrThrow, useLiveFileStore } from '~/ import { liveFileSheetSx } from '~/common/livefile/livefile.theme'; +// Helper function to apply patch (to be implemented) +function applyPatch(srcContent: string, patch: string): string { + // Implement patch application logic + return srcContent; // Placeholder +} + + interface FileOperationStatus { message: React.ReactNode; mtype: 'info' | 'changes' | 'success' | 'error'; } +interface PatchState { + srcContent: string | null; + patchContent: string | null; + newContent: string | null; +} export function useLiveFilePatch(title: string, code: string, isPartial: boolean, isMobile: boolean) { @@ -29,6 +41,7 @@ export function useLiveFilePatch(title: string, code: string, isPartial: boolean */ const [liveFileId, setLiveFileId] = React.useState(null); const [status, setStatus] = React.useState(null); + const [patchState, setPatchState] = React.useState({ srcContent: null, patchContent: null, newContent: null }); // external state const isEnabled = useUXLabsStore((state) => state.labsEnhanceCodeLiveFile && isLiveFileSupported()); @@ -36,47 +49,64 @@ export function useLiveFilePatch(title: string, code: string, isPartial: boolean const { contentReloadFromDisk, contentWriteAndReload } = useLiveFileStore(); const processLiveFile = React.useCallback(async (fileId: LiveFileId) => { - setStatus({ message: 'Processing file...', mtype: 'info' }); + // reset state + setStatus({ message: 'Processing...', mtype: 'info' }); + setPatchState({ srcContent: null, patchContent: null, newContent: null }); try { + // Step 1: Load the latest version of the file - await contentReloadFromDisk(fileId); - const srcFile = useLiveFileStore.getState().liveFiles[fileId]; + setStatus({ message: 'Loading latest file version...', mtype: 'info' }); + const srcContent = await contentReloadFromDisk(fileId); + if (!srcContent) + return setStatus({ message: 'Failed to load file content.', mtype: 'error' }); + setPatchState(prev => ({ ...prev, srcContent })); - // Step 2: Generate patch (to be implemented) - // const patch = await generatePatch(srcFile.content!, code); + // Step 2: Generate patch + setStatus({ message: 'Generating patch...', mtype: 'info' }); + // const patch = await generatePatch(srcContent, code); + const patchContent = '...'; + setPatchState(prev => ({ ...prev, patchContent })); - // Step 3: Apply patch and check if it succeeds (to be implemented) - // const newContent = applyPatch(srcFile.content!, patch); - - // For now, we'll just use the new code directly - const newContent = code; + // Step 3: Apply patch and check if it succeeds + setStatus({ message: 'Verifying patch...', mtype: 'info' }); + const newContent = applyPatch(srcContent, patchContent); + setPatchState(prev => ({ ...prev, newContent })); // Step 4: Success - user can decide to proceed - setStatus({ message: 'Patch generated successfully. Ready to apply.', mtype: 'success' }); + setStatus({ message: 'Patch generated and applied successfully. Ready to save.', mtype: 'success' }); - // You can add a confirmation step here before writing to the file - const shouldWrite = window.confirm('Patch generated successfully. Do you want to apply it to the file?'); - if (shouldWrite) { - setStatus({ message: 'Applying patch...', mtype: 'info' }); - const writeSuccess = await contentWriteAndReload(fileId, newContent); - if (!writeSuccess) { - throw new Error('Failed to write to file'); - } - setStatus({ message: 'Patch applied successfully.', mtype: 'success' }); - } } catch (error) { setStatus({ message: `Error: ${error instanceof Error ? error.message : 'An unknown error occurred'}`, mtype: 'error', }); } - }, [code, contentReloadFromDisk, contentWriteAndReload]); + }, [contentReloadFromDisk]); + + const handleSavePatch = React.useCallback(async () => { + if (!liveFileId || !patchState.newContent) return; + + setStatus({ message: 'Saving changes to file...', mtype: 'info' }); + try { + const writeSuccess = await contentWriteAndReload(liveFileId, patchState.newContent); + if (!writeSuccess) { + throw new Error('Failed to write to file'); + } + setStatus({ message: 'Changes saved successfully.', mtype: 'success' }); + } catch (error) { + setStatus({ + message: `Error saving changes: ${error instanceof Error ? error.message : 'An unknown error occurred'}`, + mtype: 'error', + }); + } + }, [liveFileId, patchState.newContent, contentWriteAndReload]); const handleSelectLiveFile = React.useCallback(async (id: LiveFileId | null) => { setLiveFileId(id); - if (id) + if (id) { await processLiveFile(id); + } }, [processLiveFile]); const handleSelectFileSystemFileHandle = React.useCallback(async (workspaceId: DWorkspaceId | null, fsfHandle: FileSystemFileHandle) => { @@ -151,14 +181,28 @@ export function useLiveFilePatch(title: string, code: string, isPartial: boolean return ( - {status.message} + {status.message} + + {status.mtype === 'success' && patchState.newContent && ( + + + + + + + + )} ); - }, [isEnabled, liveFileId, status]); + }, [isEnabled, liveFileId, status, patchState.newContent, handleSavePatch, processLiveFile]); return { button, actionBar, }; -} \ No newline at end of file +}