From 3b8d1ccdcebcc379fbd8ae54726a83b558c9894d Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Mon, 2 Sep 2024 18:01:23 +0530 Subject: [PATCH] feat: refactor Editor to use new FS --- next.config.js | 2 +- src/components/workspace/Editor/Editor.tsx | 76 ++++++++++--------- .../workspace/WorkSpace/WorkSpace.tsx | 11 +-- .../project/ManageProject/ManageProject.tsx | 5 ++ src/enum/file.ts | 1 + src/hooks/file.hooks.ts | 25 ++++++ src/hooks/fileTabs.hooks.ts | 4 +- src/hooks/index.ts | 1 + src/hooks/projectV2.hooks.ts | 10 +-- 9 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 src/hooks/file.hooks.ts diff --git a/next.config.js b/next.config.js index 59fd021..52f42a0 100644 --- a/next.config.js +++ b/next.config.js @@ -29,7 +29,7 @@ const nextConfig = withTM({ if (!options.isServer) { config.plugins.push( new MonacoWebpackPlugin({ - languages: ["typescript"], + languages: ["typescript", "json"], filename: "static/[name].worker.js", }), ); diff --git a/src/components/workspace/Editor/Editor.tsx b/src/components/workspace/Editor/Editor.tsx index cc0eb3c..18e139b 100644 --- a/src/components/workspace/Editor/Editor.tsx +++ b/src/components/workspace/Editor/Editor.tsx @@ -1,12 +1,14 @@ import { useSettingAction } from '@/hooks/setting.hooks'; -import { useWorkspaceActions } from '@/hooks/workspace.hooks'; import { ContractLanguage, Tree } from '@/interfaces/workspace.interface'; import EventEmitter from '@/utility/eventEmitter'; import { highlightCodeSnippets } from '@/utility/syntaxHighlighter'; -import { fileTypeFromFileName } from '@/utility/utils'; +import { delay, fileTypeFromFileName } from '@/utility/utils'; import EditorDefault, { loader } from '@monaco-editor/react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { FC, useEffect, useRef, useState } from 'react'; +// import { useLatest } from 'react-use'; +import { useFile, useFileTab } from '@/hooks'; +import { useProject } from '@/hooks/projectV2.hooks'; import { useLatest } from 'react-use'; import ReconnectingWebSocket from 'reconnecting-websocket'; import s from './Editor.module.scss'; @@ -14,14 +16,13 @@ import { editorOnMount } from './EditorOnMount'; type Monaco = typeof monaco; interface Props { - file: Tree; - projectId: string; className?: string; } -const Editor: FC = ({ file, projectId, className = '' }) => { - const { updateFileContent, getFileContent, updateOpenFile } = - useWorkspaceActions(); +const Editor: FC = ({ className = '' }) => { + const { activeProject } = useProject(); + const { getFile, saveFile: storeFileContent } = useFile(); + const { fileTab } = useFileTab(); const { isFormatOnSave, getSettingStateByKey } = useSettingAction(); @@ -34,7 +35,7 @@ const Editor: FC = ({ file, projectId, className = '' }) => { // Using this extra state to trigger save file from js event const [saveFileCounter, setSaveFileCounter] = useState(1); - const latestFile = useLatest(file); + const latestFile = useLatest(fileTab.active); const [initialFile, setInitialFile] = useState = ({ file, projectId, className = '' }) => { const saveFile = async () => { const fileContent = editorRef.current?.getValue() ?? ''; - if (!fileContent) return; + if (!fileContent || !fileTab.active) return; try { if (isFormatOnSave()) { editorRef.current?.trigger( @@ -61,9 +62,10 @@ const Editor: FC = ({ file, projectId, className = '' }) => { 'editor.action.formatDocument', {}, ); + await delay(200); } - await updateFileContent(file.id, fileContent, projectId); - EventEmitter.emit('FILE_SAVED', { fileId: file.id }); + await storeFileContent(fileTab.active, fileContent); + EventEmitter.emit('FILE_SAVED', { fileId: fileTab.active }); } catch (error) { /* empty */ } @@ -76,13 +78,16 @@ const Editor: FC = ({ file, projectId, className = '' }) => { window.MonacoEnvironment.getWorkerUrl = (_: string, label: string) => { if (label === 'typescript') { return '/_next/static/ts.worker.js'; + } else if (label === 'json') { + return '/_next/static/json.worker.js'; } return '/_next/static/editor.worker.js'; }; loader.config({ monaco }); + if (!fileTab.active) return; await highlightCodeSnippets( loader, - fileTypeFromFileName(file.name) as ContractLanguage, + fileTypeFromFileName(fileTab.active) as ContractLanguage, ); } @@ -113,8 +118,10 @@ const Editor: FC = ({ file, projectId, className = '' }) => { // If file is changed e.g. in case of build process then force update in editor EventEmitter.on('FORCE_UPDATE_FILE', (filePath: string) => { + const latestFilePath = `/${activeProject}/${latestFile.current}`; + (async () => { - if (filePath !== latestFile.current.path) return; + if (filePath !== latestFilePath) return; await fetchFileContent(true); })().catch((error) => { console.error('Error handling FORCE_UPDATE_FILE event:', error); @@ -127,8 +134,10 @@ const Editor: FC = ({ file, projectId, className = '' }) => { }, [isLoaded]); const fetchFileContent = async (force = false) => { - if ((!file.id || file.id === initialFile?.id) && !force) return; - let content = await getFileContent(file.id); + if (!fileTab.active) return; + if ((!fileTab.active || fileTab.active === initialFile?.id) && !force) + return; + let content = (await getFile(fileTab.active)) as string; if (!editorRef.current) return; let modelContent = editorRef.current.getValue(); @@ -141,25 +150,24 @@ const Editor: FC = ({ file, projectId, className = '' }) => { } else { editorRef.current.setValue(content); } - setInitialFile({ id: file.id, content }); + setInitialFile({ id: fileTab.active, content }); editorRef.current.focus(); }; const markFileDirty = () => { - if (!editorRef.current) return; - const fileContent = editorRef.current.getValue(); - if ( - file.id !== initialFile?.id || - !initialFile.content || - initialFile.content === fileContent - ) { - return; - } - if (!fileContent) { - return; - } - - updateOpenFile(file.id, { isDirty: true }, projectId); + // if (!editorRef.current) return; + // const fileContent = editorRef.current.getValue(); + // if ( + // file.id !== initialFile?.id || + // !initialFile.content || + // initialFile.content === fileContent + // ) { + // return; + // } + // if (!fileContent) { + // return; + // } + // updateOpenFile(file.id, { isDirty: true }, projectId); }; const initializeEditorMode = async () => { @@ -192,7 +200,7 @@ const Editor: FC = ({ file, projectId, className = '' }) => { (async () => { await fetchFileContent(); })().catch(() => {}); - }, [file, isEditorInitialized]); + }, [fileTab.active, isEditorInitialized]); useEffect(() => { if (!monacoRef.current) { @@ -229,12 +237,12 @@ const Editor: FC = ({ file, projectId, className = '' }) => { { const { tab } = router.query; const { activeProject, newFileFolder } = useProject(); + const { fileTab } = useFileTab(); const activeFile = workspaceAction.activeFile(activeProject as string); @@ -205,13 +207,8 @@ const WorkSpace: FC = () => {
- {!activeProject && !activeFile && } - {activeFile && ( - - )} + {!fileTab.active && !activeFile && } + {fileTab.active && }
diff --git a/src/components/workspace/project/ManageProject/ManageProject.tsx b/src/components/workspace/project/ManageProject/ManageProject.tsx index 26cc473..31739e2 100644 --- a/src/components/workspace/project/ManageProject/ManageProject.tsx +++ b/src/components/workspace/project/ManageProject/ManageProject.tsx @@ -16,6 +16,7 @@ const ManageProject: FC = () => { deleteProject, activeProject, loadProjectFiles, + loadProjects, } = useProject(); const projectHeader = () => ( @@ -104,6 +105,10 @@ const ManageProject: FC = () => { openProject(activeProject as string).catch(() => {}); }, [activeProject]); + useEffect(() => { + loadProjects(); + }, []); + return (
diff --git a/src/enum/file.ts b/src/enum/file.ts index ae2af90..483017f 100644 --- a/src/enum/file.ts +++ b/src/enum/file.ts @@ -32,5 +32,6 @@ export enum FileExtensionToFileType { rs = FileType.Rust, fc = FileType.FC, tact = FileType.TACT, + json = FileType.JSON, } /* eslint-enable @typescript-eslint/prefer-literal-enum-member */ diff --git a/src/hooks/file.hooks.ts b/src/hooks/file.hooks.ts new file mode 100644 index 0000000..67cc61f --- /dev/null +++ b/src/hooks/file.hooks.ts @@ -0,0 +1,25 @@ +import fileSystem from '@/lib/fs'; +import { IDEContext } from '@/state/IDE.context'; +import { useContext } from 'react'; +import { baseProjectPath } from './projectV2.hooks'; + +const useFile = () => { + const { activeProject } = useContext(IDEContext); + const projectPath = `${baseProjectPath}${activeProject}`; + const getFile = async (filePath: string) => { + return fileSystem.readFile(`${projectPath}/${filePath}`); + }; + + const saveFile = async (filePath: string, content: string) => { + return fileSystem.writeFile(`${projectPath}/${filePath}`, content, { + overwrite: true, + }); + }; + + return { + getFile, + saveFile, + }; +}; + +export default useFile; diff --git a/src/hooks/fileTabs.hooks.ts b/src/hooks/fileTabs.hooks.ts index f517064..2bbf320 100644 --- a/src/hooks/fileTabs.hooks.ts +++ b/src/hooks/fileTabs.hooks.ts @@ -1,5 +1,6 @@ import fileSystem from '@/lib/fs'; import { IDEContext, IFileTab } from '@/state/IDE.context'; +import EventEmitter from '@/utility/eventEmitter'; import { useContext } from 'react'; const useFileTab = () => { @@ -20,7 +21,7 @@ const useFileTab = () => { if (!(await fileSystem.exists(settingPath))) { await fileSystem.writeFile( settingPath, - JSON.stringify(defaultSetting, null, 2), + JSON.stringify(defaultSetting, null, 4), { overwrite: true, }, @@ -47,6 +48,7 @@ const useFileTab = () => { overwrite: true, }, ); + EventEmitter.emit('FORCE_UPDATE_FILE', settingPath); } catch (error) { console.error('Error syncing tab settings:', error); } diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 927a7e9..50353d1 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1 +1,2 @@ +export { default as useFile } from './file.hooks'; export { default as useFileTab } from './fileTabs.hooks'; diff --git a/src/hooks/projectV2.hooks.ts b/src/hooks/projectV2.hooks.ts index ff61eb0..af91cd2 100644 --- a/src/hooks/projectV2.hooks.ts +++ b/src/hooks/projectV2.hooks.ts @@ -11,7 +11,7 @@ import fileSystem from '@/lib/fs'; import ZIP from '@/lib/zip'; import { RcFile } from 'antd/es/upload'; import cloneDeep from 'lodash.clonedeep'; -import { useContext, useEffect } from 'react'; +import { useContext } from 'react'; import { IDEContext } from '../state/IDE.context'; interface FileNode { @@ -21,6 +21,8 @@ interface FileNode { parent?: string; } +export const baseProjectPath = '/'; + export const useProject = () => { const { projects, @@ -30,7 +32,6 @@ export const useProject = () => { projectFiles, setProjectFiles, } = useContext(IDEContext); - const baseProjectPath = '/'; const loadProjects = async () => { const projectCollection = await fileSystem.readdir(baseProjectPath, { @@ -202,10 +203,6 @@ export const useProject = () => { setActiveProject(projectName); }; - useEffect(() => { - loadProjects(); - }, []); - return { projects, projectFiles, @@ -218,6 +215,7 @@ export const useProject = () => { renameProjectFile, setActiveProject: updateActiveProject, loadProjectFiles, + loadProjects, }; };