diff --git a/src/common/redux/workspaces/actions.js b/src/common/redux/workspaces/actions.js index eb7da0b31..0f872949d 100644 --- a/src/common/redux/workspaces/actions.js +++ b/src/common/redux/workspaces/actions.js @@ -82,6 +82,15 @@ export const deleteWorkspace = function(name) { }; }; +export const CLONE_WORKSPACE = `${prefix}/CLONE_WORKSPACE`; +export const cloneWorkspace = function(name, cloneName) { + return function(dispatch) { + dispatch({ type: CLONE_WORKSPACE, name, cloneName }); + + ipcRenderer.send(CLONE_WORKSPACE, name, cloneName); + }; +}; + export const SET_CURRENT_WORKSPACE = `${prefix}/SET_CURRENT_WORKSPACE`; export const setCurrentWorkspace = function(workspace, contractCache) { return { type: SET_CURRENT_WORKSPACE, workspace, contractCache }; diff --git a/src/main/index.js b/src/main/index.js index b0e570f18..b47610aac 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -40,6 +40,7 @@ import { GET_CONTRACT_DETAILS, OPEN_NEW_WORKSPACE_CONFIG, PROJECT_UPDATED, + CLONE_WORKSPACE, } from "../common/redux/workspaces/actions"; import { @@ -481,6 +482,20 @@ app.on('ready', () => { ); }); + ipcMain.on(CLONE_WORKSPACE, async (event, name, cloneName) => { + const sourceWorkspace = workspaceManager.get(name); + if (sourceWorkspace) { + sourceWorkspace.clone(cloneName); + + workspaceManager.bootstrap(); + + mainWindow.webContents.send( + SET_WORKSPACES, + workspaceManager.getNonDefaultNames(), + ); + } + }); + ipcMain.on(DELETE_WORKSPACE, async (event, name) => { const tempWorkspace = workspaceManager.get(name); if (tempWorkspace) { diff --git a/src/main/types/workspaces/Workspace.js b/src/main/types/workspaces/Workspace.js index 2765ea7a7..35db7fd2c 100644 --- a/src/main/types/workspaces/Workspace.js +++ b/src/main/types/workspaces/Workspace.js @@ -1,5 +1,6 @@ import path from "path"; import fse from "fs-extra"; +import UUID from "uuid"; import WorkspaceSettings from "../settings/WorkspaceSettings"; import ContractCache from "../contracts/ContractCache"; @@ -117,6 +118,40 @@ class Workspace { // a solution here } } + + clone(cloneName) { + const sanitizedName = Workspace.getSanitizedName(cloneName); + const configDirectory = path.join(this.workspaceDirectory, "..", ".."); + const cloneDirectory = path.join( + configDirectory, + "workspaces", + sanitizedName, + ); + + try { + // copy workspace directory, make sure not to overwrite any existing data + fse.copySync(this.workspaceDirectory, cloneDirectory, { + overwrite: false, + errorOnExist: true, + }); + } catch (e) { + // Failed to copy directory. Most likely target already exists + // TODO: Handle this error more gracefully/provide user feedback + return; + } + + // update settings of cloned workspace to match new location + const db_path = path.join(cloneDirectory, "chaindata"); + let settings = new WorkspaceSettings(cloneDirectory, db_path); + settings.bootstrap(); + // set new db_path + settings.set("server.db_path", db_path); + // generate new uuid + settings.set("uuid", UUID.v4()); + // set new name + settings.set("name", cloneName); + return new Workspace(cloneName, configDirectory); + } } export default Workspace; diff --git a/src/renderer/icons/clone-regular.svg b/src/renderer/icons/clone-regular.svg new file mode 100644 index 000000000..c14f573e8 --- /dev/null +++ b/src/renderer/icons/clone-regular.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/screens/startup/HomeScreen.js b/src/renderer/screens/startup/HomeScreen.js index 2f96aea1d..dfc53a7e5 100644 --- a/src/renderer/screens/startup/HomeScreen.js +++ b/src/renderer/screens/startup/HomeScreen.js @@ -9,6 +9,7 @@ import { openDefaultWorkspace, openNewWorkspaceConfig, deleteWorkspace, + cloneWorkspace, } from "../../../common/redux/workspaces/actions"; import UpdateNotification from "../auto-update/UpdateNotification"; import ErrorModal from "../../components/modal/ErrorModal"; @@ -22,6 +23,7 @@ import Logo from "../../icons/logo.svg"; import ChainIcon from "../../icons/chain.svg"; import MenuIcon from "../../icons/list.svg"; import TrashIcon from "../../icons/trash-icon.svg"; +import CloneIcon from "../../icons/clone-regular.svg"; class HomeScreen extends Component { constructor(props) { @@ -33,8 +35,21 @@ class HomeScreen extends Component { this.props.dispatch(openWorkspace(workspaceName)); } + handleCloneWorkspace(e) { + const workspaceName = e.currentTarget.parentElement.querySelector("span").innerText; + e.stopPropagation(); + e.preventDefault(); + + document.activeElement.blur(); + + // Future improvement: Create modal dialog asking for new name + // For now, just go with hardcoded extension + const cloneName = workspaceName + "-clone"; + this.props.dispatch(cloneWorkspace(workspaceName, cloneName)); + } + handleDeleteWorkspace(e) { - const workspaceName = e.currentTarget.previousSibling.innerText; + const workspaceName = e.currentTarget.parentElement.querySelector("span").innerText; e.stopPropagation(); e.preventDefault(); @@ -79,8 +94,16 @@ class HomeScreen extends Component {