From 76ae2e67c69ec3857c89ce749427c1de04fb48d7 Mon Sep 17 00:00:00 2001 From: Saptarshi Date: Mon, 15 Mar 2021 11:53:53 +0530 Subject: [PATCH] P002116-1512: added feature to create new workspace from the dashboard --- main/index.ts | 13 ++ main/modules/shared/utils/create-directory.ts | 16 +++ .../create-workspace-dialog.tsx | 77 ++++++++++++ .../DashboardProjects.tsx | 114 +++++++++++++++++- .../components/new-project/new-project.tsx | 13 +- renderer/pages/projects/index.tsx | 13 +- 6 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 main/modules/shared/utils/create-directory.ts create mode 100644 renderer/modules/projects/projects-dashboard/components/create-workspace-dialog/create-workspace-dialog.tsx diff --git a/main/index.ts b/main/index.ts index de3c4e00..fc9a7a5b 100644 --- a/main/index.ts +++ b/main/index.ts @@ -13,6 +13,7 @@ import DevonInstancesService from './services/devon-instances/devon-instances.se import { DevonfwConfig, IdeDistribution } from './models/devonfw-dists.model'; import { ProfileSetupService } from './services/profile-setup/profile-setup.service'; import { readdirPromise } from './modules/shared/utils/promised'; +import { createDirectory } from './modules/shared/utils/create-directory'; import { InstallListener } from './modules/projects/classes/listeners/install-listener'; import { ProjectCreationListener } from './modules/projects/classes/listeners/project-creation-listener'; import { @@ -154,6 +155,17 @@ async function getDirsFromPath(path: string) { } } +async function createWorkspace(path: string) { + try { + const created = await createDirectory(path); + mainWindow.webContents.send('wsCreation:', created); + return created; + } catch (e) { + mainWindow.webContents.send('wsCreation:', ''); + return ''; + } +} + /* Enable services */ new OpenIdeListener().listen(); @@ -209,3 +221,4 @@ ipcMain.on('set:profile', (e, profile: UserProfile) => ipcMain.on('find:profileStatus', () => checkProfileStatus(mainWindow)); ipcMain.handle('find:profile', () => new ProfileSetupService().getProfile()); ipcMain.handle('get:dirsFromPath', (e, path) => getDirsFromPath(path)); +ipcMain.handle('create:workspace', (e, path) => createWorkspace(path)); diff --git a/main/modules/shared/utils/create-directory.ts b/main/modules/shared/utils/create-directory.ts new file mode 100644 index 00000000..827636a1 --- /dev/null +++ b/main/modules/shared/utils/create-directory.ts @@ -0,0 +1,16 @@ +import * as fs from 'fs'; + +export function createDirectory(path: string): Promise { + const dirReader: Promise = new Promise((resolve, reject) => { + fs.mkdir(path, (err: Error) => { + if (err) { + console.log(err); + reject(err); + } else { + resolve('success'); + } + }); + }); + + return dirReader; +} diff --git a/renderer/modules/projects/projects-dashboard/components/create-workspace-dialog/create-workspace-dialog.tsx b/renderer/modules/projects/projects-dashboard/components/create-workspace-dialog/create-workspace-dialog.tsx new file mode 100644 index 00000000..c726b1ad --- /dev/null +++ b/renderer/modules/projects/projects-dashboard/components/create-workspace-dialog/create-workspace-dialog.tsx @@ -0,0 +1,77 @@ +import { + DialogTitle, + DialogContent, + DialogActions, +} from '../../../../shared/components/dialog'; +import Dialog from '@material-ui/core/Dialog'; +import { FormControl, Button, TextField } from '@material-ui/core'; +import { ChangeEvent } from 'react'; + +interface ConfirmDialogProps { + newWorkspaceName: WorkspaceName; + onNameChange: (value: ChangeEvent) => void; + openDialog: boolean; + onClose: (value: boolean) => void; +} + +interface WorkspaceName { + value: string; + valid?: boolean; + error?: string; + touched?: boolean; +} + +export default function CreateWorkspaceDialog( + props: ConfirmDialogProps +): JSX.Element { + return ( + + + Add New Workspace + + +
+ + + + {props.newWorkspaceName.error && props.newWorkspaceName.touched ? ( +
+ {props.newWorkspaceName.error} +
+ ) : null} +
+
+ + + + +
+ ); +} diff --git a/renderer/modules/projects/projects-dashboard/components/dashboard-project-details/DashboardProjects.tsx b/renderer/modules/projects/projects-dashboard/components/dashboard-project-details/DashboardProjects.tsx index 6afd0d70..157e3949 100644 --- a/renderer/modules/projects/projects-dashboard/components/dashboard-project-details/DashboardProjects.tsx +++ b/renderer/modules/projects/projects-dashboard/components/dashboard-project-details/DashboardProjects.tsx @@ -24,8 +24,10 @@ import NextLink from '../../../../shared/components/nextjs-link/NextLink'; import ProjectDetail from './project-detail'; import Alerts from '../../../../shared/components/alerts/alerts'; import ConfirmDialog from '../../../../shared/components/confirm-dialog/confirm.dialog'; +import CreateWorkspaceDialog from '../create-workspace-dialog/create-workspace-dialog'; import MenuList from '../menu-list/menu-list'; import NewProject from '../new-project/new-project'; +import { join } from 'path'; interface DashboardProjectsProps { projects: ProjectDetails[]; @@ -37,6 +39,13 @@ interface DashboardProjectsProps { workspaces: string[]; } +interface WorkspaceName { + value: string; + valid?: boolean; + error?: string; + touched?: boolean; +} + export default function DashboardProjects( props: DashboardProjectsProps ): JSX.Element { @@ -66,6 +75,21 @@ export default function DashboardProjects( HTMLInputElement >; const [openDialog, setOpenDialog] = useState(false); + const [openCreateWorkspaceDialog, setOpenCreateWorkspaceDialog] = useState< + boolean + >(false); + const [newWorkspaceName, setNewWorkspaceName] = useState({ + value: '', + valid: false, + error: '', + touched: false, + }); + const [workspacesInIDE, setWorkspacesInIDE] = useState([]); + const ERRORMSG = { + workspaceNameExists: 'A workspace with this name already exists', + workspaceNameRequired: 'Please provide a name for the workspace', + pattern: 'Please enter a valid name', + }; const deleteConfimation = (value: boolean): void => { if (value === true) { @@ -90,8 +114,12 @@ export default function DashboardProjects( useEffect(() => { renderer.on('open:projectInIde', ideHandler); renderer.on('delete:project', deleteHandler); + global.ipcRenderer + .invoke('get:dirsFromPath', join(props.dirPath, 'workspaces')) + .then((dirs: string[]) => setWorkspacesInIDE(dirs)); return () => { renderer.removeAll(); + global.ipcRenderer.removeAllListeners('get:dirsFromPath'); }; }, []); @@ -186,6 +214,80 @@ export default function DashboardProjects( }; }; + const openWorkspaceDialog = () => { + setOpenCreateWorkspaceDialog(true); + }; + + const closeWorkspaceDialog = (value: boolean): void => { + if (value === true) { + global.ipcRenderer + .invoke( + 'create:workspace', + join(props.dirPath, 'workspaces', newWorkspaceName.value) + ) + .then((status: string) => console.log(status)); + } + setOpenCreateWorkspaceDialog(false); + updateWsNameInDialog({ + value: '', + valid: false, + error: '', + touched: false, + }); + }; + + const handleWorkspaceNameChange = (event: ChangeEvent) => { + validateNewWorkspaceName(event.target.value); + }; + + const validateNewWorkspaceName = (name: string) => { + const wsName = name; + if ( + workspacesInIDE.filter((ws) => ws.toLowerCase() === wsName.toLowerCase()) + .length + ) { + updateWsNameInDialog({ + value: wsName, + error: ERRORMSG.workspaceNameExists, + valid: false, + }); + } else if (wsName.match(/^[a-z](\-*[a-z]\d*)*$/gi) == null) { + updateWsNameInDialog({ + value: wsName, + error: ERRORMSG.pattern, + valid: false, + }); + } else { + updateWsNameInDialog({ + value: wsName, + error: '', + valid: true, + }); + } + + if (!wsName) { + updateWsNameInDialog({ + value: wsName, + error: ERRORMSG.workspaceNameRequired, + valid: false, + }); + } + }; + + const updateWsNameInDialog = (formData: WorkspaceName) => { + const updatedData = newWorkspaceName; + updatedData.value = formData.value; + updatedData.error = formData.error; + updatedData.valid = formData.valid; + updatedData.touched = true; + setNewWorkspaceName((prevState: WorkspaceName) => { + return { + ...prevState, + updatedData, + }; + }); + }; + return (
- + +
{props.workspaces && props.workspaces.length && @@ -287,6 +393,12 @@ export default function DashboardProjects( openDialog={openDialog} onClose={deleteConfimation} > + ); } diff --git a/renderer/modules/projects/projects-dashboard/components/new-project/new-project.tsx b/renderer/modules/projects/projects-dashboard/components/new-project/new-project.tsx index 672ee873..afa6b8ac 100644 --- a/renderer/modules/projects/projects-dashboard/components/new-project/new-project.tsx +++ b/renderer/modules/projects/projects-dashboard/components/new-project/new-project.tsx @@ -5,20 +5,25 @@ import Typography from '@material-ui/core/Typography'; import useNewProjectStyles from './new-project.styles'; import Box from '@material-ui/core/Box'; -export default function NewProject(): JSX.Element { +interface NewProjectProps { + buttonText: string; + buttonAction?: () => void; +} + +export default function NewProject(props: NewProjectProps): JSX.Element { const classes = useNewProjectStyles(); return ( - + - Add New Project + {props.buttonText} diff --git a/renderer/pages/projects/index.tsx b/renderer/pages/projects/index.tsx index 6b35be17..addb5c34 100644 --- a/renderer/pages/projects/index.tsx +++ b/renderer/pages/projects/index.tsx @@ -26,6 +26,7 @@ export default function Projects(): JSX.Element { ); const [workspaceDir, setWorkspaceDir] = useState([]); const { state, dispatch } = useContext(StepperContext); + const workspaceService = new WorkspaceService(setWorkspaceDir); useEffect(() => { if (state.creatingProject) { @@ -38,8 +39,8 @@ export default function Projects(): JSX.Element { } global.ipcRenderer.on('ide:projects', ideProjectsHandler); - const workspaceService = new WorkspaceService(setWorkspaceDir); - workspaceService.getProjectsInWorkspace(state.projectData.path); + global.ipcRenderer.on('wsCreation:', getUpdatedWorkspaces); + getUpdatedWorkspaces(); return () => { global.ipcRenderer.removeAllListeners('ide:projects'); workspaceService.closeListener(); @@ -49,11 +50,15 @@ export default function Projects(): JSX.Element { useEffect(() => { if (state.projectData.path) { global.ipcRenderer.send('ide:projects', state.projectData.path); - const workspaceService = new WorkspaceService(setWorkspaceDir); - workspaceService.getProjectsInWorkspace(state.projectData.path); + getUpdatedWorkspaces(); } }, [state]); + const getUpdatedWorkspaces = () => { + // const workspaceService = new WorkspaceService(setWorkspaceDir); + workspaceService.getProjectsInWorkspace(state.projectData.path); + }; + const ideProjectsHandler = ( _: unknown, data: { projects: ProjectDetails[] }