diff --git a/apps/remix-ide-e2e/src/tests/homeTab.test.ts b/apps/remix-ide-e2e/src/tests/homeTab.test.ts
index 81f78be22a..7285556078 100644
--- a/apps/remix-ide-e2e/src/tests/homeTab.test.ts
+++ b/apps/remix-ide-e2e/src/tests/homeTab.test.ts
@@ -4,17 +4,14 @@ import init from '../helpers/init'
module.exports = {
- before: function (browser: NightwatchBrowser, done: VoidFunction) {
- init(browser, done)
- },
-
- 'Should create new file': function (browser: NightwatchBrowser) {
- browser
- .waitForElementVisible('*[data-id="homeTabNewFile"]')
- .click('*[data-id="homeTabNewFile"]')
- .waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
- .sendKeys('*[data-id$="/blank"] .remixui_items', 'newTestFile')
- .sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
- .waitForElementVisible('li[data-id="treeViewLitreeViewItemnewTestFile.sol"]')
- }
+ before: function (browser: NightwatchBrowser, done: VoidFunction) {
+ init(browser, done)
+ },
+
+ 'Should start coding': function (browser: NightwatchBrowser) {
+ browser
+ .waitForElementVisible('*[data-id="homeTabStartCoding"]')
+ .click('*[data-id="homeTabStartCoding"]')
+ .waitForElementVisible('div[data-id="treeViewDivtreeViewItemcontracts/helloWorld.sol"]')
+ }
}
\ No newline at end of file
diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
index 1cc93342bc..fffb4cc82a 100644
--- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
+++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
@@ -235,7 +235,7 @@ const sources = [
content:
`
pragma solidity ^0.8.0;
- contract helloWorld {
+ contract HelloWorld {
string public message;
fallback () external {
@@ -249,7 +249,7 @@ const sources = [
},
'checkBalance.sol': {
content: `pragma solidity ^0.8.0;
- contract checkBalance {
+ contract CheckBalance {
constructor () payable {}
function sendSomeEther(uint256 num) public {
diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts
index 2522d94be7..154e571bb0 100644
--- a/apps/remix-ide/src/app/files/fileManager.ts
+++ b/apps/remix-ide/src/app/files/fileManager.ts
@@ -22,9 +22,11 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
- methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
- 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh',
- 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory'],
+ methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
+ 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
+ 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
+ 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory'
+ ],
kind: 'file-system'
}
const errorMsg = {
@@ -37,7 +39,6 @@ const errorMsg = {
const createError = (err) => {
return new Error(`${errorMsg[err.code]} ${err.message || ''}`)
}
-
class FileManager extends Plugin {
mode: string
openedFiles: any
@@ -249,6 +250,30 @@ class FileManager extends Plugin {
throw new Error(e)
}
}
+
+ /**
+ * Set the content of a specific file, does nnot rewrite file if it exists but creates a new unique name
+ * @param {string} path path of the file
+ * @param {string} data content to write on the file
+ * @returns {void}
+ */
+ async writeFileNoRewrite(path, data) {
+ try {
+ path = this.normalize(path)
+ path = this.limitPluginScope(path)
+ if (await this.exists(path)) {
+ const newPath = await helper.createNonClashingNameAsync(path, this)
+ const content = await this.setFileContent(newPath, data)
+ return {newContent: content, newPath}
+ } else {
+ const ret = await this.setFileContent(path, data)
+ this.emit('fileAdded', path)
+ return {newContent: ret, newpath: path}
+ }
+ } catch (e) {
+ throw new Error(e)
+ }
+ }
/**
* Return the content of a specific file
diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js
index b391daf5bd..7b813aea1c 100644
--- a/apps/remix-ide/src/app/panels/file-panel.js
+++ b/apps/remix-ide/src/app/panels/file-panel.js
@@ -30,7 +30,19 @@ const { SlitherHandle } = require('../files/slither-handle.js')
const profile = {
name: 'filePanel',
displayName: 'File explorer',
- methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getAvailableWorkspaceName', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'],
+ methods: [
+ 'createNewFile',
+ 'uploadFile',
+ 'getCurrentWorkspace',
+ 'getAvailableWorkspaceName',
+ 'getWorkspaces',
+ 'createWorkspace',
+ 'switchToWorkspace',
+ 'setWorkspace',
+ 'registerContextMenuItem',
+ 'renameWorkspace',
+ 'deleteWorkspace',
+ ],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
description: 'Remix IDE file explorer',
@@ -41,7 +53,7 @@ const profile = {
maintainedBy: 'Remix'
}
module.exports = class Filepanel extends ViewPlugin {
- constructor (appManager) {
+ constructor(appManager) {
super(profile)
this.registry = Registry.getInstance()
this.fileProviders = this.registry.get('fileproviders').api
@@ -60,13 +72,17 @@ module.exports = class Filepanel extends ViewPlugin {
this.currentWorkspaceMetadata = null
}
- render () {
- return
+ render() {
+ return (
+
+
+
+ )
}
/**
* @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] }
- * typically:
+ * typically:
* group 0 for file manipulations
* group 1 for download operations
* group 2 for running operations (script for instance)
@@ -77,7 +93,7 @@ module.exports = class Filepanel extends ViewPlugin {
* group 7 for generating resource files (UML, documentation, ...)
* @param callback (...args) => void
*/
- registerContextMenuItem (item) {
+ registerContextMenuItem(item) {
return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err)
@@ -86,7 +102,7 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- removePluginActions (plugin) {
+ removePluginActions(plugin) {
return new Promise((resolve, reject) => {
this.emit('removePluginActionsReducerEvent', plugin, (err, data) => {
if (err) reject(err)
@@ -95,30 +111,30 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- getCurrentWorkspace () {
+ getCurrentWorkspace() {
return this.currentWorkspaceMetadata
}
- getWorkspaces () {
+ getWorkspaces() {
return this.workspaces
}
- getAvailableWorkspaceName (name) {
- if(!this.workspaces) return name
+ getAvailableWorkspaceName(name) {
+ if (!this.workspaces) return name
let index = 1
- let workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
+ let workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
while (workspace) {
index++
- workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index)
+ workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
}
return name + ' - ' + index
}
- setWorkspaces (workspaces) {
+ setWorkspaces(workspaces) {
this.workspaces = workspaces
}
- createNewFile () {
+ createNewFile() {
return new Promise((resolve, reject) => {
this.emit('createNewFileInputReducerEvent', '/', (err, data) => {
if (err) reject(err)
@@ -127,7 +143,7 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- uploadFile (target) {
+ uploadFile(target) {
return new Promise((resolve, reject) => {
return this.emit('uploadFileReducerEvent', '/', target, (err, data) => {
if (err) reject(err)
@@ -136,7 +152,7 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- createWorkspace (workspaceName, workspaceTemplateName, isEmpty) {
+ createWorkspace(workspaceName, workspaceTemplateName, isEmpty) {
return new Promise((resolve, reject) => {
this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => {
if (err) reject(err)
@@ -145,7 +161,7 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- renameWorkspace (oldName, workspaceName) {
+ renameWorkspace(oldName, workspaceName) {
return new Promise((resolve, reject) => {
this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => {
if (err) reject(err)
@@ -154,7 +170,7 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- deleteWorkspace (workspaceName) {
+ deleteWorkspace(workspaceName) {
return new Promise((resolve, reject) => {
this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => {
if (err) reject(err)
@@ -163,25 +179,52 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
- setWorkspace (workspace) {
- const workspaceProvider = this.fileProviders.workspace
+ saveRecent(workspaceName) {
+ if (!localStorage.getItem('recentWorkspaces')) {
+ localStorage.setItem('recentWorkspaces', JSON.stringify([ workspaceName ]))
+ } else {
+ let recents = JSON.parse(localStorage.getItem('recentWorkspaces'))
+ // checking if we have a duplication
+ if (!recents.find((el) => {
+ return el === workspaceName
+ })) {
+ recents = ([workspaceName, ...recents])
+ recents = recents.filter((el) => { return el != "" })
+ localStorage.setItem('recentWorkspaces', JSON.stringify(recents))
+ }
+ }
+ }
- this.currentWorkspaceMetadata = { name: workspace.name, isLocalhost: workspace.isLocalhost, absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}` }
- if (workspace.name !== " - connect to localhost - ") {
+ setWorkspace(workspace) {
+ const workspaceProvider = this.fileProviders.workspace
+ const current = this.currentWorkspaceMetadata
+ this.currentWorkspaceMetadata = {
+ name: workspace.name,
+ isLocalhost: workspace.isLocalhost,
+ absolutePath: `${workspaceProvider.workspacesPath}/${workspace.name}`,
+ }
+ if (this.currentWorkspaceMetadata.name !== current) {
+ this.saveRecent(workspace.name)
+ }
+ if (workspace.name !== ' - connect to localhost - ') {
localStorage.setItem('currentWorkspace', workspace.name)
}
this.emit('setWorkspace', workspace)
}
- workspaceRenamed (oldName, workspaceName) {
+ switchToWorkspace(workspaceName) {
+ this.emit('switchToWorkspace', workspaceName)
+ }
+
+ workspaceRenamed(oldName, workspaceName) {
this.emit('workspaceRenamed', oldName, workspaceName)
}
- workspaceDeleted (workspace) {
+ workspaceDeleted(workspace) {
this.emit('workspaceDeleted', workspace)
}
- workspaceCreated (workspace) {
+ workspaceCreated(workspace) {
this.emit('workspaceCreated', workspace)
}
/** end section */
diff --git a/apps/remix-ide/src/app/tabs/locales/en/home.json b/apps/remix-ide/src/app/tabs/locales/en/home.json
index dc76b8d547..76578a4db8 100644
--- a/apps/remix-ide/src/app/tabs/locales/en/home.json
+++ b/apps/remix-ide/src/app/tabs/locales/en/home.json
@@ -56,7 +56,10 @@
"home.searchDocumentation": "Search Documentation",
"home.files": "Files",
"home.newFile": "New File",
+ "home.startCoding": "Start Coding",
+ "home.startCodingPlayground": "Open a playground for prototyping and simply learning",
"home.openFile": "Open File",
+ "home.openFileTooltip": "Open a File from your File System",
"home.accessFileSystem": "Access File System",
"home.loadFrom": "Load from",
"home.resources": "Resources",
diff --git a/apps/remix-ide/src/app/tabs/locales/fr/home.json b/apps/remix-ide/src/app/tabs/locales/fr/home.json
index cfe3e5df99..4efa0a4bd6 100644
--- a/apps/remix-ide/src/app/tabs/locales/fr/home.json
+++ b/apps/remix-ide/src/app/tabs/locales/fr/home.json
@@ -55,6 +55,7 @@
"home.files": "Files",
"home.newFile": "New File",
"home.openFile": "Open File",
+ "home.openFileTooltip": "Open a File from you File System",
"home.connectToLocalhost": "Access File System",
"home.loadFrom": "Load from",
"home.resources": "Resources"
diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js
index 9f4a70e017..ce2351de46 100644
--- a/apps/remix-ide/src/remixAppManager.js
+++ b/apps/remix-ide/src/remixAppManager.js
@@ -5,8 +5,7 @@ import {IframePlugin} from '@remixproject/engine-web'
const _paq = (window._paq = window._paq || [])
// requiredModule removes the plugin from the plugin manager list on UI
-const requiredModules = [
- // services + layout views + system views
+const requiredModules = [ // services + layout views + system views
'manager',
'config',
'compilerArtefacts',
@@ -74,7 +73,8 @@ const requiredModules = [
'compilationDetails',
'contractflattener',
'solidity-script',
- 'openaigpt'
+ 'openaigpt',
+ 'home'
]
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
index 3f187e57d5..41d0e13f23 100644
--- a/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
+++ b/libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import React, {useState, useRef, useReducer} from 'react'
-import {FormattedMessage} from 'react-intl'
-import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line
-import {Toaster} from '@remix-ui/toaster' // eslint-disable-line
+import React, { useState, useRef, useReducer, useEffect } from 'react'
+import { FormattedMessage } from 'react-intl'
+import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
+import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
const _paq = (window._paq = window._paq || []) // eslint-disable-line
-import {CustomTooltip} from '@remix-ui/helper'
+import { CustomTooltip } from '@remix-ui/helper'
+import { TEMPLATE_NAMES } from '@remix-ui/workspace'
interface HomeTabFileProps {
plugin: any
@@ -25,7 +26,7 @@ const loadingReducer = (state = loadingInitialState, action) => {
}
}
-function HomeTabFile({plugin}: HomeTabFileProps) {
+function HomeTabFile({ plugin }: HomeTabFileProps) {
const [state, setState] = useState<{
searchInput: string
showModalDialog: boolean
@@ -37,18 +38,55 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
}
importSource: string
toasterMsg: string
+ recentWorkspaces: Array
}>({
searchInput: '',
showModalDialog: false,
- modalInfo: {title: '', loadItem: '', examples: [], prefix: ''},
+ modalInfo: { title: '', loadItem: '', examples: [], prefix: '' },
importSource: '',
- toasterMsg: ''
+ toasterMsg: '',
+ recentWorkspaces: []
})
const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const inputValue = useRef(null)
+ useEffect(() => {
+ plugin.on('filePanel', 'setWorkspace', async () => {
+ let recents = JSON.parse(localStorage.getItem('recentWorkspaces'))
+
+ if (!recents) {
+ recents = []
+ } else {
+ setState((prevState) => {
+ return { ...prevState, recentWorkspaces: recents.slice(0, recents.length <= 3 ? recents.length : 3) }
+ })
+ }
+ })
+
+ const deleteSavedWorkspace = (name) => {
+ const recents = JSON.parse(localStorage.getItem('recentWorkspaces'))
+ let newRecents = recents
+ if (!recents) {
+ newRecents = []
+ } else {
+ newRecents = recents.filter((el) => { return el !== name})
+ localStorage.setItem('recentWorkspaces', JSON.stringify(newRecents))
+ }
+ setState((prevState) => {
+ return { ...prevState, recentWorkspaces: newRecents.slice(0, newRecents.length <= 3 ? newRecents.length : 3) }
+ })
+ }
+ plugin.on('filePanel', 'workspaceDeleted', async (deletedName) => {
+ deleteSavedWorkspace(deletedName)
+ })
+ return () => {
+ plugin.off('filePanel', 'setWorkspace')
+ plugin.off('filePanel', 'workspaceDeleted')
+ }
+ }, [plugin])
+
const processLoading = (type: string) => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'importFrom' + type])
const contentImport = plugin.contentImport
@@ -57,12 +95,12 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
if ((type === 'ipfs' || type === 'IPFS') && startsWith !== 'ipfs' && startsWith !== 'IPFS') {
setState((prevState) => {
- return {...prevState, importSource: startsWith + state.importSource}
+ return { ...prevState, importSource: startsWith + state.importSource }
})
}
contentImport.import(
state.modalInfo.prefix + state.importSource,
- (loadingMsg) => dispatch({tooltip: loadingMsg}),
+ (loadingMsg) => dispatch({ tooltip: loadingMsg }),
async (error, content, cleanUrl, type, url) => {
if (error) {
toast(error.message || error)
@@ -80,22 +118,47 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
}
)
setState((prevState) => {
- return {...prevState, showModalDialog: false, importSource: ''}
+ return { ...prevState, showModalDialog: false, importSource: '' }
})
}
const toast = (message: string) => {
setState((prevState) => {
- return {...prevState, toasterMsg: message}
+ return { ...prevState, toasterMsg: message }
})
}
- const createNewFile = async () => {
- _paq.push(['trackEvent', 'hometab', 'filesSection', 'createNewFile'])
+ const startCoding = async () => {
+ _paq.push(['trackEvent', 'hometab', 'filesSection', 'startCoding'])
plugin.verticalIcons.select('filePanel')
- await plugin.call('filePanel', 'createNewFile')
- }
+ const wName = 'Playground'
+ const workspaces = await plugin.call('filePanel', 'getWorkspaces')
+ let createFile = true
+ if (!workspaces.find((workspace) => workspace.name === wName)) {
+ await plugin.call('filePanel', 'createWorkspace', wName, 'playground')
+ createFile = false
+ }
+ await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false })
+ await plugin.call('filePanel', 'switchToWorkspace', { name: wName, isLocalHost: false }) // calling once is not working.
+ const content = `// SPDX-License-Identifier: MIT
+ pragma solidity >=0.6.12 <0.9.0;
+
+ contract HelloWorld {
+ function print() public pure returns (string memory) {
+ return "Hello World!";
+ }
+ }
+ `
+ if (createFile) {
+ const { newPath } = await plugin.call('fileManager', 'writeFileNoRewrite', '/contracts/helloWorld.sol', content)
+ await plugin.call('fileManager', 'open', newPath)
+ } else {
+ await plugin.call('fileManager', 'open', '/contracts/helloWorld.sol')
+ }
+
+ }
+
const uploadFile = async (target) => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'uploadFile'])
await plugin.call('filePanel', 'uploadFile', target)
@@ -120,8 +183,8 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
title: title,
loadItem: loadItem,
examples: examples,
- prefix
- }
+ prefix,
+ },
}
})
}
@@ -129,10 +192,15 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
const hideFullMessage = () => {
//eslint-disable-line
setState((prevState) => {
- return {...prevState, showModalDialog: false, importSource: ''}
+ return { ...prevState, showModalDialog: false, importSource: '' }
})
}
+ const handleSwichToRecentWorkspace = async (e, workspaceName) => {
+ e.preventDefault()
+ await plugin.call('filePanel', 'switchToWorkspace', { name: workspaceName, isLocalhost: false })
+ }
+
const examples = state.modalInfo.examples.map((urlEl, key) => (
{urlEl}
@@ -161,15 +229,15 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
{state.modalInfo.prefix &&
ipfs://}
{
setState((prevState) => {
- return {...prevState, importSource: inputValue.current.value}
+ return { ...prevState, importSource: inputValue.current.value }
})
}}
/>
@@ -178,40 +246,82 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
-
diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts
index 3c85231cb6..526bc2c467 100644
--- a/libs/remix-ui/workspace/src/lib/actions/events.ts
+++ b/libs/remix-ui/workspace/src/lib/actions/events.ts
@@ -40,6 +40,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
uploadFile(target, dir, cb)
})
+ plugin.on('filePanel', 'switchToWorkspace', async (workspace) => {
+ await switchToWorkspace(workspace.name)
+ })
+
plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => {
setFileDecorators(items)
})
diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts
index e425315711..c4818c8d90 100644
--- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts
+++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts
@@ -2,7 +2,30 @@ import React from 'react'
import { bufferToHex } from '@ethereumjs/util'
import { hash } from '@remix-project/remix-lib'
import axios, { AxiosResponse } from 'axios'
-import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setCurrentWorkspaceBranches, setCurrentWorkspaceCurrentBranch, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace, setCurrentWorkspaceIsGitRepo, setGitConfig } from './payload'
+import {
+ addInputFieldSuccess,
+ cloneRepositoryFailed,
+ cloneRepositoryRequest,
+ cloneRepositorySuccess,
+ createWorkspaceError,
+ createWorkspaceRequest,
+ createWorkspaceSuccess,
+ displayNotification,
+ displayPopUp,
+ fetchWorkspaceDirectoryError,
+ fetchWorkspaceDirectoryRequest,
+ fetchWorkspaceDirectorySuccess,
+ hideNotification,
+ setCurrentWorkspace,
+ setCurrentWorkspaceBranches,
+ setCurrentWorkspaceCurrentBranch,
+ setDeleteWorkspace,
+ setMode,
+ setReadOnlyMode,
+ setRenameWorkspace,
+ setCurrentWorkspaceIsGitRepo,
+ setGitConfig,
+} from './payload'
import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper'
import { FileTree, JSONStandardInput, WorkspaceTemplate } from '../types'
@@ -16,14 +39,15 @@ import { AppModal, ModalTypes } from '@remix-ui/app'
import { contractDeployerScripts, etherscanScripts } from '@remix-project/remix-ws-templates'
declare global {
- interface Window { remixFileSystemCallback: IndexedDBStorage; }
+ interface Window {
+ remixFileSystemCallback: IndexedDBStorage
+ }
}
-
const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - '
const queryParams = new QueryParams()
-const _paq = window._paq = window._paq || [] //eslint-disable-line
+const _paq = (window._paq = window._paq || []) //eslint-disable-line
let plugin, dispatch: React.Dispatch
export const setPlugin = (filePanelPlugin, reducerDispatch) => {
@@ -70,19 +94,29 @@ export const addInputField = async (type: 'file' | 'folder', path: string, cb?:
})
})
- promise.then((files) => {
- dispatch(addInputFieldSuccess(path, files, type))
- }).catch((error) => {
- console.error(error)
- })
+ promise
+ .then((files) => {
+ dispatch(addInputFieldSuccess(path, files, type))
+ })
+ .catch((error) => {
+ console.error(error)
+ })
return promise
}
const removeSlash = (s: string) => {
- return s.replace(/^\/+/, "")
-}
-
-export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, opts = null, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean = false, createCommit: boolean = true) => {
+ return s.replace(/^\/+/, '')
+}
+
+export const createWorkspace = async (
+ workspaceName: string,
+ workspaceTemplateName: WorkspaceTemplate,
+ opts = null,
+ isEmpty = false,
+ cb?: (err: Error, result?: string | number | boolean | Record) => void,
+ isGitRepo: boolean = false,
+ createCommit: boolean = true
+) => {
await plugin.fileManager.closeAllFiles()
const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName)
dispatch(createWorkspaceRequest())
@@ -121,7 +155,7 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
await plugin.call('dGitProvider', 'commit', {
author: {
name,
- email
+ email,
},
message: `Initial commit: remix template ${workspaceTemplateName}`,
})
@@ -138,7 +172,6 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
}
if (workspaceTemplateName === 'semaphore') {
const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler')
-
if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler')
}
// this call needs to be here after the callback because it calls dGitProvider which also calls this function and that would cause an infinite loop
@@ -153,18 +186,17 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
export const createWorkspaceTemplate = async (workspaceName: string, template: WorkspaceTemplate = 'remixDefault') => {
if (!workspaceName) throw new Error('workspace name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
- if (await workspaceExists(workspaceName) && template === 'remixDefault') throw new Error('workspace already exists')
+ if ((await workspaceExists(workspaceName)) && template === 'remixDefault') throw new Error('workspace already exists')
else {
const workspaceProvider = plugin.fileProviders.workspace
-
await workspaceProvider.createWorkspace(workspaceName)
}
}
export type UrlParametersType = {
- gist: string,
- code: string,
- url: string,
+ gist: string
+ code: string
+ url: string
language: string
}
@@ -176,7 +208,8 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
case 'code-template':
// creates a new workspace code-sample and loads code from url params.
try {
- let path = ''; let content
+ let path = ''
+ let content
if (params.code) {
const hashed = bufferToHex(hash.keccakFromString(params.code))
@@ -193,7 +226,7 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
try {
content = JSON.parse(content) as any
- if (content.language && content.language === "Solidity" && content.sources) {
+ if (content.language && content.language === 'Solidity' && content.sources) {
const standardInput: JSONStandardInput = content as JSONStandardInput
for (const [fname, source] of Object.entries(standardInput.sources)) {
await workspaceProvider.set(fname, source.content)
@@ -221,7 +254,18 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
const data = response.data as { files: any }
if (!data.files) {
- return dispatch(displayNotification('Gist load error', 'No files found', 'OK', null, () => { dispatch(hideNotification()) }, null))
+ return dispatch(
+ displayNotification(
+ 'Gist load error',
+ 'No files found',
+ 'OK',
+ null,
+ () => {
+ dispatch(hideNotification())
+ },
+ null
+ )
+ )
}
const obj = {}
@@ -232,11 +276,22 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
})
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (errorLoadingFile) {
- dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => { }, null))
+ dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
}
})
} catch (e) {
- dispatch(displayNotification('Gist load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
+ dispatch(
+ displayNotification(
+ 'Gist load error',
+ e.message,
+ 'OK',
+ null,
+ () => {
+ dispatch(hideNotification())
+ },
+ null
+ )
+ )
console.error(e)
}
break
@@ -256,7 +311,18 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
}
}
} catch (e) {
- dispatch(displayNotification('Workspace load error', e.message, 'OK', null, () => { dispatch(hideNotification()) }, null))
+ dispatch(
+ displayNotification(
+ 'Workspace load error',
+ e.message,
+ 'OK',
+ null,
+ () => {
+ dispatch(hideNotification())
+ },
+ null
+ )
+ )
console.error(e)
}
break
@@ -277,17 +343,18 @@ export const fetchWorkspaceDirectory = async (path: string) => {
const promise: Promise = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree: FileTree) => {
if (error) console.error(error)
-
resolve(fileTree)
})
})
dispatch(fetchWorkspaceDirectoryRequest())
- promise.then((fileTree) => {
- dispatch(fetchWorkspaceDirectorySuccess(path, fileTree))
- }).catch((error) => {
- dispatch(fetchWorkspaceDirectoryError(error.message))
- })
+ promise
+ .then((fileTree) => {
+ dispatch(fetchWorkspaceDirectorySuccess(path, fileTree))
+ })
+ .catch((error) => {
+ dispatch(fetchWorkspaceDirectoryError(error.message))
+ })
return promise
}
@@ -295,6 +362,7 @@ export const renameWorkspace = async (oldName: string, workspaceName: string, cb
await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName))
await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
+ await plugin.deleteWorkspace(oldName)
await plugin.workspaceRenamed(oldName, workspaceName)
cb && cb(null, workspaceName)
}
@@ -319,7 +387,9 @@ export const deleteWorkspace = async (workspaceName: string, cb?: (err: Error, r
}
export const deleteAllWorkspaces = async () => {
- await (await getWorkspaces()).map(async workspace => {
+ await (
+ await getWorkspaces()
+ ).map(async (workspace) => {
await deleteWorkspaceFromProvider(workspace.name)
await dispatch(setDeleteWorkspace(workspace.name))
plugin.workspaceDeleted(workspace.name)
@@ -356,7 +426,6 @@ export const switchToWorkspace = async (name: string) => {
if (isGitRepo) {
const isActive = await plugin.call('manager', 'isActive', 'dgit')
-
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
}
dispatch(setMode('browser'))
@@ -370,18 +439,18 @@ const loadFile = (name, file, provider, cb?): void => {
fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) {
- return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => { }))
+ return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => {}))
}
try {
await provider.set(name, event.target.result)
} catch (error) {
- return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => { }))
+ return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => {}))
}
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
- if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
+ if (config.get('currentFile') === name && editor.currentContent() !== event.target.result) {
editor.setText(name, event.target.result)
}
}
@@ -394,11 +463,11 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
// the files module. Please ask the user here if they want to overwrite
// a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module.
- [...target.files].forEach(async (file) => {
+ ;[...target.files].forEach(async (file) => {
const workspaceProvider = plugin.fileProviders.workspace
const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}`
- if (!await workspaceProvider.exists(name)) {
+ if (!(await workspaceProvider.exists(name))) {
loadFile(name, file, workspaceProvider, cb)
} else {
const modalContent: AppModal = {
@@ -412,7 +481,7 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
loadFile(name, file, workspaceProvider, cb)
},
cancelFn: () => {},
- hideFn: () => {}
+ hideFn: () => {},
}
plugin.call('notification', 'modal', modalContent)
}
@@ -420,10 +489,10 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
}
export const uploadFolder = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record) => void) => {
- for(const file of [...target.files]) {
+ for (const file of [...target.files]) {
const workspaceProvider = plugin.fileProviders.workspace
const name = targetFolder === '/' ? file.webkitRelativePath : `${targetFolder}/${file.webkitRelativePath}`
- if (!await workspaceProvider.exists(name)) {
+ if (!(await workspaceProvider.exists(name))) {
loadFile(name, file, workspaceProvider, cb)
} else {
const modalContent: AppModal = {
@@ -437,51 +506,53 @@ export const uploadFolder = async (target, targetFolder: string, cb?: (err: Erro
loadFile(name, file, workspaceProvider, cb)
},
cancelFn: () => {},
- hideFn: () => {}
+ hideFn: () => {},
}
plugin.call('notification', 'modal', modalContent)
}
}
}
-export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => {
+export const getWorkspaces = async (): Promise<{ name: string; isGitRepo: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[]> | undefined => {
try {
- const workspaces: { name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[] = await new Promise((resolve, reject) => {
+ const workspaces: { name: string; isGitRepo: boolean; branches?: { remote: any; name: string }[]; currentBranch?: string }[] = await new Promise((resolve, reject) => {
const workspacesPath = plugin.fileProviders.workspace.workspacesPath
plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
if (error) {
return reject(error)
}
- Promise.all(Object.keys(items)
- .filter((item) => items[item].isDirectory)
- .map(async (folder) => {
- const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
-
- if (isGitRepo) {
- let branches = []
- let currentBranch = null
-
- branches = await getGitRepoBranches(folder)
- currentBranch = await getGitRepoCurrentBranch(folder)
- return {
- name: folder.replace(workspacesPath + '/', ''),
- isGitRepo,
- branches,
- currentBranch
- }
- } else {
- return {
- name: folder.replace(workspacesPath + '/', ''),
- isGitRepo
+ Promise.all(
+ Object.keys(items)
+ .filter((item) => items[item].isDirectory)
+ .map(async (folder) => {
+ const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
+
+ if (isGitRepo) {
+ let branches = []
+ let currentBranch = null
+
+ branches = await getGitRepoBranches(folder)
+ currentBranch = await getGitRepoCurrentBranch(folder)
+ return {
+ name: folder.replace(workspacesPath + '/', ''),
+ isGitRepo,
+ branches,
+ currentBranch,
+ }
+ } else {
+ return {
+ name: folder.replace(workspacesPath + '/', ''),
+ isGitRepo,
+ }
}
- }
- })).then(workspacesList => resolve(workspacesList))
+ })
+ ).then((workspacesList) => resolve(workspacesList))
})
})
await plugin.setWorkspaces(workspaces)
return workspaces
- } catch (e) { }
+ } catch (e) {}
}
export const cloneRepository = async (url: string) => {
@@ -496,37 +567,40 @@ export const cloneRepository = async (url: string) => {
const promise = plugin.call('dGitProvider', 'clone', repoConfig, repoName, true)
dispatch(cloneRepositoryRequest())
- promise.then(async () => {
- const isActive = await plugin.call('manager', 'isActive', 'dgit')
+ promise
+ .then(async () => {
+ const isActive = await plugin.call('manager', 'isActive', 'dgit')
- if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
- await fetchWorkspaceDirectory(ROOT_PATH)
- const workspacesPath = plugin.fileProviders.workspace.workspacesPath
- const branches = await getGitRepoBranches(workspacesPath + '/' + repoName)
+ if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ const workspacesPath = plugin.fileProviders.workspace.workspacesPath
+ const branches = await getGitRepoBranches(workspacesPath + '/' + repoName)
- dispatch(setCurrentWorkspaceBranches(branches))
- const currentBranch = await getGitRepoCurrentBranch(workspacesPath + '/' + repoName)
+ dispatch(setCurrentWorkspaceBranches(branches))
+ const currentBranch = await getGitRepoCurrentBranch(workspacesPath + '/' + repoName)
- dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- const cloneModal = {
- id: 'cloneGitRepository',
- title: 'Clone Git Repository',
- message: 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in Settings plugin',
- modalType: 'modal',
- okLabel: 'OK',
- okFn: async () => {
- await deleteWorkspace(repoName)
- dispatch(cloneRepositoryFailed())
- },
- hideFn: async () => {
- await deleteWorkspace(repoName)
- dispatch(cloneRepositoryFailed())
+ dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ const cloneModal = {
+ id: 'cloneGitRepository',
+ title: 'Clone Git Repository',
+ message:
+ 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in Settings plugin',
+ modalType: 'modal',
+ okLabel: 'OK',
+ okFn: async () => {
+ await deleteWorkspace(repoName)
+ dispatch(cloneRepositoryFailed())
+ },
+ hideFn: async () => {
+ await deleteWorkspace(repoName)
+ dispatch(cloneRepositoryFailed())
+ },
}
- }
- plugin.call('notification', 'modal', cloneModal)
- })
+ plugin.call('notification', 'modal', cloneModal)
+ })
} catch (e) {
dispatch(displayPopUp('An error occured: ' + e))
}
@@ -540,7 +614,6 @@ export const checkGit = async () => {
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
}
-
export const getRepositoryTitle = async (url: string) => {
const urlArray = url.split('/')
let name = urlArray.length > 0 ? urlArray[urlArray.length - 1] : ''
@@ -561,25 +634,24 @@ export const getRepositoryTitle = async (url: string) => {
}
export const getGitRepoBranches = async (workspacePath: string) => {
- const gitConfig: { fs: IndexedDBStorage, dir: string } = {
+ const gitConfig: { fs: IndexedDBStorage; dir: string } = {
fs: window.remixFileSystemCallback,
- dir: addSlash(workspacePath)
+ dir: addSlash(workspacePath),
}
- const branches: { remote: any; name: string; }[] = await plugin.call('dGitProvider', 'branches', { ...gitConfig })
+ const branches: { remote: any; name: string }[] = await plugin.call('dGitProvider', 'branches', { ...gitConfig })
return branches
}
export const getGitRepoCurrentBranch = async (workspaceName: string) => {
- const gitConfig: { fs: IndexedDBStorage, dir: string } = {
+ const gitConfig: { fs: IndexedDBStorage; dir: string } = {
fs: window.remixFileSystemCallback,
- dir: addSlash(workspaceName)
+ dir: addSlash(workspaceName),
}
const currentBranch: string = await plugin.call('dGitProvider', 'currentbranch', { ...gitConfig })
return currentBranch
}
export const showAllBranches = async () => {
- console.log('showAllBranches')
const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
plugin.call('menuicons', 'select', 'dgit')
@@ -618,28 +690,34 @@ export const switchBranch = async (branch: string) => {
okLabel: 'Force Checkout',
okFn: async () => {
dispatch(cloneRepositoryRequest())
- plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => {
- await fetchWorkspaceDirectory(ROOT_PATH)
- dispatch(setCurrentWorkspaceCurrentBranch(branch))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- dispatch(cloneRepositoryFailed())
- })
+ plugin
+ .call('dGitProvider', 'checkout', { ref: branch, force: true }, false)
+ .then(async () => {
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ dispatch(setCurrentWorkspaceCurrentBranch(branch))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ dispatch(cloneRepositoryFailed())
+ })
},
cancelLabel: 'Cancel',
- cancelFn: () => { },
- hideFn: () => { }
+ cancelFn: () => {},
+ hideFn: () => {},
}
plugin.call('notification', 'modal', cloneModal)
} else {
dispatch(cloneRepositoryRequest())
- plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => {
- await fetchWorkspaceDirectory(ROOT_PATH)
- dispatch(setCurrentWorkspaceCurrentBranch(branch))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- dispatch(cloneRepositoryFailed())
- })
+ plugin
+ .call('dGitProvider', 'checkout', { ref: branch, force: true }, false)
+ .then(async () => {
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ dispatch(setCurrentWorkspaceCurrentBranch(branch))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ dispatch(cloneRepositoryFailed())
+ })
}
}
@@ -647,48 +725,49 @@ export const createNewBranch = async (branch: string) => {
const promise = plugin.call('dGitProvider', 'branch', { ref: branch, checkout: true }, false)
dispatch(cloneRepositoryRequest())
- promise.then(async () => {
- await fetchWorkspaceDirectory(ROOT_PATH)
- dispatch(setCurrentWorkspaceCurrentBranch(branch))
- const workspacesPath = plugin.fileProviders.workspace.workspacesPath
- const workspaceName = plugin.fileProviders.workspace.workspace
- const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
-
- dispatch(setCurrentWorkspaceBranches(branches))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- dispatch(cloneRepositoryFailed())
- })
+ promise
+ .then(async () => {
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ dispatch(setCurrentWorkspaceCurrentBranch(branch))
+ const workspacesPath = plugin.fileProviders.workspace.workspacesPath
+ const workspaceName = plugin.fileProviders.workspace.workspace
+ const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
+
+ dispatch(setCurrentWorkspaceBranches(branches))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ dispatch(cloneRepositoryFailed())
+ })
return promise
}
export const createSolidityGithubAction = async () => {
const path = '.github/workflows/run-solidity-unittesting.yml'
- await plugin.call('fileManager', 'writeFile', path , solTestYml)
+ await plugin.call('fileManager', 'writeFile', path, solTestYml)
plugin.call('fileManager', 'open', path)
}
export const createTsSolGithubAction = async () => {
const path = '.github/workflows/run-js-test.yml'
- await plugin.call('fileManager', 'writeFile', path , tsSolTestYml)
+ await plugin.call('fileManager', 'writeFile', path, tsSolTestYml)
plugin.call('fileManager', 'open', path)
}
export const createSlitherGithubAction = async () => {
const path = '.github/workflows/run-slither-action.yml'
- await plugin.call('fileManager', 'writeFile', path , slitherYml)
+ await plugin.call('fileManager', 'writeFile', path, slitherYml)
plugin.call('fileManager', 'open', path)
}
const scriptsRef = {
- 'deployer': contractDeployerScripts,
- 'etherscan': etherscanScripts
+ deployer: contractDeployerScripts,
+ etherscan: etherscanScripts,
}
export const createHelperScripts = async (script: string) => {
-
if (!scriptsRef[script]) return
await scriptsRef[script](plugin)
plugin.call('notification', 'toast', 'scripts added in the "scripts" folder')
@@ -708,38 +787,44 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => {
okLabel: 'Force Checkout',
okFn: async () => {
dispatch(cloneRepositoryRequest())
- plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => {
- await fetchWorkspaceDirectory(ROOT_PATH)
- dispatch(setCurrentWorkspaceCurrentBranch(branch))
- const workspacesPath = plugin.fileProviders.workspace.workspacesPath
- const workspaceName = plugin.fileProviders.workspace.workspace
- const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
-
- dispatch(setCurrentWorkspaceBranches(branches))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- dispatch(cloneRepositoryFailed())
- })
+ plugin
+ .call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false)
+ .then(async () => {
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ dispatch(setCurrentWorkspaceCurrentBranch(branch))
+ const workspacesPath = plugin.fileProviders.workspace.workspacesPath
+ const workspaceName = plugin.fileProviders.workspace.workspace
+ const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
+
+ dispatch(setCurrentWorkspaceBranches(branches))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ dispatch(cloneRepositoryFailed())
+ })
},
cancelLabel: 'Cancel',
- cancelFn: () => { },
- hideFn: () => { }
+ cancelFn: () => {},
+ hideFn: () => {},
}
plugin.call('notification', 'modal', cloneModal)
} else {
dispatch(cloneRepositoryRequest())
- plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => {
- await fetchWorkspaceDirectory(ROOT_PATH)
- dispatch(setCurrentWorkspaceCurrentBranch(branch))
- const workspacesPath = plugin.fileProviders.workspace.workspacesPath
- const workspaceName = plugin.fileProviders.workspace.workspace
- const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
-
- dispatch(setCurrentWorkspaceBranches(branches))
- dispatch(cloneRepositorySuccess())
- }).catch(() => {
- dispatch(cloneRepositoryFailed())
- })
+ plugin
+ .call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false)
+ .then(async () => {
+ await fetchWorkspaceDirectory(ROOT_PATH)
+ dispatch(setCurrentWorkspaceCurrentBranch(branch))
+ const workspacesPath = plugin.fileProviders.workspace.workspacesPath
+ const workspaceName = plugin.fileProviders.workspace.workspace
+ const branches = await getGitRepoBranches(workspacesPath + '/' + workspaceName)
+
+ dispatch(setCurrentWorkspaceBranches(branches))
+ dispatch(cloneRepositorySuccess())
+ })
+ .catch(() => {
+ dispatch(cloneRepositoryFailed())
+ })
}
}
diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
index 0b7cda677a..73587e6628 100644
--- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
+++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
@@ -903,10 +903,10 @@ export function Workspace() {
) : null}
-
-
+
+
-
+
diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts
index ad91681659..a10f7fcc4b 100644
--- a/libs/remix-ui/workspace/src/lib/types/index.ts
+++ b/libs/remix-ui/workspace/src/lib/types/index.ts
@@ -7,17 +7,17 @@ import { ViewPlugin } from '@remixproject/engine-web'
export type action = { name: string, type?: Array, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean, group: number }
export interface JSONStandardInput {
- language: "Solidity";
- settings?: any,
+ language: 'Solidity'
+ settings?: any
sources: {
[globalName: string]: {
- keccak256?: string;
- content: string;
- },
- };
+ keccak256?: string
+ content: string
+ }
+ }
}
export type MenuItems = action[]
-export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'semaphore'
+export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' | 'playground' | 'semaphore'
export interface WorkspaceProps {
plugin: FilePanelType
}
@@ -38,42 +38,42 @@ export interface Modal {
}
export interface FileType {
- path: string,
- name: string,
- isDirectory: boolean,
- type: 'folder' | 'file' | 'gist',
+ path: string
+ name: string
+ isDirectory: boolean
+ type: 'folder' | 'file' | 'gist'
child?: File[]
}
export interface FilePanelType extends ViewPlugin {
- setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void,
- createWorkspace: (name: string, workspaceTemplateName: string) => void,
- renameWorkspace: (oldName: string, newName: string) => void
- compileContractForUml: (path: string) => void
- workspaceRenamed: ({ name }) => void,
- workspaceCreated: ({ name }) => void,
- workspaceDeleted: ({ name }) => void,
- workspace?: any // workspace provider,
- browser?: any // browser provider
- localhost?: any // localhost provider
- fileManager? : any
- appManager: RemixAppManager
- registry?: any // registry
- pluginApi?: any
- request: {
- createWorkspace: () => void,
- setWorkspace: (workspaceName: string) => void,
- createNewFile: () => void,
- uploadFile: (target: EventTarget & HTMLInputElement) => void,
- getCurrentWorkspace: () => void
- } // api request,
- workspaces: any,
- registeredMenuItems: MenuItems // menu items
- removedMenuItems: MenuItems
- initialWorkspace: string,
- resetNewFile: () => void,
- getWorkspaces: () => string[]
- }
+ setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void
+ createWorkspace: (name: string, workspaceTemplateName: string) => void
+ renameWorkspace: (oldName: string, newName: string) => void
+ compileContractForUml: (path: string) => void
+ workspaceRenamed: ({ name }) => void
+ workspaceCreated: ({ name }) => void
+ workspaceDeleted: ({ name }) => void
+ workspace?: any // workspace provider,
+ browser?: any // browser provider
+ localhost?: any // localhost provider
+ fileManager?: any
+ appManager: RemixAppManager
+ registry?: any // registry
+ pluginApi?: any
+ request: {
+ createWorkspace: () => void
+ setWorkspace: (workspaceName: string) => void
+ createNewFile: () => void
+ uploadFile: (target: EventTarget & HTMLInputElement) => void
+ getCurrentWorkspace: () => void
+ } // api request,
+ workspaces: any
+ registeredMenuItems: MenuItems // menu items
+ removedMenuItems: MenuItems
+ initialWorkspace: string
+ resetNewFile: () => void
+ getWorkspaces: () => string[]
+}
/* eslint-disable-next-line */
export interface FileExplorerProps {
@@ -128,41 +128,41 @@ export interface FileExplorerProps {
}
type Placement = import('react-overlays/usePopper').Placement
export interface FileExplorerMenuProps {
- title: string,
- menuItems: string[],
- createNewFile: (folder?: string) => void,
- createNewFolder: (parentFolder?: string) => void,
- publishToGist: (path?: string) => void,
- uploadFile: (target: EventTarget & HTMLInputElement) => void
- uploadFolder: (target: EventTarget & HTMLInputElement) => void
- tooltipPlacement?: Placement
+ title: string
+ menuItems: string[]
+ createNewFile: (folder?: string) => void
+ createNewFolder: (parentFolder?: string) => void
+ publishToGist: (path?: string) => void
+ uploadFile: (target: EventTarget & HTMLInputElement) => void
+ uploadFolder: (target: EventTarget & HTMLInputElement) => void
+ tooltipPlacement?: Placement
}
export interface FileExplorerContextMenuProps {
- actions: action[],
- createNewFile: (folder?: string) => void,
- createNewFolder: (parentFolder?: string) => void,
- deletePath: (path: string | string[]) => void,
- renamePath: (path: string, type: string) => void,
- downloadPath: (path: string) => void,
- hideContextMenu: () => void,
- publishToGist?: (path?: string, type?: string) => void,
- pushChangesToGist?: (path?: string, type?: string) => void,
- publishFolderToGist?: (path?: string, type?: string) => void,
- publishFileToGist?: (path?: string, type?: string) => void,
- runScript?: (path: string) => void,
- emit?: (cmd: customAction) => void,
- pageX: number,
- pageY: number,
- path: string,
- type: string,
- focus: {key:string, type:string}[],
- onMouseOver?: (...args) => void,
- copy?: (path: string, type: string) => void,
- paste?: (destination: string, type: string) => void
- copyFileName?: (path: string, type: string) => void
- copyPath?: (path: string, type: string) => void
- generateUml?: (path: string) => Promise
- uploadFile?: (target: EventTarget & HTMLInputElement) => void
+ actions: action[]
+ createNewFile: (folder?: string) => void
+ createNewFolder: (parentFolder?: string) => void
+ deletePath: (path: string | string[]) => void
+ renamePath: (path: string, type: string) => void
+ downloadPath: (path: string) => void
+ hideContextMenu: () => void
+ publishToGist?: (path?: string, type?: string) => void
+ pushChangesToGist?: (path?: string, type?: string) => void
+ publishFolderToGist?: (path?: string, type?: string) => void
+ publishFileToGist?: (path?: string, type?: string) => void
+ runScript?: (path: string) => void
+ emit?: (cmd: customAction) => void
+ pageX: number
+ pageY: number
+ path: string
+ type: string
+ focus: { key: string; type: string }[]
+ onMouseOver?: (...args) => void
+ copy?: (path: string, type: string) => void
+ paste?: (destination: string, type: string) => void
+ copyFileName?: (path: string, type: string) => void
+ copyPath?: (path: string, type: string) => void
+ generateUml?: (path: string) => Promise
+ uploadFile?: (target: EventTarget & HTMLInputElement) => void
}
export interface WorkSpaceState {
@@ -190,9 +190,9 @@ export interface WorkSpaceState {
showContextMenu: boolean
reservedKeywords: string[]
copyElement: CopyElementType[]
- }
+}
-export type FileFocusContextType = {
+export type FileFocusContextType = {
element: string
x: number
y: number
diff --git a/libs/remix-ui/workspace/src/lib/utils/constants.ts b/libs/remix-ui/workspace/src/lib/utils/constants.ts
index 0dc54144bc..60f0d03ea0 100644
--- a/libs/remix-ui/workspace/src/lib/utils/constants.ts
+++ b/libs/remix-ui/workspace/src/lib/utils/constants.ts
@@ -81,5 +81,6 @@ export const TEMPLATE_NAMES = {
'ozerc1155': 'OpenZeppelin ERC1155',
'zeroxErc20': '0xProject ERC20',
'gnosisSafeMultisig': 'Gnosis Safe',
+ 'playground': 'Playground',
'semaphore': 'Semaphore'
}
diff --git a/libs/remix-ws-templates/src/index.ts b/libs/remix-ws-templates/src/index.ts
index b99e70ea62..48c239019b 100644
--- a/libs/remix-ws-templates/src/index.ts
+++ b/libs/remix-ws-templates/src/index.ts
@@ -5,6 +5,7 @@ export { default as ozerc721 } from './templates/ozerc721'
export { default as ozerc1155 } from './templates/ozerc1155'
export { default as zeroxErc20 } from './templates/zeroxErc20'
export { default as gnosisSafeMultisig } from './templates/gnosisSafeMultisig'
+export { default as playground } from './templates/playground'
export { default as semaphore } from './templates/semaphore'
export { contractDeployerScripts } from './script-templates/contract-deployer'
diff --git a/libs/remix-ws-templates/src/templates/playground/.prettierrc b/libs/remix-ws-templates/src/templates/playground/.prettierrc
new file mode 100644
index 0000000000..b2a56f2371
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/.prettierrc
@@ -0,0 +1,38 @@
+{
+ "overrides": [
+ {
+ "files": "*.sol",
+ "options": {
+ "printWidth": 80,
+ "tabWidth": 4,
+ "useTabs": false,
+ "singleQuote": false,
+ "bracketSpacing": false
+ }
+ },
+ {
+ "files": "*.yml",
+ "options": {}
+ },
+ {
+ "files": "*.yaml",
+ "options": {}
+ },
+ {
+ "files": "*.toml",
+ "options": {}
+ },
+ {
+ "files": "*.json",
+ "options": {}
+ },
+ {
+ "files": "*.js",
+ "options": {}
+ },
+ {
+ "files": "*.ts",
+ "options": {}
+ }
+ ]
+}
diff --git a/libs/remix-ws-templates/src/templates/playground/README.txt b/libs/remix-ws-templates/src/templates/playground/README.txt
new file mode 100644
index 0000000000..e27de83769
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/README.txt
@@ -0,0 +1,28 @@
+REMIX DEFAULT WORKSPACE
+
+Remix default workspace is present when:
+i. Remix loads for the very first time
+ii. A new workspace is created with 'Default' template
+iii. There are no files existing in the File Explorer
+
+This workspace contains 3 directories:
+
+1. 'contracts': Holds three contracts with increasing levels of complexity.
+2. 'scripts': Contains four typescript files to deploy a contract. It is explained below.
+3. 'tests': Contains one Solidity test file for 'Ballot' contract & one JS test file for 'Storage' contract.
+
+SCRIPTS
+
+The 'scripts' folder has four typescript files which help to deploy the 'Storage' contract using 'web3.js' and 'ethers.js' libraries.
+
+For the deployment of any other contract, just update the contract's name from 'Storage' to the desired contract and provide constructor arguments accordingly
+in the file `deploy_with_ethers.ts` or `deploy_with_web3.ts`
+
+In the 'tests' folder there is a script containing Mocha-Chai unit tests for 'Storage' contract.
+
+To run a script, right click on file name in the file explorer and click 'Run'. Remember, Solidity file must already be compiled.
+Output from script will appear in remix terminal.
+
+Please note, require/import is supported in a limited manner for Remix supported modules.
+For now, modules supported by Remix are ethers, web3, swarmgw, chai, multihashes, remix and hardhat only for hardhat.ethers object/plugin.
+For unsupported modules, an error like this will be thrown: ' module require is not supported by Remix IDE' will be shown.
diff --git a/libs/remix-ws-templates/src/templates/playground/contracts/helloWorld.sol b/libs/remix-ws-templates/src/templates/playground/contracts/helloWorld.sol
new file mode 100644
index 0000000000..47fabd793a
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/contracts/helloWorld.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.6.12 <0.9.0;
+
+contract HelloWorld {
+ function print() public pure returns (string memory) {
+ return "Hello World!";
+ }
+}
\ No newline at end of file
diff --git a/libs/remix-ws-templates/src/templates/playground/index.ts b/libs/remix-ws-templates/src/templates/playground/index.ts
new file mode 100644
index 0000000000..52e33a5a43
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/index.ts
@@ -0,0 +1,16 @@
+export default async () => {
+ return {
+ // @ts-ignore
+ 'contracts/helloWorld.sol': (await import('raw-loader!./contracts/helloWorld.sol')).default,
+ // @ts-ignore
+ 'scripts/deploy_with_ethers.ts': (await import('!!raw-loader!./scripts/deploy_with_ethers.ts')).default,
+ // @ts-ignore
+ 'scripts/deploy_with_web3.ts': (await import('!!raw-loader!./scripts/deploy_with_web3.ts')).default,
+ // @ts-ignore
+ 'scripts/ethers-lib.ts': (await import('!!raw-loader!./scripts/ethers-lib.ts')).default,
+ // @ts-ignore
+ 'scripts/web3-lib.ts': (await import('!!raw-loader!./scripts/web3-lib.ts')).default,
+ // @ts-ignore
+ '.prettierrc.json': (await import('raw-loader!./.prettierrc')).default,
+ }
+}
\ No newline at end of file
diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts
new file mode 100644
index 0000000000..d190d654f0
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts
@@ -0,0 +1,14 @@
+// This script can be used to deploy the "Storage" contract using ethers.js library.
+// Please make sure to compile "./contracts/1_Storage.sol" file before running this script.
+// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S
+
+import { deploy } from './ethers-lib'
+
+(async () => {
+ try {
+ const result = await deploy('HelloWorld', [])
+ console.log(`address: ${result.address}`)
+ } catch (e) {
+ console.log(e.message)
+ }
+})()
\ No newline at end of file
diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_web3.ts b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_web3.ts
new file mode 100644
index 0000000000..1378a92657
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_web3.ts
@@ -0,0 +1,14 @@
+// This script can be used to deploy the "Storage" contract using Web3 library.
+// Please make sure to compile "./contracts/1_Storage.sol" file before running this script.
+// And use Right click -> "Run" from context menu of the file to run the script. Shortcut: Ctrl+Shift+S
+
+import { deploy } from './web3-lib'
+
+(async () => {
+ try {
+ const result = await deploy('HelloWorld', [])
+ console.log(`address: ${result.address}`)
+ } catch (e) {
+ console.log(e.message)
+ }
+})()
\ No newline at end of file
diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts
new file mode 100644
index 0000000000..2753f9bcd0
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts
@@ -0,0 +1,29 @@
+import { ethers } from 'ethers'
+
+/**
+ * Deploy the given contract
+ * @param {string} contractName name of the contract to deploy
+ * @param {Array} args list of constructor' parameters
+ * @param {Number} accountIndex account index from the exposed account
+ * @return {Contract} deployed contract
+ */
+export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {
+
+ console.log(`deploying ${contractName}`)
+ // Note that the script needs the ABI which is generated from the compilation artifact.
+ // Make sure contract is compiled and artifacts are generated
+ const artifactsPath = `browser/contracts/artifacts/${contractName}.json` // Change this for different path
+
+ const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
+ // 'web3Provider' is a remix global variable object
+
+ const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
+
+ const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer)
+
+ const contract = await factory.deploy(...args)
+
+ // The contract is NOT deployed yet; we must wait until it is mined
+ await contract.deployed()
+ return contract
+}
\ No newline at end of file
diff --git a/libs/remix-ws-templates/src/templates/playground/scripts/web3-lib.ts b/libs/remix-ws-templates/src/templates/playground/scripts/web3-lib.ts
new file mode 100644
index 0000000000..c57e501039
--- /dev/null
+++ b/libs/remix-ws-templates/src/templates/playground/scripts/web3-lib.ts
@@ -0,0 +1,36 @@
+import Web3 from 'web3'
+import { Contract, ContractSendMethod, Options } from 'web3-eth-contract'
+
+/**
+ * Deploy the given contract
+ * @param {string} contractName name of the contract to deploy
+ * @param {Array} args list of constructor' parameters
+ * @param {string} from account used to send the transaction
+ * @param {number} gas gas limit
+ * @return {Options} deployed contract
+ */
+export const deploy = async (contractName: string, args: Array, from?: string, gas?: number): Promise => {
+
+ const web3 = new Web3(web3Provider)
+ console.log(`deploying ${contractName}`)
+ // Note that the script needs the ABI which is generated from the compilation artifact.
+ // Make sure contract is compiled and artifacts are generated
+ const artifactsPath = `browser/contracts/artifacts/${contractName}.json`
+
+ const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath))
+
+ const accounts = await web3.eth.getAccounts()
+
+ const contract: Contract = new web3.eth.Contract(metadata.abi)
+
+ const contractSend: ContractSendMethod = contract.deploy({
+ data: metadata.data.bytecode.object,
+ arguments: args
+ })
+
+ const newContractInstance = await contractSend.send({
+ from: from || accounts[0],
+ gas: gas || 1500000
+ })
+ return newContractInstance.options
+}
\ No newline at end of file