From e4625aed09c0b9beb12616e1f1aac86d4b48b80a Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 29 Apr 2024 11:03:10 +0200 Subject: [PATCH 01/41] add_templates fix E2E --- apps/etherscan/src/app/views/VerifyView.tsx | 2 +- apps/remix-ide-e2e/src/tests/ballot.test.ts | 2 + apps/remix-ide-e2e/src/tests/circom.test.ts | 12 +- apps/remix-ide-e2e/src/tests/erc721.test.ts | 4 +- .../src/tests/solidityUnittests.test.ts | 2 + .../remix-ide-e2e/src/tests/workspace.test.ts | 47 +- .../src/tests/workspace_git.test.ts | 8 +- apps/remix-ide/src/app.js | 7 +- apps/remix-ide/src/app/panels/file-panel.js | 2 +- .../src/app/plugins/templates-selection.tsx | 473 ++++++++++++++++++ .../workspace/src/lib/actions/events.ts | 14 +- .../workspace/src/lib/actions/workspace.ts | 62 ++- .../lib/components/workspace-hamburger.tsx | 41 -- .../workspace/src/lib/remix-ui-workspace.tsx | 203 +------- .../workspace/src/lib/utils/constants.ts | 18 + .../contract-deployer/index.ts | 2 +- .../create2-solidity-factory/index.ts | 2 +- .../src/script-templates/etherscan/index.ts | 2 +- .../src/script-templates/sindri/index.ts | 2 +- 19 files changed, 598 insertions(+), 307 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/templates-selection.tsx diff --git a/apps/etherscan/src/app/views/VerifyView.tsx b/apps/etherscan/src/app/views/VerifyView.tsx index ca70dba74cd..f41e9203afd 100644 --- a/apps/etherscan/src/app/views/VerifyView.tsx +++ b/apps/etherscan/src/app/views/VerifyView.tsx @@ -211,7 +211,7 @@ export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, netwo type="button" className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" onClick={async () => { - etherscanScripts(client) + etherscanScripts({}, client) }} > Generate Verification Scripts diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index dbdef20903e..36b20b1a0cd 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -97,6 +97,8 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation diff --git a/apps/remix-ide-e2e/src/tests/circom.test.ts b/apps/remix-ide-e2e/src/tests/circom.test.ts index a5f6e2a1efb..f895fbd1a63 100644 --- a/apps/remix-ide-e2e/src/tests/circom.test.ts +++ b/apps/remix-ide-e2e/src/tests/circom.test.ts @@ -13,10 +13,8 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=semaphore]') + .waitForElementPresent('*[data-id="create-semaphore"]') + .scrollAndClick('*[data-id="create-semaphore"]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -155,10 +153,8 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=hashchecker]') + .waitForElementPresent('*[data-id="create-hashchecker"]') + .scrollAndClick('*[data-id="create-hashchecker"]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) diff --git a/apps/remix-ide-e2e/src/tests/erc721.test.ts b/apps/remix-ide-e2e/src/tests/erc721.test.ts index 373477f064a..90b125efa27 100644 --- a/apps/remix-ide-e2e/src/tests/erc721.test.ts +++ b/apps/remix-ide-e2e/src/tests/erc721.test.ts @@ -17,12 +17,12 @@ module.exports = { .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') // create contract + .waitForElementPresent('*[data-id="create-hashchecker"]') + .scrollAndClick('*[data-id="create-hashchecker"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc721]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts index c96f7023398..a5536423f51 100644 --- a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts @@ -180,6 +180,8 @@ module.exports = { // creating a new workspace .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]') .setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new') diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index d351b05ce18..f06092ec009 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -38,6 +38,8 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation @@ -110,12 +112,12 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-blank"]') + .scrollAndClick('*[data-id="create-blank"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' }) - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=blank]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -133,12 +135,12 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc20"]') + .scrollAndClick('*[data-id="create-ozerc20"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' }) - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc20]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -194,12 +196,12 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc721"]') + .scrollAndClick('*[data-id="create-ozerc721"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc721]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -255,12 +257,12 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc1155"]') + .scrollAndClick('*[data-id="create-ozerc1155"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' }) - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc1155]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -316,15 +318,10 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc1155-{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}"]') + .scrollAndClick('*[data-id="create-ozerc1155-{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc1155]') - .waitForElementPresent('*[data-id="ozCustomization"]') - .click('*[data-id="featureTypeMintable"]') - .click('*[data-id="featureTypeBurnable"]') - .click('*[data-id="featureTypePausable"]') - .click('*[data-id="upgradeTypeUups"]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -386,10 +383,10 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-hashchecker"]') + .scrollAndClick('*[data-id="create-hashchecker"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=hashchecker]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) @@ -424,6 +421,8 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]') .setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name') @@ -438,6 +437,8 @@ module.exports = { }) .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]') .setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1') @@ -494,10 +495,10 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc1155"]') + .scrollAndClick('*[data-id="create-ozerc1155"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc1155]') .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'sometestworkspace' }) .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) @@ -521,11 +522,11 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-ozerc1155"]') + .scrollAndClick('*[data-id="create-ozerc1155"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=ozerc1155]') .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_db_test' }) .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) @@ -551,10 +552,10 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]') + .scrollAndClick('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=uniswapV4HookBookMultiSigSwapHook]') // eslint-disable-next-line dot-notation .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'multisig cookbook' }) .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') diff --git a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts index fdde711a8fd..2980f1a7405 100644 --- a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts @@ -13,6 +13,8 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') .waitForElementVisible({ @@ -47,6 +49,8 @@ module.exports = { .waitForElementNotVisible('[data-id="workspaceGitPanel"]') .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-blank"]') + .scrollAndClick('*[data-id="create-blank"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') // eslint-disable-next-line dot-notation @@ -391,10 +395,10 @@ module.exports = { browser .click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-uniswapV4Template"]') + .scrollAndClick('*[data-id="create-uniswapV4Template"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button') - .click('select[id="wstemplate"]') - .click('select[id="wstemplate"] option[value=uniswapV4Template]') .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') .execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() }) .pause(100) diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 2dafcd7efec..64faf3a39b8 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -61,6 +61,8 @@ import { Matomo } from './app/plugins/matomo' import {SolCoder} from './app/plugins/solcoderAI' +import {TemplatesSelection} from './app/plugins/templates-selection' + const isElectron = require('is-electron') const remixLib = require('@remix-project/remix-lib') @@ -315,6 +317,8 @@ class AppComponent { // ----------------- run script after each compilation results ----------- const pluginStateLogger = new PluginStateLogger() + const templateSelection = new TemplatesSelection() + this.engine.register([ permissionHandler, this.layout, @@ -366,7 +370,8 @@ class AppComponent { solcoder, git, pluginStateLogger, - matomo + matomo, + templateSelection ]) //---- fs plugin diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 02d233d5bb8..b6ff0405132 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -195,7 +195,7 @@ module.exports = class Filepanel extends ViewPlugin { if (err) reject(err) else resolve(data || true) }) - }) + }, false) } renameWorkspace(oldName, workspaceName) { diff --git a/apps/remix-ide/src/app/plugins/templates-selection.tsx b/apps/remix-ide/src/app/plugins/templates-selection.tsx new file mode 100644 index 00000000000..dfc98490ec3 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/templates-selection.tsx @@ -0,0 +1,473 @@ + +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { AppModal } from '@remix-ui/app' +import { ViewPlugin } from '@remixproject/engine-web' +import { PluginViewWrapper } from '@remix-ui/helper' +import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' +import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' +import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' +import type { TemplateGroup } from '@remix-ui/workspace' + +const isElectron = require('is-electron') + +//@ts-ignore +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'templateSelection', + displayName: 'Template Selection', + description: 'templateSelection', + location: 'mainPanel', + methods: [], + events: [] +} + +export class TemplatesSelection extends ViewPlugin { + templates: Array + dispatch: React.Dispatch = () => { } + constructor() { + super(profile) + } + + async onActivation() { + this.handleThemeChange() + await this.call('tabs', 'focus', 'remixGuide') + this.renderComponent() + _paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide']) + } + + onDeactivation(): void { + } + + private handleThemeChange() { + this.on('theme', 'themeChanged', (theme: any) => { + this.renderComponent() + }) + } + + setDispatch(dispatch: React.Dispatch): void { + this.dispatch = dispatch + this.renderComponent() + } + + render() { + return ( +
+ +
+ ) + } + + renderComponent() { + this.dispatch({ + ...this, + }) + } + + updateComponent() { + /* + const opts = { + // @ts-ignore: Object is possibly 'null'. + mintable: mintableCheckboxRef.current.checked, + // @ts-ignore: Object is possibly 'null'. + burnable: burnableCheckboxRef.current.checked, + // @ts-ignore: Object is possibly 'null'. + pausable: pausableCheckboxRef.current.checked, + // @ts-ignore: Object is possibly 'null'. + upgradeable: transparentRadioRef.current.checked ? transparentRadioRef.current.value : uupsRadioRef.current.checked ? uupsRadioRef.current.value : false + } + */ + this.templates = [ + { + name: "Generic", + items: [ + { value: "remixDefault", displayName: window._intl.formatMessage({ id: 'filePanel.basic' }) }, + { value: "blank", displayName: window._intl.formatMessage({ id: 'filePanel.blank' }) } + ] + }, + { + name: "OpenZeppelin", + items: [ + { + value: "ozerc20", + displayName: "ERC20" + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)" + }, + { + value: "ozerc1155", + displayName: "ERC1155" + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + mintable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + mintable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + mintable: true + } + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + mintable: true, + burnable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + mintable: true, + burnable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + mintable: true, + burnable: true + } + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + mintable: true, + pausable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + mintable: true, + pausable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + mintable: true, + pausable: true + } + } + ] + }, + { + name: "OpenZeppelin Proxy", + items: [ + { + value: "ozerc20", + displayName: "ERC20", + opts: { + upgradeable: 'uups' + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + upgradeable: 'uups' + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + upgradeable: 'uups' + } + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + upgradeable: 'uups', + mintable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + upgradeable: 'uups', + mintable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + upgradeable: 'uups', + mintable: true + } + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + upgradeable: 'uups', + mintable: true, + burnable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + upgradeable: 'uups', + mintable: true, + burnable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + upgradeable: 'uups', + mintable: true, + burnable: true + } + }, + { + value: "ozerc20", + displayName: "ERC20", + opts: { + upgradeable: 'uups', + mintable: true, + pausable: true + } + }, + { + value: "ozerc721", + displayName: "ERC721 (NFT)", + opts: { + upgradeable: 'uups', + mintable: true, + pausable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + upgradeable: 'uups', + mintable: true, + pausable: true + } + }, + { + value: "ozerc1155", + displayName: "ERC1155", + opts: { + upgradeable: 'uups', + mintable: true, + burnable: true, + pausable: true + } + } + ] + }, + { + name: "OxProject", + items: [ + { value: "zeroxErc20", displayName: "ERC20" } + ] + }, + { + name: "Gnosis Safe", + items: [ + { value: "gnosisSafeMultisig", displayName: window._intl.formatMessage({ id: 'filePanel.multiSigWallet' }) } + ] + }, + { + name: "Circom ZKP", + items: [ + { value: "semaphore", displayName: window._intl.formatMessage({ id: 'filePanel.semaphore' }) }, + { value: "hashchecker", displayName: window._intl.formatMessage({ id: 'filePanel.hashchecker' }) }, + { value: "rln", displayName: window._intl.formatMessage({ id: 'filePanel.rln' }) } + ] + }, + { + name: "Generic ZKP", + items: [ + { value: "sindriScripts", displayName: window._intl.formatMessage({ id: 'filePanel.addscriptsindri' }) }, + ] + }, + { + name: "Uniswap V4", + items: [ + { value: "uniswapV4Template", displayName: window._intl.formatMessage({ id: 'filePanel.uniswapV4Template' }) }, + { value: "breakthroughLabsUniswapv4Hooks", displayName: window._intl.formatMessage({ id: 'filePanel.breakthroughLabsUniswapv4Hooks' }) }, + { value: "uniswapV4HookBookMultiSigSwapHook", displayName: window._intl.formatMessage({ id: 'filePanel.uniswapV4HookBookMultiSigSwapHook' }) } + ] + }, + { + name: "Solidity CREATE2", + items: [ + { value: "contractCreate2Factory", displayName: window._intl.formatMessage({ id: 'filePanel.addcreate2solidityfactory' }) }, + { value: "contractDeployerScripts", displayName: window._intl.formatMessage({ id: 'filePanel.addscriptdeployer' }) } + ] + }, + { + name: "Contract Verification", + items: [ + { value: "etherscanScripts", displayName: window._intl.formatMessage({ id: 'filePanel.addscriptetherscan' }) }, + ] + } + ] + + const createWorkspace = async (item) => { + const defaultName = await this.call('filePanel', 'getAvailableWorkspaceName', item.displayName) + + const username = await this.call('settings', 'get', 'settings/github-user-name') + const email = await this.call('settings', 'get', 'settings/github-email') + const gitNotSet = !username || !email + let workspaceName = defaultName + let initGit = true + const modal: AppModal = { + id: 'TemplatesSelection', + title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), + message: await createModalMessage(defaultName, gitNotSet, (value) => workspaceName = value, (value) => initGit = !!value), + okLabel: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.ok':'filePanel.selectFolder' }), + } + const modalResult = await this.call('notification', 'modal', modal) + if (!modalResult) return + this.emit('createWorkspaceReducerEvent', workspaceName, item.value, item.opts, false, (e, data) => { + if (e) { + const modal: AppModal = { + id: 'TemplatesSelection', + title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), + message: e.message, + okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), + cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' }) + } + this.call('notification', 'modal', modal) + console.error(e) + } + + }, initGit) + } + + const addToExistingWorkspace = async (item) => { + this.emit('addTemplateToWorkspaceReducerEvent', item.value, item.opts, false, (e, data) => { + if (e) { + const modal: AppModal = { + id: 'TemplatesSelection', + title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), + message: e.message, + okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), + cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' }) + } + this.call('notification', 'modal', modal) + console.error(e) + } + + }) + } + + return ( + + { + + this.templates.map(template => { + return + {template.items.map(item => { + return +
+ {item.displayName} + {JSON.stringify(item.opts)} +
+
+
+ })} +
+ })} +
+ ) + } + +} + +const createModalMessage = async ( + defaultName: string, + gitConfigNotSet: boolean, + onChangeTemplateName: (name: string) => void, + onChangeInitGit: (name: string) => void) => { + + return ( + <> + + onChangeTemplateName(e.target.value)} + /> +
+ onChangeInitGit(e.target.value)} + /> + +
+ {gitConfigNotSet ? ( +
+ +
+ ) : ( + <> + )} + + ) +} + diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index 74f8bad6b5c..c9563c9c998 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -5,7 +5,7 @@ import React from 'react' import { action, FileTree, WorkspaceTemplate } from '../types' import { ROOT_PATH } from '../utils/constants' import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode, setFileDecorationSuccess } from './payload' -import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' +import { addInputField, createWorkspace, populateWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' const LOCALHOST = ' - connect to localhost - ' let plugin, dispatch: React.Dispatch @@ -13,8 +13,16 @@ let plugin, dispatch: React.Dispatch export const listenOnPluginEvents = (filePanelPlugin) => { plugin = filePanelPlugin - plugin.on('filePanel', 'createWorkspaceReducerEvent', (name: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void) => { - createWorkspace(name, workspaceTemplateName, null, isEmpty, cb) + plugin.on('templateSelection', 'createWorkspaceReducerEvent', (name: string, workspaceTemplateName: WorkspaceTemplate, opts: any, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean) => { + createWorkspace(name, workspaceTemplateName, opts, isEmpty, cb, isGitRepo) + }) + + plugin.on('templateSelection', 'addTemplateToWorkspaceReducerEvent', (workspaceTemplateName: WorkspaceTemplate, opts: any, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void) => { + populateWorkspace(workspaceTemplateName, opts, isEmpty, cb) + }) + + plugin.on('filePanel', 'createWorkspaceReducerEvent', (name: string, workspaceTemplateName: WorkspaceTemplate, isEmpty = false, cb: (err: Error, result?: string | number | boolean | Record) => void, isGitRepo: boolean) => { + createWorkspace(name, workspaceTemplateName, null, isEmpty, cb, isGitRepo) }) plugin.on('filePanel', 'renameWorkspaceReducerEvent', (oldName: string, workspaceName: string, cb: (err: Error, result?: string | number | boolean | Record) => void) => { diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index d8a8b7060e1..5b82e6306aa 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -1,6 +1,7 @@ import React from 'react' import { bytesToHex } from '@ethereumjs/util' import { hash } from '@remix-project/remix-lib' +import { createNonClashingNameAsync } from '@remix-ui/helper' import { TEMPLATE_METADATA, TEMPLATE_NAMES } from '../utils/constants' import { TemplateType } from '../types' import IpfsHttpClient from 'ipfs-http-client' @@ -154,6 +155,7 @@ export const createWorkspace = async ( await plugin.workspaceCreated(workspaceName) if (isGitRepo && createCommit) { + console.log('CREATE COMMIT') const name = await plugin.call('settings', 'get', 'settings/github-user-name') const email = await plugin.call('settings', 'get', 'settings/github-email') const currentBranch: branch = await dgitPlugin.call('dgitApi', 'currentbranch') @@ -191,25 +193,7 @@ export const createWorkspace = async ( } } } - if (metadata && metadata.type === 'plugin') { - plugin.call('notification', 'toast', 'Please wait while the workspace is being populated with the template.') - dispatch(cloneRepositoryRequest()) - setTimeout(() => { - plugin.call(metadata.name, metadata.endpoint, ...metadata.params).then(() => { - dispatch(cloneRepositorySuccess()) - }).catch((e) => { - dispatch(cloneRepositorySuccess()) - plugin.call('notification', 'toast', 'error adding template ' + e.message || e) - }) - }, 5000) - } else if (!isEmpty && !(isGitRepo && createCommit)) await loadWorkspacePreset(workspaceTemplateName, opts) - cb && cb(null, workspaceName) - - if (workspaceTemplateName === 'semaphore' || workspaceTemplateName === 'hashchecker' || workspaceTemplateName === 'rln') { - const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') - if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler') - _paq.push(['trackEvent', 'circuit-compiler', 'template', 'create', workspaceTemplateName]) - } + await populateWorkspace(workspaceTemplateName, opts, isEmpty, (err: Error) => { cb && cb(err, workspaceName) }, isGitRepo, createCommit) // 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 await plugin.setWorkspaces(await getWorkspaces()) }).catch((error) => { @@ -219,6 +203,40 @@ export const createWorkspace = async ( return promise } +export const populateWorkspace = async ( + workspaceTemplateName: WorkspaceTemplate, + opts = null, + isEmpty = false, + cb?: (err: Error, result?: string | number | boolean | Record) => void, + isGitRepo: boolean = false, + createCommit: boolean = false +) => { + const metadata = TEMPLATE_METADATA[workspaceTemplateName] + if (metadata && metadata.type === 'plugin') { + plugin.call('notification', 'toast', 'Please wait while the workspace is being populated with the template.') + dispatch(cloneRepositoryRequest()) + setTimeout(() => { + plugin.call(metadata.name, metadata.endpoint, ...metadata.params).then(() => { + dispatch(cloneRepositorySuccess()) + }).catch((e) => { + dispatch(cloneRepositorySuccess()) + plugin.call('notification', 'toast', 'error adding template ' + e.message || e) + }) + }, 5000) + } else if (!isEmpty && !(isGitRepo && createCommit)) await loadWorkspacePreset(workspaceTemplateName, opts) + cb && cb(null) + if (isGitRepo) { + await checkGit() + const isActive = await plugin.call('manager', 'isActive', 'dgit') + if (!isActive) await plugin.call('manager', 'activatePlugin', 'dgit') + } + if (workspaceTemplateName === 'semaphore' || workspaceTemplateName === 'hashchecker' || workspaceTemplateName === 'rln') { + const isCircomActive = await plugin.call('manager', 'isActive', 'circuit-compiler') + if (!isCircomActive) await plugin.call('manager', 'activatePlugin', 'circuit-compiler') + _paq.push(['trackEvent', 'circuit-compiler', 'template', 'create', workspaceTemplateName]) + } +} + export const createWorkspaceTemplate = async (workspaceName: string, template: WorkspaceTemplate = 'remixDefault', metadata?: TemplateType) => { if (!workspaceName) throw new Error('workspace name cannot be empty') if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed') @@ -388,12 +406,14 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe try { const templateList = Object.keys(templateWithContent) if (!templateList.includes(template)) break + _paq.push(['trackEvent', 'workspace', 'template', template]) // @ts-ignore - const files = await templateWithContent[template](opts) + const files = await templateWithContent[template](opts, plugin) for (const file in files) { try { - await workspaceProvider.set(file, files[file]) + const uniqueFileName = await createNonClashingNameAsync(file, plugin.fileManager) + await workspaceProvider.set(uniqueFileName, files[file]) } catch (error) { console.error(error) } diff --git a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx index a08dee8028f..04072a8f1a7 100644 --- a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx +++ b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx @@ -163,47 +163,6 @@ export function HamburgerMenu(props: HamburgerMenuProps) { }} platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} > - - { - props.addHelperScripts('etherscanScripts') - props.hideIconsMenu(!showIconsMenu) - }} - platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} - > - { - props.addHelperScripts('contractDeployerScripts') - props.hideIconsMenu(!showIconsMenu) - }} - platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} - > - { - props.addHelperScripts('sindriScripts') - props.hideIconsMenu(!showIconsMenu) - }} - platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} - > - { - props.addHelperScripts('contractCreate2Factory') - props.hideIconsMenu(!showIconsMenu) - }} - platforms={[appPlatformTypes.web, appPlatformTypes.desktop]} - > ) } 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 5b924b96976..2bcc1b2de76 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -42,11 +42,8 @@ export function Workspace() { const uupsRadioRef = useRef() const global = useContext(FileSystemContext) const workspaceRenameInput = useRef() - const workspaceCreateInput = useRef() - const workspaceCreateTemplateInput = useRef() const intl = useIntl() const cloneUrlRef = useRef() - const initGitRepoRef = useRef() const filteredBranches = selectedWorkspace ? (selectedWorkspace.branches || []).filter((branch) => branch.name.includes(branchFilter) && branch.name !== 'HEAD').slice(0, 20) : [] const currentBranch = selectedWorkspace ? selectedWorkspace.currentBranch : null @@ -321,14 +318,9 @@ export function Workspace() { intl.formatMessage({ id: 'filePanel.cancel' }) ) } - const createWorkspace = () => { - global.modal( - intl.formatMessage({ id: (platform !== appPlatformTypes.desktop)? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), - createModalMessage(), - intl.formatMessage({ id: (platform !== appPlatformTypes.desktop)? 'filePanel.ok':'filePanel.selectFolder' }), - onFinishCreateWorkspace, - intl.formatMessage({ id: 'filePanel.cancel' }) - ) + const createWorkspace = async () => { + await global.plugin.call('manager', 'activatePlugin', 'templateSelection') + await global.plugin.call('tabs', 'focus', 'templateSelection') } const deleteCurrentWorkspace = () => { @@ -448,38 +440,6 @@ export function Workspace() { } } - const onFinishCreateWorkspace = async () => { - if (workspaceCreateInput.current === undefined) return - // @ts-ignore: Object is possibly 'null'. - const workspaceName = workspaceCreateInput.current.value - // @ts-ignore: Object is possibly 'null'. - const workspaceTemplateName = workspaceCreateTemplateInput.current.value || 'remixDefault' - const initGitRepo = initGitRepoRef.current.checked - const opts = { - // @ts-ignore: Object is possibly 'null'. - mintable: mintableCheckboxRef.current.checked, - // @ts-ignore: Object is possibly 'null'. - burnable: burnableCheckboxRef.current.checked, - // @ts-ignore: Object is possibly 'null'. - pausable: pausableCheckboxRef.current.checked, - // @ts-ignore: Object is possibly 'null'. - upgradeable: transparentRadioRef.current.checked ? transparentRadioRef.current.value : uupsRadioRef.current.checked ? uupsRadioRef.current.value : false - } - - try { - await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName, opts, initGitRepo) - } catch (e) { - global.modal( - intl.formatMessage({ id: (platform !== appPlatformTypes.desktop)? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), - e.message, - intl.formatMessage({ id: 'filePanel.ok' }), - () => {}, - intl.formatMessage({ id: 'filePanel.cancel' }) - ) - console.error(e) - } - } - const onFinishDeleteWorkspace = async () => { try { await global.dispatchDeleteWorkspace(global.fs.browser.currentWorkspace) @@ -868,163 +828,6 @@ export function Workspace() { } } - const createModalMessage = () => { - return ( - <> - - -
- - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - - -
- { }} - /> - -
- {!global.fs.gitConfig.username || !global.fs.gitConfig.email ? ( -
- -
- ) : ( - <> - )} - - ) - } - const renameModalMessage = (workspaceName?: string) => { return (
diff --git a/libs/remix-ui/workspace/src/lib/utils/constants.ts b/libs/remix-ui/workspace/src/lib/utils/constants.ts index 823bcabdc3e..9257c6a3811 100644 --- a/libs/remix-ui/workspace/src/lib/utils/constants.ts +++ b/libs/remix-ui/workspace/src/lib/utils/constants.ts @@ -110,3 +110,21 @@ export const TEMPLATE_METADATA: Record = { } } +export type TemplateOption = { + mintable?: boolean + burnable?: boolean + pausable?: boolean + upgradeable?: 'uups' | 'transparent' +} + +export type Template = { + value: string + displayName: string + opts?: TemplateOption +} + +export type TemplateGroup = { + name: string + items: Array