Merge pull request #3654 from ethereum/startCoding

recent workspaces & Start Coding
pull/4173/head^2
yann300 1 year ago committed by GitHub
commit c57e2f6310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      apps/remix-ide-e2e/src/tests/homeTab.test.ts
  2. 4
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  3. 33
      apps/remix-ide/src/app/files/fileManager.ts
  4. 93
      apps/remix-ide/src/app/panels/file-panel.js
  5. 3
      apps/remix-ide/src/app/tabs/locales/en/home.json
  6. 1
      apps/remix-ide/src/app/tabs/locales/fr/home.json
  7. 6
      apps/remix-ide/src/remixAppManager.js
  8. 170
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  9. 22
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  10. 6
      libs/remix-ui/plugin-manager/src/lib/components/LocalPluginForm.tsx
  11. 4
      libs/remix-ui/workspace/src/lib/actions/events.ts
  12. 235
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  13. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  14. 102
      libs/remix-ui/workspace/src/lib/types/index.ts
  15. 1
      libs/remix-ui/workspace/src/lib/utils/constants.ts
  16. 1
      libs/remix-ws-templates/src/index.ts
  17. 38
      libs/remix-ws-templates/src/templates/playground/.prettierrc
  18. 28
      libs/remix-ws-templates/src/templates/playground/README.txt
  19. 8
      libs/remix-ws-templates/src/templates/playground/contracts/helloWorld.sol
  20. 16
      libs/remix-ws-templates/src/templates/playground/index.ts
  21. 14
      libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_ethers.ts
  22. 14
      libs/remix-ws-templates/src/templates/playground/scripts/deploy_with_web3.ts
  23. 29
      libs/remix-ws-templates/src/templates/playground/scripts/ethers-lib.ts
  24. 36
      libs/remix-ws-templates/src/templates/playground/scripts/web3-lib.ts

@ -8,13 +8,10 @@ module.exports = {
init(browser, done) init(browser, done)
}, },
'Should create new file': function (browser: NightwatchBrowser) { 'Should start coding': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="homeTabNewFile"]') .waitForElementVisible('*[data-id="homeTabStartCoding"]')
.click('*[data-id="homeTabNewFile"]') .click('*[data-id="homeTabStartCoding"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000) .waitForElementVisible('div[data-id="treeViewDivtreeViewItemcontracts/helloWorld.sol"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'newTestFile')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemnewTestFile.sol"]')
} }
} }

@ -235,7 +235,7 @@ const sources = [
content: content:
` `
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
contract helloWorld { contract HelloWorld {
string public message; string public message;
fallback () external { fallback () external {
@ -249,7 +249,7 @@ const sources = [
}, },
'checkBalance.sol': { 'checkBalance.sol': {
content: `pragma solidity ^0.8.0; content: `pragma solidity ^0.8.0;
contract checkBalance { contract CheckBalance {
constructor () payable {} constructor () payable {}
function sendSomeEther(uint256 num) public { function sendSomeEther(uint256 num) public {

@ -22,9 +22,11 @@ const profile = {
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
permission: true, permission: true,
version: packageJson.version, version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory'], 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory'
],
kind: 'file-system' kind: 'file-system'
} }
const errorMsg = { const errorMsg = {
@ -37,7 +39,6 @@ const errorMsg = {
const createError = (err) => { const createError = (err) => {
return new Error(`${errorMsg[err.code]} ${err.message || ''}`) return new Error(`${errorMsg[err.code]} ${err.message || ''}`)
} }
class FileManager extends Plugin { class FileManager extends Plugin {
mode: string mode: string
openedFiles: any openedFiles: any
@ -250,6 +251,30 @@ class FileManager extends Plugin {
} }
} }
/**
* 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 * Return the content of a specific file
* @param {string} path path of the file * @param {string} path path of the file

@ -30,7 +30,19 @@ const { SlitherHandle } = require('../files/slither-handle.js')
const profile = { const profile = {
name: 'filePanel', name: 'filePanel',
displayName: 'File explorer', 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'], events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
description: 'Remix IDE file explorer', description: 'Remix IDE file explorer',
@ -41,7 +53,7 @@ const profile = {
maintainedBy: 'Remix' maintainedBy: 'Remix'
} }
module.exports = class Filepanel extends ViewPlugin { module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) { constructor(appManager) {
super(profile) super(profile)
this.registry = Registry.getInstance() this.registry = Registry.getInstance()
this.fileProviders = this.registry.get('fileproviders').api this.fileProviders = this.registry.get('fileproviders').api
@ -60,8 +72,12 @@ module.exports = class Filepanel extends ViewPlugin {
this.currentWorkspaceMetadata = null this.currentWorkspaceMetadata = null
} }
render () { render() {
return <div id='fileExplorerView'><FileSystemProvider plugin={this} /></div> return (
<div id="fileExplorerView">
<FileSystemProvider plugin={this} />
</div>
)
} }
/** /**
@ -77,7 +93,7 @@ module.exports = class Filepanel extends ViewPlugin {
* group 7 for generating resource files (UML, documentation, ...) * group 7 for generating resource files (UML, documentation, ...)
* @param callback (...args) => void * @param callback (...args) => void
*/ */
registerContextMenuItem (item) { registerContextMenuItem(item) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.emit('registerContextMenuItemReducerEvent', item, (err, data) => { this.emit('registerContextMenuItemReducerEvent', item, (err, data) => {
if (err) reject(err) if (err) reject(err)
@ -86,7 +102,7 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
removePluginActions (plugin) { removePluginActions(plugin) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.emit('removePluginActionsReducerEvent', plugin, (err, data) => { this.emit('removePluginActionsReducerEvent', plugin, (err, data) => {
if (err) reject(err) if (err) reject(err)
@ -95,30 +111,30 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
getCurrentWorkspace () { getCurrentWorkspace() {
return this.currentWorkspaceMetadata return this.currentWorkspaceMetadata
} }
getWorkspaces () { getWorkspaces() {
return this.workspaces return this.workspaces
} }
getAvailableWorkspaceName (name) { getAvailableWorkspaceName(name) {
if(!this.workspaces) return name if (!this.workspaces) return name
let index = 1 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) { while (workspace) {
index++ index++
workspace = this.workspaces.find(workspace => workspace.name === name + ' - ' + index) workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
} }
return name + ' - ' + index return name + ' - ' + index
} }
setWorkspaces (workspaces) { setWorkspaces(workspaces) {
this.workspaces = workspaces this.workspaces = workspaces
} }
createNewFile () { createNewFile() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.emit('createNewFileInputReducerEvent', '/', (err, data) => { this.emit('createNewFileInputReducerEvent', '/', (err, data) => {
if (err) reject(err) if (err) reject(err)
@ -127,7 +143,7 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
uploadFile (target) { uploadFile(target) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.emit('uploadFileReducerEvent', '/', target, (err, data) => { return this.emit('uploadFileReducerEvent', '/', target, (err, data) => {
if (err) reject(err) 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) => { return new Promise((resolve, reject) => {
this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => { this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => {
if (err) reject(err) 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) => { return new Promise((resolve, reject) => {
this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => { this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => {
if (err) reject(err) if (err) reject(err)
@ -154,7 +170,7 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
deleteWorkspace (workspaceName) { deleteWorkspace(workspaceName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => { this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => {
if (err) reject(err) if (err) reject(err)
@ -163,25 +179,52 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
setWorkspace (workspace) { saveRecent(workspaceName) {
const workspaceProvider = this.fileProviders.workspace 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}` } setWorkspace(workspace) {
if (workspace.name !== " - connect to localhost - ") { 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) localStorage.setItem('currentWorkspace', workspace.name)
} }
this.emit('setWorkspace', workspace) this.emit('setWorkspace', workspace)
} }
workspaceRenamed (oldName, workspaceName) { switchToWorkspace(workspaceName) {
this.emit('switchToWorkspace', workspaceName)
}
workspaceRenamed(oldName, workspaceName) {
this.emit('workspaceRenamed', oldName, workspaceName) this.emit('workspaceRenamed', oldName, workspaceName)
} }
workspaceDeleted (workspace) { workspaceDeleted(workspace) {
this.emit('workspaceDeleted', workspace) this.emit('workspaceDeleted', workspace)
} }
workspaceCreated (workspace) { workspaceCreated(workspace) {
this.emit('workspaceCreated', workspace) this.emit('workspaceCreated', workspace)
} }
/** end section */ /** end section */

@ -56,7 +56,10 @@
"home.searchDocumentation": "Search Documentation", "home.searchDocumentation": "Search Documentation",
"home.files": "Files", "home.files": "Files",
"home.newFile": "New File", "home.newFile": "New File",
"home.startCoding": "Start Coding",
"home.startCodingPlayground": "Open a playground for prototyping and simply learning",
"home.openFile": "Open File", "home.openFile": "Open File",
"home.openFileTooltip": "Open a File from your File System",
"home.accessFileSystem": "Access File System", "home.accessFileSystem": "Access File System",
"home.loadFrom": "Load from", "home.loadFrom": "Load from",
"home.resources": "Resources", "home.resources": "Resources",

@ -55,6 +55,7 @@
"home.files": "Files", "home.files": "Files",
"home.newFile": "New File", "home.newFile": "New File",
"home.openFile": "Open File", "home.openFile": "Open File",
"home.openFileTooltip": "Open a File from you File System",
"home.connectToLocalhost": "Access File System", "home.connectToLocalhost": "Access File System",
"home.loadFrom": "Load from", "home.loadFrom": "Load from",
"home.resources": "Resources" "home.resources": "Resources"

@ -5,8 +5,7 @@ import {IframePlugin} from '@remixproject/engine-web'
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
// requiredModule removes the plugin from the plugin manager list on UI // requiredModule removes the plugin from the plugin manager list on UI
const requiredModules = [ const requiredModules = [ // services + layout views + system views
// services + layout views + system views
'manager', 'manager',
'config', 'config',
'compilerArtefacts', 'compilerArtefacts',
@ -74,7 +73,8 @@ const requiredModules = [
'compilationDetails', 'compilationDetails',
'contractflattener', 'contractflattener',
'solidity-script', 'solidity-script',
'openaigpt' 'openaigpt',
'home'
] ]
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)

@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import React, {useState, useRef, useReducer} from 'react' import React, { useState, useRef, useReducer, useEffect } from 'react'
import {FormattedMessage} from 'react-intl' import { FormattedMessage } from 'react-intl'
import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
const _paq = (window._paq = window._paq || []) // 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 { interface HomeTabFileProps {
plugin: any plugin: any
@ -25,7 +26,7 @@ const loadingReducer = (state = loadingInitialState, action) => {
} }
} }
function HomeTabFile({plugin}: HomeTabFileProps) { function HomeTabFile({ plugin }: HomeTabFileProps) {
const [state, setState] = useState<{ const [state, setState] = useState<{
searchInput: string searchInput: string
showModalDialog: boolean showModalDialog: boolean
@ -37,18 +38,55 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
} }
importSource: string importSource: string
toasterMsg: string toasterMsg: string
recentWorkspaces: Array<string>
}>({ }>({
searchInput: '', searchInput: '',
showModalDialog: false, showModalDialog: false,
modalInfo: {title: '', loadItem: '', examples: [], prefix: ''}, modalInfo: { title: '', loadItem: '', examples: [], prefix: '' },
importSource: '', importSource: '',
toasterMsg: '' toasterMsg: '',
recentWorkspaces: []
}) })
const [, dispatch] = useReducer(loadingReducer, loadingInitialState) const [, dispatch] = useReducer(loadingReducer, loadingInitialState)
const inputValue = useRef(null) 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) => { const processLoading = (type: string) => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'importFrom' + type]) _paq.push(['trackEvent', 'hometab', 'filesSection', 'importFrom' + type])
const contentImport = plugin.contentImport const contentImport = plugin.contentImport
@ -57,12 +95,12 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
if ((type === 'ipfs' || type === 'IPFS') && startsWith !== 'ipfs' && startsWith !== 'IPFS') { if ((type === 'ipfs' || type === 'IPFS') && startsWith !== 'ipfs' && startsWith !== 'IPFS') {
setState((prevState) => { setState((prevState) => {
return {...prevState, importSource: startsWith + state.importSource} return { ...prevState, importSource: startsWith + state.importSource }
}) })
} }
contentImport.import( contentImport.import(
state.modalInfo.prefix + state.importSource, state.modalInfo.prefix + state.importSource,
(loadingMsg) => dispatch({tooltip: loadingMsg}), (loadingMsg) => dispatch({ tooltip: loadingMsg }),
async (error, content, cleanUrl, type, url) => { async (error, content, cleanUrl, type, url) => {
if (error) { if (error) {
toast(error.message || error) toast(error.message || error)
@ -80,20 +118,45 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
} }
) )
setState((prevState) => { setState((prevState) => {
return {...prevState, showModalDialog: false, importSource: ''} return { ...prevState, showModalDialog: false, importSource: '' }
}) })
} }
const toast = (message: string) => { const toast = (message: string) => {
setState((prevState) => { setState((prevState) => {
return {...prevState, toasterMsg: message} return { ...prevState, toasterMsg: message }
}) })
} }
const createNewFile = async () => { const startCoding = async () => {
_paq.push(['trackEvent', 'hometab', 'filesSection', 'createNewFile']) _paq.push(['trackEvent', 'hometab', 'filesSection', 'startCoding'])
plugin.verticalIcons.select('filePanel') 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) => { const uploadFile = async (target) => {
@ -120,8 +183,8 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
title: title, title: title,
loadItem: loadItem, loadItem: loadItem,
examples: examples, examples: examples,
prefix prefix,
} },
} }
}) })
} }
@ -129,10 +192,15 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
const hideFullMessage = () => { const hideFullMessage = () => {
//eslint-disable-line //eslint-disable-line
setState((prevState) => { 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) => ( const examples = state.modalInfo.examples.map((urlEl, key) => (
<div key={key} className="p-1 user-select-auto"> <div key={key} className="p-1 user-select-auto">
<a>{urlEl}</a> <a>{urlEl}</a>
@ -161,15 +229,15 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
{state.modalInfo.prefix && <span className="text-nowrap align-self-center mr-2">ipfs://</span>} {state.modalInfo.prefix && <span className="text-nowrap align-self-center mr-2">ipfs://</span>}
<input <input
ref={inputValue} ref={inputValue}
type="text" type='text'
name="prompt_text" name='prompt_text'
id="inputPrompt_text" id='inputPrompt_text'
className="w-100 mt-1 form-control" className="w-100 mt-1 form-control"
data-id="homeTabModalDialogCustomPromptText" data-id="homeTabModalDialogCustomPromptText"
value={state.importSource} value={state.importSource}
onInput={(e) => { onInput={(e) => {
setState((prevState) => { setState((prevState) => {
return {...prevState, importSource: inputValue.current.value} return { ...prevState, importSource: inputValue.current.value }
}) })
}} }}
/> />
@ -178,14 +246,31 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
</ModalDialog> </ModalDialog>
<Toaster message={state.toasterMsg} /> <Toaster message={state.toasterMsg} />
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection"> <div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection">
<label style={{fontSize: '1.2rem'}}> <label style={{ fontSize: '1.2rem' }}>
<FormattedMessage id="home.files" /> <FormattedMessage id="home.files" />
</label> </label>
<div className="dflex"> <div className="d-flex flex-column">
<button className="btn btn-primary p-2 mr-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}> <div className="d-flex flex-row">
<FormattedMessage id="home.newFile" /> <CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='home.startCodingPlayground' />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button className="btn btn-primary text-nowrap p-2 mr-2 border my-1" data-id="homeTabStartCoding" style={{ width: 'fit-content' }} onClick={() => startCoding()}>
<FormattedMessage id="home.startCoding" />
</button> </button>
<label className="btn p-2 mr-2 border my-1" style={{width: 'fit-content', cursor: 'pointer'}} htmlFor="openFileInput"> </CustomTooltip>
<CustomTooltip
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='home.openFileTooltip' />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<span>
<label className="btn text-nowrap p-2 mr-2 border my-1" style={{ width: 'fit-content', cursor: 'pointer' }} htmlFor="openFileInput">
<FormattedMessage id="home.openFile" /> <FormattedMessage id="home.openFile" />
</label> </label>
<input <input
@ -199,6 +284,8 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
}} }}
multiple multiple
/> />
</span>
</CustomTooltip>
<CustomTooltip <CustomTooltip
placement={'top'} placement={'top'}
tooltipId="overlay-tooltip" tooltipId="overlay-tooltip"
@ -206,12 +293,35 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
tooltipText={<FormattedMessage id="home.connectToLocalhost" />} tooltipText={<FormattedMessage id="home.connectToLocalhost" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3" tooltipTextClasses="border bg-light text-dark p-1 pr-3"
> >
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}> <button className="btn text-nowrap p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}>
<FormattedMessage id="home.accessFileSystem" /> <FormattedMessage id="home.accessFileSystem" />
</button> </button>
</CustomTooltip> </CustomTooltip>
</div> </div>
<label style={{fontSize: '0.8rem'}} className="pt-2"> {(state.recentWorkspaces[0] || state.recentWorkspaces[1] || state.recentWorkspaces[2]) && (
<div className="d-flex flex-column">
<label style={{ fontSize: '0.8rem' }} className="mt-3">
Recent workspaces
</label>
{state.recentWorkspaces[0] && state.recentWorkspaces[0] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[0])}>
{state.recentWorkspaces[0]}
</a>
)}
{state.recentWorkspaces[1] && state.recentWorkspaces[1] !== '' && (
<a className="cursor-pointer mb-1 ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[1])}>
{state.recentWorkspaces[1]}
</a>
)}
{state.recentWorkspaces[2] && state.recentWorkspaces[2] !== '' && (
<a className="cursor-pointer ml-2" href="#" onClick={(e) => handleSwichToRecentWorkspace(e, state.recentWorkspaces[2])}>
{state.recentWorkspaces[2]}
</a>
)}
</div>
)}
</div>
<label style={{ fontSize: '0.8rem' }} className="pt-3">
<FormattedMessage id="home.loadFrom" /> <FormattedMessage id="home.loadFrom" />
</label> </label>
<div className="d-flex"> <div className="d-flex">
@ -221,7 +331,7 @@ function HomeTabFile({plugin}: HomeTabFileProps) {
onClick={() => onClick={() =>
showFullMessage('GitHub', 'github URL', [ showFullMessage('GitHub', 'github URL', [
'https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol',
'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol' 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol',
]) ])
} }
> >

@ -107,18 +107,18 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="Gnosis Safe MultiSig" workspaceTitle="Gnosis Safe MultiSig"
description={intl.formatMessage({ description={
id: 'home.gnosisSafeMultisigTemplateDesc' intl.formatMessage({ id: 'home.gnosisSafeMultisigTemplateDesc' })
})} }
callback={() => createWorkspace('gnosisSafeMultisig')} callback={() => createWorkspace("gnosisSafeMultisig")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
workspaceTitle="0xProject ERC20" workspaceTitle="0xProject ERC20"
description={intl.formatMessage({ description={
id: 'home.zeroxErc20TemplateDesc' intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' })
})} }
callback={() => createWorkspace('zeroxErc20')} callback={() => createWorkspace("zeroxErc20")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sourcifyLogo" gsID="sourcifyLogo"
@ -132,7 +132,7 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.ozerc721TemplateDesc' id: 'home.ozerc721TemplateDesc'
})} })}
callback={() => createWorkspace('ozerc721')} callback={() => createWorkspace("ozerc721")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="sUTLogo" gsID="sUTLogo"
@ -140,7 +140,7 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.ozerc1155TemplateDesc' id: 'home.ozerc1155TemplateDesc'
})} })}
callback={() => createWorkspace('ozerc1155')} callback={() => createWorkspace("ozerc1155")}
/> />
<WorkspaceTemplate <WorkspaceTemplate
gsID="solhintLogo" gsID="solhintLogo"
@ -148,7 +148,7 @@ function HomeTabGetStarted({plugin}: HomeTabGetStartedProps) {
description={intl.formatMessage({ description={intl.formatMessage({
id: 'home.remixDefaultTemplateDesc' id: 'home.remixDefaultTemplateDesc'
})} })}
callback={() => createWorkspace('remixDefault')} callback={() => createWorkspace("remixDefault")}
/> />
</Carousel> </Carousel>
</ThemeContext.Provider> </ThemeContext.Provider>

@ -265,7 +265,7 @@ function LocalPluginForm({closeModal, visible, pluginManager}: LocalPluginFormPr
checked={location === 'sidePanel'} checked={location === 'sidePanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')}
/> />
<label className="form-check-label" htmlFor="sidePanel"> <label className="form-check-label" htmlFor="localPluginRadioButtonsidePanelSidePanel">
<FormattedMessage id="pluginManager.localForm.sidePanel" /> <FormattedMessage id="pluginManager.localForm.sidePanel" />
</label> </label>
</div> </div>
@ -280,7 +280,7 @@ function LocalPluginForm({closeModal, visible, pluginManager}: LocalPluginFormPr
checked={location === 'mainPanel'} checked={location === 'mainPanel'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')}
/> />
<label className="form-check-label" htmlFor="mainPanel"> <label className="form-check-label" htmlFor="localPluginRadioButtonsidePanelMainPanel">
<FormattedMessage id="pluginManager.localForm.mainPanel" /> <FormattedMessage id="pluginManager.localForm.mainPanel" />
</label> </label>
</div> </div>
@ -295,7 +295,7 @@ function LocalPluginForm({closeModal, visible, pluginManager}: LocalPluginFormPr
checked={location === 'none'} checked={location === 'none'}
onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')} onChange={(e) => setLocation(e.target.value as 'sidePanel' | 'mainPanel' | 'none')}
/> />
<label className="form-check-label" htmlFor="none"> <label className="form-check-label" htmlFor="localPluginRadioButtonsidePanelNone">
<FormattedMessage id="pluginManager.localForm.none" /> <FormattedMessage id="pluginManager.localForm.none" />
</label> </label>
</div> </div>

@ -40,6 +40,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
uploadFile(target, dir, cb) uploadFile(target, dir, cb)
}) })
plugin.on('filePanel', 'switchToWorkspace', async (workspace) => {
await switchToWorkspace(workspace.name)
})
plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => { plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => {
setFileDecorators(items) setFileDecorators(items)
}) })

@ -2,7 +2,30 @@ import React from 'react'
import { bufferToHex } from '@ethereumjs/util' import { bufferToHex } from '@ethereumjs/util'
import { hash } from '@remix-project/remix-lib' import { hash } from '@remix-project/remix-lib'
import axios, { AxiosResponse } from 'axios' 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 { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper'
import { FileTree, JSONStandardInput, WorkspaceTemplate } from '../types' 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' import { contractDeployerScripts, etherscanScripts } from '@remix-project/remix-ws-templates'
declare global { declare global {
interface Window { remixFileSystemCallback: IndexedDBStorage; } interface Window {
remixFileSystemCallback: IndexedDBStorage
}
} }
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
const NO_WORKSPACE = ' - none - ' const NO_WORKSPACE = ' - none - '
const queryParams = new QueryParams() 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<any> let plugin, dispatch: React.Dispatch<any>
export const setPlugin = (filePanelPlugin, reducerDispatch) => { export const setPlugin = (filePanelPlugin, reducerDispatch) => {
@ -70,19 +94,29 @@ export const addInputField = async (type: 'file' | 'folder', path: string, cb?:
}) })
}) })
promise.then((files) => { promise
.then((files) => {
dispatch(addInputFieldSuccess(path, files, type)) dispatch(addInputFieldSuccess(path, files, type))
}).catch((error) => { })
.catch((error) => {
console.error(error) console.error(error)
}) })
return promise return promise
} }
const removeSlash = (s: string) => { const removeSlash = (s: string) => {
return s.replace(/^\/+/, "") return s.replace(/^\/+/, '')
} }
export const createWorkspace = async (workspaceName: string, workspaceTemplateName: WorkspaceTemplate, opts = null, isEmpty = false, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void, isGitRepo: boolean = false, createCommit: boolean = true) => { export const createWorkspace = async (
workspaceName: string,
workspaceTemplateName: WorkspaceTemplate,
opts = null,
isEmpty = false,
cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void,
isGitRepo: boolean = false,
createCommit: boolean = true
) => {
await plugin.fileManager.closeAllFiles() await plugin.fileManager.closeAllFiles()
const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName) const promise = createWorkspaceTemplate(workspaceName, workspaceTemplateName)
dispatch(createWorkspaceRequest()) dispatch(createWorkspaceRequest())
@ -121,7 +155,7 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
await plugin.call('dGitProvider', 'commit', { await plugin.call('dGitProvider', 'commit', {
author: { author: {
name, name,
email email,
}, },
message: `Initial commit: remix template ${workspaceTemplateName}`, message: `Initial commit: remix template ${workspaceTemplateName}`,
}) })
@ -138,7 +172,6 @@ export const createWorkspace = async (workspaceName: string, workspaceTemplateNa
} }
if (workspaceTemplateName === 'semaphore') { if (workspaceTemplateName === 'semaphore') {
const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler')
if (!isCircomActive) await plugin.call('manager', 'activatePlugin', '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 // 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') => { export const createWorkspaceTemplate = async (workspaceName: string, template: WorkspaceTemplate = 'remixDefault') => {
if (!workspaceName) throw new Error('workspace name cannot be empty') if (!workspaceName) throw new Error('workspace name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') 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 { else {
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
await workspaceProvider.createWorkspace(workspaceName) await workspaceProvider.createWorkspace(workspaceName)
} }
} }
export type UrlParametersType = { export type UrlParametersType = {
gist: string, gist: string
code: string, code: string
url: string, url: string
language: string language: string
} }
@ -176,7 +208,8 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
case 'code-template': case 'code-template':
// creates a new workspace code-sample and loads code from url params. // creates a new workspace code-sample and loads code from url params.
try { try {
let path = ''; let content let path = ''
let content
if (params.code) { if (params.code) {
const hashed = bufferToHex(hash.keccakFromString(params.code)) const hashed = bufferToHex(hash.keccakFromString(params.code))
@ -193,7 +226,7 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
try { try {
content = JSON.parse(content) as any 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 const standardInput: JSONStandardInput = content as JSONStandardInput
for (const [fname, source] of Object.entries(standardInput.sources)) { for (const [fname, source] of Object.entries(standardInput.sources)) {
await workspaceProvider.set(fname, source.content) await workspaceProvider.set(fname, source.content)
@ -221,7 +254,18 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
const data = response.data as { files: any } const data = response.data as { files: any }
if (!data.files) { 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 = {} const obj = {}
@ -232,11 +276,22 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
}) })
plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => { plugin.fileManager.setBatchFiles(obj, 'workspace', true, (errorLoadingFile) => {
if (errorLoadingFile) { if (errorLoadingFile) {
dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => { }, null)) dispatch(displayNotification('', errorLoadingFile.message || errorLoadingFile, 'OK', null, () => {}, null))
} }
}) })
} catch (e) { } 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) console.error(e)
} }
break break
@ -256,7 +311,18 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe
} }
} }
} catch (e) { } 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) console.error(e)
} }
break break
@ -277,15 +343,16 @@ export const fetchWorkspaceDirectory = async (path: string) => {
const promise: Promise<FileTree> = new Promise((resolve) => { const promise: Promise<FileTree> = new Promise((resolve) => {
provider.resolveDirectory(path, (error, fileTree: FileTree) => { provider.resolveDirectory(path, (error, fileTree: FileTree) => {
if (error) console.error(error) if (error) console.error(error)
resolve(fileTree) resolve(fileTree)
}) })
}) })
dispatch(fetchWorkspaceDirectoryRequest()) dispatch(fetchWorkspaceDirectoryRequest())
promise.then((fileTree) => { promise
.then((fileTree) => {
dispatch(fetchWorkspaceDirectorySuccess(path, fileTree)) dispatch(fetchWorkspaceDirectorySuccess(path, fileTree))
}).catch((error) => { })
.catch((error) => {
dispatch(fetchWorkspaceDirectoryError(error.message)) dispatch(fetchWorkspaceDirectoryError(error.message))
}) })
return promise return promise
@ -295,6 +362,7 @@ export const renameWorkspace = async (oldName: string, workspaceName: string, cb
await renameWorkspaceFromProvider(oldName, workspaceName) await renameWorkspaceFromProvider(oldName, workspaceName)
await dispatch(setRenameWorkspace(oldName, workspaceName)) await dispatch(setRenameWorkspace(oldName, workspaceName))
await plugin.setWorkspace({ name: workspaceName, isLocalhost: false }) await plugin.setWorkspace({ name: workspaceName, isLocalhost: false })
await plugin.deleteWorkspace(oldName)
await plugin.workspaceRenamed(oldName, workspaceName) await plugin.workspaceRenamed(oldName, workspaceName)
cb && cb(null, workspaceName) cb && cb(null, workspaceName)
} }
@ -319,7 +387,9 @@ export const deleteWorkspace = async (workspaceName: string, cb?: (err: Error, r
} }
export const deleteAllWorkspaces = async () => { export const deleteAllWorkspaces = async () => {
await (await getWorkspaces()).map(async workspace => { await (
await getWorkspaces()
).map(async (workspace) => {
await deleteWorkspaceFromProvider(workspace.name) await deleteWorkspaceFromProvider(workspace.name)
await dispatch(setDeleteWorkspace(workspace.name)) await dispatch(setDeleteWorkspace(workspace.name))
plugin.workspaceDeleted(workspace.name) plugin.workspaceDeleted(workspace.name)
@ -356,7 +426,6 @@ export const switchToWorkspace = async (name: string) => {
if (isGitRepo) { if (isGitRepo) {
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
} }
dispatch(setMode('browser')) dispatch(setMode('browser'))
@ -370,18 +439,18 @@ const loadFile = (name, file, provider, cb?): void => {
fileReader.onload = async function (event) { fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) { 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 { try {
await provider.set(name, event.target.result) await provider.set(name, event.target.result)
} catch (error) { } 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 config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').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) 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 // 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 // a file and then just use `files.add`. The file explorer will
// pick that up via the 'fileAdded' event from the files module. // 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 workspaceProvider = plugin.fileProviders.workspace
const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}`
if (!await workspaceProvider.exists(name)) { if (!(await workspaceProvider.exists(name))) {
loadFile(name, file, workspaceProvider, cb) loadFile(name, file, workspaceProvider, cb)
} else { } else {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -412,7 +481,7 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
loadFile(name, file, workspaceProvider, cb) loadFile(name, file, workspaceProvider, cb)
}, },
cancelFn: () => {}, cancelFn: () => {},
hideFn: () => {} hideFn: () => {},
} }
plugin.call('notification', 'modal', modalContent) 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<string, any>) => void) => { export const uploadFolder = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
for(const file of [...target.files]) { for (const file of [...target.files]) {
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
const name = targetFolder === '/' ? file.webkitRelativePath : `${targetFolder}/${file.webkitRelativePath}` const name = targetFolder === '/' ? file.webkitRelativePath : `${targetFolder}/${file.webkitRelativePath}`
if (!await workspaceProvider.exists(name)) { if (!(await workspaceProvider.exists(name))) {
loadFile(name, file, workspaceProvider, cb) loadFile(name, file, workspaceProvider, cb)
} else { } else {
const modalContent: AppModal = { const modalContent: AppModal = {
@ -437,23 +506,24 @@ export const uploadFolder = async (target, targetFolder: string, cb?: (err: Erro
loadFile(name, file, workspaceProvider, cb) loadFile(name, file, workspaceProvider, cb)
}, },
cancelFn: () => {}, cancelFn: () => {},
hideFn: () => {} hideFn: () => {},
} }
plugin.call('notification', 'modal', modalContent) 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 { 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 const workspacesPath = plugin.fileProviders.workspace.workspacesPath
plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => { plugin.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
if (error) { if (error) {
return reject(error) return reject(error)
} }
Promise.all(Object.keys(items) Promise.all(
Object.keys(items)
.filter((item) => items[item].isDirectory) .filter((item) => items[item].isDirectory)
.map(async (folder) => { .map(async (folder) => {
const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git') const isGitRepo: boolean = await plugin.fileProviders.browser.exists('/' + folder + '/.git')
@ -468,20 +538,21 @@ export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolea
name: folder.replace(workspacesPath + '/', ''), name: folder.replace(workspacesPath + '/', ''),
isGitRepo, isGitRepo,
branches, branches,
currentBranch currentBranch,
} }
} else { } else {
return { return {
name: folder.replace(workspacesPath + '/', ''), name: folder.replace(workspacesPath + '/', ''),
isGitRepo isGitRepo,
} }
} }
})).then(workspacesList => resolve(workspacesList)) })
).then((workspacesList) => resolve(workspacesList))
}) })
}) })
await plugin.setWorkspaces(workspaces) await plugin.setWorkspaces(workspaces)
return workspaces return workspaces
} catch (e) { } } catch (e) {}
} }
export const cloneRepository = async (url: string) => { export const cloneRepository = async (url: string) => {
@ -496,7 +567,8 @@ export const cloneRepository = async (url: string) => {
const promise = plugin.call('dGitProvider', 'clone', repoConfig, repoName, true) const promise = plugin.call('dGitProvider', 'clone', repoConfig, repoName, true)
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
promise.then(async () => { promise
.then(async () => {
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
@ -509,11 +581,13 @@ export const cloneRepository = async (url: string) => {
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch)) dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
const cloneModal = { const cloneModal = {
id: 'cloneGitRepository', id: 'cloneGitRepository',
title: 'Clone Git Repository', 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', 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', modalType: 'modal',
okLabel: 'OK', okLabel: 'OK',
okFn: async () => { okFn: async () => {
@ -523,7 +597,7 @@ export const cloneRepository = async (url: string) => {
hideFn: async () => { hideFn: async () => {
await deleteWorkspace(repoName) await deleteWorkspace(repoName)
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
} },
} }
plugin.call('notification', 'modal', cloneModal) plugin.call('notification', 'modal', cloneModal)
}) })
@ -540,7 +614,6 @@ export const checkGit = async () => {
dispatch(setCurrentWorkspaceCurrentBranch(currentBranch)) dispatch(setCurrentWorkspaceCurrentBranch(currentBranch))
} }
export const getRepositoryTitle = async (url: string) => { export const getRepositoryTitle = async (url: string) => {
const urlArray = url.split('/') const urlArray = url.split('/')
let name = urlArray.length > 0 ? urlArray[urlArray.length - 1] : '' 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) => { export const getGitRepoBranches = async (workspacePath: string) => {
const gitConfig: { fs: IndexedDBStorage, dir: string } = { const gitConfig: { fs: IndexedDBStorage; dir: string } = {
fs: window.remixFileSystemCallback, 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 return branches
} }
export const getGitRepoCurrentBranch = async (workspaceName: string) => { export const getGitRepoCurrentBranch = async (workspaceName: string) => {
const gitConfig: { fs: IndexedDBStorage, dir: string } = { const gitConfig: { fs: IndexedDBStorage; dir: string } = {
fs: window.remixFileSystemCallback, fs: window.remixFileSystemCallback,
dir: addSlash(workspaceName) dir: addSlash(workspaceName),
} }
const currentBranch: string = await plugin.call('dGitProvider', 'currentbranch', { ...gitConfig }) const currentBranch: string = await plugin.call('dGitProvider', 'currentbranch', { ...gitConfig })
return currentBranch return currentBranch
} }
export const showAllBranches = async () => { export const showAllBranches = async () => {
console.log('showAllBranches')
const isActive = await plugin.call('manager', 'isActive', 'dgit') const isActive = await plugin.call('manager', 'isActive', 'dgit')
if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit')
plugin.call('menuicons', 'select', 'dgit') plugin.call('menuicons', 'select', 'dgit')
@ -618,26 +690,32 @@ export const switchBranch = async (branch: string) => {
okLabel: 'Force Checkout', okLabel: 'Force Checkout',
okFn: async () => { okFn: async () => {
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => { plugin
.call('dGitProvider', 'checkout', { ref: branch, force: true }, false)
.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH) await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch)) dispatch(setCurrentWorkspaceCurrentBranch(branch))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
}) })
}, },
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
cancelFn: () => { }, cancelFn: () => {},
hideFn: () => { } hideFn: () => {},
} }
plugin.call('notification', 'modal', cloneModal) plugin.call('notification', 'modal', cloneModal)
} else { } else {
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, force: true }, false).then(async () => { plugin
.call('dGitProvider', 'checkout', { ref: branch, force: true }, false)
.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH) await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch)) dispatch(setCurrentWorkspaceCurrentBranch(branch))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
}) })
} }
@ -647,7 +725,8 @@ export const createNewBranch = async (branch: string) => {
const promise = plugin.call('dGitProvider', 'branch', { ref: branch, checkout: true }, false) const promise = plugin.call('dGitProvider', 'branch', { ref: branch, checkout: true }, false)
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
promise.then(async () => { promise
.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH) await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch)) dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
@ -656,7 +735,8 @@ export const createNewBranch = async (branch: string) => {
dispatch(setCurrentWorkspaceBranches(branches)) dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
}) })
return promise return promise
@ -665,30 +745,29 @@ export const createNewBranch = async (branch: string) => {
export const createSolidityGithubAction = async () => { export const createSolidityGithubAction = async () => {
const path = '.github/workflows/run-solidity-unittesting.yml' 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) plugin.call('fileManager', 'open', path)
} }
export const createTsSolGithubAction = async () => { export const createTsSolGithubAction = async () => {
const path = '.github/workflows/run-js-test.yml' 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) plugin.call('fileManager', 'open', path)
} }
export const createSlitherGithubAction = async () => { export const createSlitherGithubAction = async () => {
const path = '.github/workflows/run-slither-action.yml' 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) plugin.call('fileManager', 'open', path)
} }
const scriptsRef = { const scriptsRef = {
'deployer': contractDeployerScripts, deployer: contractDeployerScripts,
'etherscan': etherscanScripts etherscan: etherscanScripts,
} }
export const createHelperScripts = async (script: string) => { export const createHelperScripts = async (script: string) => {
if (!scriptsRef[script]) return if (!scriptsRef[script]) return
await scriptsRef[script](plugin) await scriptsRef[script](plugin)
plugin.call('notification', 'toast', 'scripts added in the "scripts" folder') plugin.call('notification', 'toast', 'scripts added in the "scripts" folder')
@ -708,7 +787,9 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => {
okLabel: 'Force Checkout', okLabel: 'Force Checkout',
okFn: async () => { okFn: async () => {
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => { plugin
.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false)
.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH) await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch)) dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
@ -717,18 +798,21 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => {
dispatch(setCurrentWorkspaceBranches(branches)) dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
}) })
}, },
cancelLabel: 'Cancel', cancelLabel: 'Cancel',
cancelFn: () => { }, cancelFn: () => {},
hideFn: () => { } hideFn: () => {},
} }
plugin.call('notification', 'modal', cloneModal) plugin.call('notification', 'modal', cloneModal)
} else { } else {
dispatch(cloneRepositoryRequest()) dispatch(cloneRepositoryRequest())
plugin.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false).then(async () => { plugin
.call('dGitProvider', 'checkout', { ref: branch, remote, force: true }, false)
.then(async () => {
await fetchWorkspaceDirectory(ROOT_PATH) await fetchWorkspaceDirectory(ROOT_PATH)
dispatch(setCurrentWorkspaceCurrentBranch(branch)) dispatch(setCurrentWorkspaceCurrentBranch(branch))
const workspacesPath = plugin.fileProviders.workspace.workspacesPath const workspacesPath = plugin.fileProviders.workspace.workspacesPath
@ -737,7 +821,8 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => {
dispatch(setCurrentWorkspaceBranches(branches)) dispatch(setCurrentWorkspaceBranches(branches))
dispatch(cloneRepositorySuccess()) dispatch(cloneRepositorySuccess())
}).catch(() => { })
.catch(() => {
dispatch(cloneRepositoryFailed()) dispatch(cloneRepositoryFailed())
}) })
} }

@ -903,8 +903,8 @@ export function Workspace() {
</span> </span>
) : null} ) : null}
<span className="d-flex"> <span className="d-flex">
<label className="pl-1 form-check-label" htmlFor="workspacesSelect" style={{wordBreak: 'keep-all'}}> <label className="pl-2 form-check-label" style={{wordBreak: 'keep-all'}}>
<FormattedMessage id="filePanel.workspace" /> <FormattedMessage id='filePanel.workspace' />
</label> </label>
</span> </span>
</div> </div>

@ -7,17 +7,17 @@ import { ViewPlugin } from '@remixproject/engine-web'
export type action = { name: string, type?: Array<WorkspaceElement>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean, group: number } export type action = { name: string, type?: Array<WorkspaceElement>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean, group: number }
export interface JSONStandardInput { export interface JSONStandardInput {
language: "Solidity"; language: 'Solidity'
settings?: any, settings?: any
sources: { sources: {
[globalName: string]: { [globalName: string]: {
keccak256?: string; keccak256?: string
content: string; content: string
}, }
}; }
} }
export type MenuItems = action[] 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 { export interface WorkspaceProps {
plugin: FilePanelType plugin: FilePanelType
} }
@ -38,42 +38,42 @@ export interface Modal {
} }
export interface FileType { export interface FileType {
path: string, path: string
name: string, name: string
isDirectory: boolean, isDirectory: boolean
type: 'folder' | 'file' | 'gist', type: 'folder' | 'file' | 'gist'
child?: File[] child?: File[]
} }
export interface FilePanelType extends ViewPlugin { export interface FilePanelType extends ViewPlugin {
setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void, setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void
createWorkspace: (name: string, workspaceTemplateName: string) => void, createWorkspace: (name: string, workspaceTemplateName: string) => void
renameWorkspace: (oldName: string, newName: string) => void renameWorkspace: (oldName: string, newName: string) => void
compileContractForUml: (path: string) => void compileContractForUml: (path: string) => void
workspaceRenamed: ({ name }) => void, workspaceRenamed: ({ name }) => void
workspaceCreated: ({ name }) => void, workspaceCreated: ({ name }) => void
workspaceDeleted: ({ name }) => void, workspaceDeleted: ({ name }) => void
workspace?: any // workspace provider, workspace?: any // workspace provider,
browser?: any // browser provider browser?: any // browser provider
localhost?: any // localhost provider localhost?: any // localhost provider
fileManager? : any fileManager?: any
appManager: RemixAppManager appManager: RemixAppManager
registry?: any // registry registry?: any // registry
pluginApi?: any pluginApi?: any
request: { request: {
createWorkspace: () => void, createWorkspace: () => void
setWorkspace: (workspaceName: string) => void, setWorkspace: (workspaceName: string) => void
createNewFile: () => void, createNewFile: () => void
uploadFile: (target: EventTarget & HTMLInputElement) => void, uploadFile: (target: EventTarget & HTMLInputElement) => void
getCurrentWorkspace: () => void getCurrentWorkspace: () => void
} // api request, } // api request,
workspaces: any, workspaces: any
registeredMenuItems: MenuItems // menu items registeredMenuItems: MenuItems // menu items
removedMenuItems: MenuItems removedMenuItems: MenuItems
initialWorkspace: string, initialWorkspace: string
resetNewFile: () => void, resetNewFile: () => void
getWorkspaces: () => string[] getWorkspaces: () => string[]
} }
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface FileExplorerProps { export interface FileExplorerProps {
@ -128,36 +128,36 @@ export interface FileExplorerProps {
} }
type Placement = import('react-overlays/usePopper').Placement type Placement = import('react-overlays/usePopper').Placement
export interface FileExplorerMenuProps { export interface FileExplorerMenuProps {
title: string, title: string
menuItems: string[], menuItems: string[]
createNewFile: (folder?: string) => void, createNewFile: (folder?: string) => void
createNewFolder: (parentFolder?: string) => void, createNewFolder: (parentFolder?: string) => void
publishToGist: (path?: string) => void, publishToGist: (path?: string) => void
uploadFile: (target: EventTarget & HTMLInputElement) => void uploadFile: (target: EventTarget & HTMLInputElement) => void
uploadFolder: (target: EventTarget & HTMLInputElement) => void uploadFolder: (target: EventTarget & HTMLInputElement) => void
tooltipPlacement?: Placement tooltipPlacement?: Placement
} }
export interface FileExplorerContextMenuProps { export interface FileExplorerContextMenuProps {
actions: action[], actions: action[]
createNewFile: (folder?: string) => void, createNewFile: (folder?: string) => void
createNewFolder: (parentFolder?: string) => void, createNewFolder: (parentFolder?: string) => void
deletePath: (path: string | string[]) => void, deletePath: (path: string | string[]) => void
renamePath: (path: string, type: string) => void, renamePath: (path: string, type: string) => void
downloadPath: (path: string) => void, downloadPath: (path: string) => void
hideContextMenu: () => void, hideContextMenu: () => void
publishToGist?: (path?: string, type?: string) => void, publishToGist?: (path?: string, type?: string) => void
pushChangesToGist?: (path?: string, type?: string) => void, pushChangesToGist?: (path?: string, type?: string) => void
publishFolderToGist?: (path?: string, type?: string) => void, publishFolderToGist?: (path?: string, type?: string) => void
publishFileToGist?: (path?: string, type?: string) => void, publishFileToGist?: (path?: string, type?: string) => void
runScript?: (path: string) => void, runScript?: (path: string) => void
emit?: (cmd: customAction) => void, emit?: (cmd: customAction) => void
pageX: number, pageX: number
pageY: number, pageY: number
path: string, path: string
type: string, type: string
focus: {key:string, type:string}[], focus: { key: string; type: string }[]
onMouseOver?: (...args) => void, onMouseOver?: (...args) => void
copy?: (path: string, type: string) => void, copy?: (path: string, type: string) => void
paste?: (destination: string, type: string) => void paste?: (destination: string, type: string) => void
copyFileName?: (path: string, type: string) => void copyFileName?: (path: string, type: string) => void
copyPath?: (path: string, type: string) => void copyPath?: (path: string, type: string) => void
@ -190,7 +190,7 @@ export interface WorkSpaceState {
showContextMenu: boolean showContextMenu: boolean
reservedKeywords: string[] reservedKeywords: string[]
copyElement: CopyElementType[] copyElement: CopyElementType[]
} }
export type FileFocusContextType = { export type FileFocusContextType = {
element: string element: string

@ -81,5 +81,6 @@ export const TEMPLATE_NAMES = {
'ozerc1155': 'OpenZeppelin ERC1155', 'ozerc1155': 'OpenZeppelin ERC1155',
'zeroxErc20': '0xProject ERC20', 'zeroxErc20': '0xProject ERC20',
'gnosisSafeMultisig': 'Gnosis Safe', 'gnosisSafeMultisig': 'Gnosis Safe',
'playground': 'Playground',
'semaphore': 'Semaphore' 'semaphore': 'Semaphore'
} }

@ -5,6 +5,7 @@ export { default as ozerc721 } from './templates/ozerc721'
export { default as ozerc1155 } from './templates/ozerc1155' export { default as ozerc1155 } from './templates/ozerc1155'
export { default as zeroxErc20 } from './templates/zeroxErc20' export { default as zeroxErc20 } from './templates/zeroxErc20'
export { default as gnosisSafeMultisig } from './templates/gnosisSafeMultisig' export { default as gnosisSafeMultisig } from './templates/gnosisSafeMultisig'
export { default as playground } from './templates/playground'
export { default as semaphore } from './templates/semaphore' export { default as semaphore } from './templates/semaphore'
export { contractDeployerScripts } from './script-templates/contract-deployer' export { contractDeployerScripts } from './script-templates/contract-deployer'

@ -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": {}
}
]
}

@ -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_name> module require is not supported by Remix IDE' will be shown.

@ -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!";
}
}

@ -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,
}
}

@ -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)
}
})()

@ -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)
}
})()

@ -0,0 +1,29 @@
import { ethers } from 'ethers'
/**
* Deploy the given contract
* @param {string} contractName name of the contract to deploy
* @param {Array<any>} 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<any>, accountIndex?: number): Promise<ethers.Contract> => {
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
}

@ -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<any>} 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<any>, from?: string, gas?: number): Promise<Options> => {
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
}
Loading…
Cancel
Save