Merge branch 'resolve_with_package' of https://github.com/ethereum/remix-project into resolve_with_package

pull/5370/head
filip mertens 2 years ago
commit 5026e9a6e2
  1. 12
      apps/remix-ide/src/app/files/fileManager.ts
  2. 7
      apps/remix-ide/src/app/plugins/permission-handler-plugin.tsx
  3. 3
      apps/remix-ide/src/app/tabs/locales/en/debugger.json
  4. 16
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  5. 4
      apps/remix-ide/src/app/tabs/locales/en/index.js
  6. 13
      apps/remix-ide/src/app/tabs/locales/en/permissionHandler.json
  7. 35
      apps/remix-ide/src/app/tabs/locales/en/solidityUnitTesting.json
  8. 2
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  9. 41
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  10. 3
      apps/remix-ide/src/app/tabs/locales/zh/debugger.json
  11. 28
      apps/remix-ide/src/app/tabs/locales/zh/filePanel.json
  12. 13
      apps/remix-ide/src/app/tabs/locales/zh/home.json
  13. 4
      apps/remix-ide/src/app/tabs/locales/zh/index.js
  14. 13
      apps/remix-ide/src/app/tabs/locales/zh/permissionHandler.json
  15. 35
      apps/remix-ide/src/app/tabs/locales/zh/solidityUnitTesting.json
  16. 2
      apps/remix-ide/src/app/tabs/locales/zh/terminal.json
  17. 41
      apps/remix-ide/src/app/tabs/locales/zh/udapp.json
  18. 10
      apps/remix-ide/src/blockchain/blockchain.js
  19. 16
      libs/remix-lib/src/execution/txRunner.ts
  20. 3
      libs/remix-lib/src/execution/txRunnerVM.ts
  21. 3
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  22. 6
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  23. 10
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  24. 6
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  25. 18
      libs/remix-ui/permission-handler/src/lib/permission-dialog.tsx
  26. 39
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  27. 24
      libs/remix-ui/run-tab/src/lib/components/contractGUI.tsx
  28. 26
      libs/remix-ui/run-tab/src/lib/components/recorderCardUI.tsx
  29. 71
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  30. 3
      libs/remix-ui/terminal/src/lib/components/RenderKnownTransactions.tsx
  31. 3
      libs/remix-ui/terminal/src/lib/components/RenderUnknownTransactions.tsx
  32. 4
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  33. 9
      libs/remix-ui/workspace/src/lib/actions/index.ts
  34. 6
      libs/remix-ui/workspace/src/lib/components/file-explorer-context-menu.tsx
  35. 9
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  36. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  37. 7
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  38. 50
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  39. 2
      libs/remix-ui/workspace/src/lib/types/index.ts
  40. 6
      libs/remix-ui/workspace/src/lib/utils/index.ts

@ -1,4 +1,5 @@
'use strict' 'use strict'
import { saveAs } from 'file-saver'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
@ -343,6 +344,17 @@ class FileManager extends Plugin {
} }
} }
async download(path) {
try {
const fileName = helper.extractNameFromKey(path)
path = this.normalize(path)
const content: any = await this.readFile(path)
saveAs(new Blob([content]), fileName)
} catch (e) {
throw new Error(e)
}
}
/** /**
* Create a directory * Create a directory
* @param {string} path path of the new directory * @param {string} path path of the new directory

@ -1,4 +1,5 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { AppModal } from '@remix-ui/app' import { AppModal } from '@remix-ui/app'
import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permission-handler' import { PermissionHandlerDialog, PermissionHandlerValue } from '@remix-ui/permission-handler'
@ -103,10 +104,10 @@ export class PermissionHandlerPlugin extends Plugin {
} }
const modal: AppModal = { const modal: AppModal = {
id: 'PermissionHandler', id: 'PermissionHandler',
title: `Permission needed for ${to.displayName || to.name}`, title: <FormattedMessage id='permissionHandler.permissionNeededFor' values={{ to: to.displayName || to.name }} />,
message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>, message: <PermissionHandlerDialog plugin={this} theme={await this.getTheme()} value={value}></PermissionHandlerDialog>,
okLabel: 'Accept', okLabel: <FormattedMessage id='permissionHandler.accept' />,
cancelLabel: 'Decline' cancelLabel: <FormattedMessage id='permissionHandler.decline' />
} }
const result = await this.call('notification', 'modal', modal) const result = await this.call('notification', 'modal', modal)

@ -4,5 +4,8 @@
"debugger.stopDebugging": "Stop debugging", "debugger.stopDebugging": "Stop debugging",
"debugger.startDebugging": "Start debugging", "debugger.startDebugging": "Start debugging",
"debugger.placeholder": "Transaction hash, should start with 0x", "debugger.placeholder": "Transaction hash, should start with 0x",
"debugger.debugLocaNodeLabel": "Force using local node",
"debugger.useGeneratedSources": "Use generated sources",
"debugger.debugWithGeneratedSources": "Debug with generated sources",
"debugger.introduction": "When Debugging with a transaction hash, if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings. For supported networks, please see" "debugger.introduction": "When Debugging with a transaction hash, if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings. For supported networks, please see"
} }

@ -14,7 +14,9 @@
"filePanel.workspace.download": "Download Workspace", "filePanel.workspace.download": "Download Workspace",
"filePanel.workspace.restore": "Restore Workspace Backup", "filePanel.workspace.restore": "Restore Workspace Backup",
"filePanel.workspace.clone": "Clone Git Repository", "filePanel.workspace.clone": "Clone Git Repository",
"filePanel.workspace.cloneMessage": "Please provide a valid git repository url.",
"filePanel.workspace.enterGitUrl": "Enter git repository url", "filePanel.workspace.enterGitUrl": "Enter git repository url",
"filePanel.workspace.switch": "Switch To Workspace",
"filePanel.workspace.solghaction": "Adds a preset yml file to run solidity unit tests on github actions CI.", "filePanel.workspace.solghaction": "Adds a preset yml file to run solidity unit tests on github actions CI.",
"filePanel.solghaction": "Solidity Test Workflow", "filePanel.solghaction": "Solidity Test Workflow",
"filePanel.workspace.tssoltestghaction": "Adds a preset yml file to run mocha and chai tests for solidity on github actions CI", "filePanel.workspace.tssoltestghaction": "Adds a preset yml file to run mocha and chai tests for solidity on github actions CI",
@ -38,5 +40,17 @@
"filePanel.createNewFolder": "Create New Folder", "filePanel.createNewFolder": "Create New Folder",
"filePanel.publishToGist": "Publish all the current workspace files to a github gist", "filePanel.publishToGist": "Publish all the current workspace files to a github gist",
"filePanel.uploadFile": "Load a local file into current workspace", "filePanel.uploadFile": "Load a local file into current workspace",
"filePanel.updateGist": "Update the current [gist] explorer" "filePanel.updateGist": "Update the current [gist] explorer",
"filePanel.viewAllBranches": "View all branches",
"filePanel.createBranch": "Create branch",
"filePanel.switchBranches": "Switch branches",
"filePanel.checkoutGitBranch": "Checkout Git Branch",
"filePanel.findOrCreateABranch": "Find or create a branch.",
"filePanel.initGitRepositoryLabel": "Initialize workspace as a new git repository",
"filePanel.initGitRepositoryWarning": "Please add username and email to Remix GitHub Settings to use git features.",
"filePanel.workspaceName": "Workspace name",
"filePanel.customizeTemplate": "Customize template",
"filePanel.features": "Features",
"filePanel.upgradeability": "Upgradeability",
"filePanel.ok": "OK"
} }

@ -8,6 +8,8 @@ import settingsJson from './settings.json';
import solidityJson from './solidity.json'; import solidityJson from './solidity.json';
import terminalJson from './terminal.json'; import terminalJson from './terminal.json';
import udappJson from './udapp.json'; import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
export default { export default {
...debuggerJson, ...debuggerJson,
@ -20,4 +22,6 @@ export default {
...solidityJson, ...solidityJson,
...terminalJson, ...terminalJson,
...udappJson, ...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
} }

@ -0,0 +1,13 @@
{
"permissionHandler.allPermissionsReset": "All permisssions have been reset.",
"permissionHandler.rememberText": "has changed and",
"permissionHandler.permissionHandlerMessage": "\"{from}\" {rememberText} would like to access to \"{method}\" of \"{to}\"`",
"permissionHandler.description": "Description",
"permissionHandler.noDescriptionProvided": "No description Provided",
"permissionHandler.makeSureYouTrustThisPlugin": "Make sure you trust this plugin before processing this call.",
"permissionHandler.rememberThisChoice": "Remember this choice",
"permissionHandler.resetAllPermissions": "Reset all Permissions",
"permissionHandler.permissionNeededFor": "Permission needed for {to}",
"permissionHandler.accept": "Accept",
"permissionHandler.decline": "Decline"
}

@ -0,0 +1,35 @@
{
"solidityUnitTesting.displayName": "Solidity unit testing",
"solidityUnitTesting.testDirectory": "Test directory",
"solidityUnitTesting.testYourSmartContract": "Test your smart contract in Solidity.",
"solidityUnitTesting.selectDirectory": "Select directory to load and generate test files.",
"solidityUnitTesting.uiPathInputTooltip": "Press 'Enter' to change the path for test files.",
"solidityUnitTesting.uiPathInputButtonTooltip": "Create a test folder",
"solidityUnitTesting.create": "Create",
"solidityUnitTesting.generateTestsButtonTooltip": "Generate a sample test file",
"solidityUnitTesting.generate": "Generate",
"solidityUnitTesting.generateTestsLinkTooltip": "Check out documentation.",
"solidityUnitTesting.howToUse": "How to use...",
"solidityUnitTesting.runButtonTitle1": "Run tests",
"solidityUnitTesting.runButtonTitle2": "Please select Solidity compiler version greater than 0.4.12.",
"solidityUnitTesting.runButtonTitle3": "No solidity file selected",
"solidityUnitTesting.runButtonTitle4": "The \"Solidity Plugin\" should be activated",
"solidityUnitTesting.runButtonTitle5": "No test file selected",
"solidityUnitTesting.stopButtonLabel1": "Stop",
"solidityUnitTesting.stopButtonLabel2": "Stopping",
"solidityUnitTesting.run": "Run",
"solidityUnitTesting.runTestsTabStopActionTooltip": "Stop running tests",
"solidityUnitTesting.selectAll": "Select all",
"solidityUnitTesting.testTabTestsExecutionStopped": "The test execution has been stopped",
"solidityUnitTesting.testTabTestsExecutionStoppedError": "The test execution has been stopped because of error(s) in your test file",
"solidityUnitTesting.progress": "Progress: {readyTestsNumber} finished (of {runningTestsNumber})",
"solidityUnitTesting.resultFor": "Result for",
"solidityUnitTesting.passed": "Passed",
"solidityUnitTesting.failed": "Failed",
"solidityUnitTesting.timeTaken": "Time Taken",
"solidityUnitTesting.errorMessage": "Error Message",
"solidityUnitTesting.assertion": "Assertion",
"solidityUnitTesting.expectedValueShouldBe": "Expected value should be",
"solidityUnitTesting.receivedValue": "Received value",
"solidityUnitTesting.skippingTheRemainingTests": "Skipping the remaining tests of the function."
}

@ -1,7 +1,9 @@
{ {
"terminal.listen": "listen on all transactions", "terminal.listen": "listen on all transactions",
"terminal.listenTitle": "If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you",
"terminal.search": "Search with transaction hash or address", "terminal.search": "Search with transaction hash or address",
"terminal.used": "used", "terminal.used": "used",
"terminal.debug": "Debug",
"terminal.welcomeText1": "Welcome to", "terminal.welcomeText1": "Welcome to",
"terminal.welcomeText2": "Your files are stored in", "terminal.welcomeText2": "Your files are stored in",
"terminal.welcomeText3": "You can use this terminal to", "terminal.welcomeText3": "You can use this terminal to",

@ -4,6 +4,9 @@
"udapp.account": "Account", "udapp.account": "Account",
"udapp.value": "Value", "udapp.value": "Value",
"udapp.contract": "Contract", "udapp.contract": "Contract",
"udapp.compiledBy": "Compiled by {compilerName}",
"udapp.infoSyncCompiledContractTooltip": "Click here to import contracts compiled from an external framework.{br}This action is enabled when Remix is connected to an external{br} framework (hardhat, truffle, foundry) through remixd.",
"udapp.remixIpfsUdappTooltip": "Publishing the source code and metadata to IPFS facilitates{br} source code verification using Sourcify and will greatly foster{br} contract adoption (auditing, debugging, calling it, etc...)",
"udapp.signAMessage": "Sign a message", "udapp.signAMessage": "Sign a message",
"udapp.enterAMessageToSign": "Enter a message to sign", "udapp.enterAMessageToSign": "Enter a message to sign",
"udapp.hash": "hash", "udapp.hash": "hash",
@ -15,10 +18,46 @@
"udapp.publishTo": "Publish to", "udapp.publishTo": "Publish to",
"udapp.or": "or", "udapp.or": "or",
"udapp.atAddress": "At Address", "udapp.atAddress": "At Address",
"udapp.atAddressOptionsTitle1": "address of contract",
"udapp.atAddressOptionsTitle2": "Interact with the deployed contract - requires the .abi file or {br} compiled .sol file to be selected in the editor {br}(with the same compiler configuration)",
"udapp.atAddressOptionsTitle3": "Compile a *.sol file or select a *.abi file.",
"udapp.atAddressOptionsTitle4": "To interact with a deployed contract, either{br} enter its address and compile its source *.sol file {br}(with the same compiler settings) or select its .abi file in the editor. ",
"udapp.contractOptionsTitle1": "Please compile *.sol file to deploy or access a contract",
"udapp.contractOptionsTitle2": "Select a compiled contract to deploy or to use with At Address.",
"udapp.contractOptionsTitle3": "Select and compile *.sol file to deploy or access a contract.",
"udapp.contractOptionsTitle4": "When there is a compiled .sol file, choose the {br} contract to deploy or to use with AtAddress.'",
"udapp.checkSumWarning": "It seems you are not using a checksumed address.{br}A checksummed address is an address that contains uppercase letters, as specified in {a}.{br}Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.",
"udapp.isOverSizePrompt": "Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. {br}More info: {a}",
"udapp.thisContractMayBeAbstract": "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.",
"udapp.noCompiledContracts": "No compiled contracts", "udapp.noCompiledContracts": "No compiled contracts",
"udapp.addressOfContract": "Address of contract",
"udapp.loadContractFromAddress": "Load contract from Address", "udapp.loadContractFromAddress": "Load contract from Address",
"udapp.deployedContracts": "Deployed Contracts", "udapp.deployedContracts": "Deployed Contracts",
"udapp.deployAndRunClearInstances": "Clear instances list and reset recorder", "udapp.deployAndRunClearInstances": "Clear instances list and reset recorder",
"udapp.deployAndRunNoInstanceText": "Currently you have no contract instances to interact with.", "udapp.deployAndRunNoInstanceText": "Currently you have no contract instances to interact with.",
"udapp.transactionsRecorded": "Transactions recorded" "udapp.transactionsRecorded": "Transactions recorded",
"udapp.transactionsCountTooltip": "The number of recorded transactions",
"udapp.transactionSaveTooltip1": "No transactions to save",
"udapp.transactionSaveTooltip2": "Save {count} transaction as scenario file",
"udapp.transactionSaveTooltip3": "Save {count} transactions as scenario file",
"udapp.infoRecorderTooltip": "Save transactions (deployed contracts and function executions) {br}and replay them in another environment e.g Transactions created {br}in Remix VM can be replayed in the Injected Provider.",
"udapp.livemodeRecorderTooltip": "If contracts are updated after recording transactions,{br} checking this box will run recorded transactions {br}with the latest copy of the compiled contracts",
"udapp.livemodeRecorderLabel": "Run transactions using the latest compilation result",
"udapp.runRecorderTooltip": "Run transaction(s) from the current scenario file",
"udapp.save": "Save",
"udapp.run": "Run",
"udapp.ok": "OK",
"udapp.alert": "Alert",
"udapp.proceed": "Proceed",
"udapp.cancel": "Cancel",
"udapp.parameters": "Parameters",
"udapp.copyParameters": "Copy encoded input parameters to clipboard",
"udapp.copyCalldata": "Copy calldata to clipboard",
"udapp.deployWithProxy": "Deploy with Proxy",
"udapp.upgradeWithProxy": "Upgrade with Proxy",
"udapp.useLastDeployedERC1967Contract": "Use last deployed ERC1967 contract",
"udapp.proxyAddressLabel": "Proxy Address",
"udapp.proxyAddressPlaceholder": "proxy address",
"udapp.proxyAddressInputTooltip": "Enter previously deployed proxy address on the selected network",
"udapp.proxyAddressTooltip": "Select this option to use the last deployed ERC1967 contract on the current network."
} }

@ -4,5 +4,8 @@
"debugger.stopDebugging": "停止调试", "debugger.stopDebugging": "停止调试",
"debugger.startDebugging": "开始调试", "debugger.startDebugging": "开始调试",
"debugger.placeholder": "交易哈希, 应该以 0x 开头", "debugger.placeholder": "交易哈希, 应该以 0x 开头",
"debugger.debugLocaNodeLabel": "强制使用本地节点",
"debugger.useGeneratedSources": "使用生成的源",
"debugger.debugWithGeneratedSources": "使用生成的源进行调试",
"debugger.introduction": "当使用交易哈希调试时, 如果该合约已被验证, Remix 会试图从 Sourcify 或 Etherscan 获取源码. 在 Remix 中设置您的 Etherscan API key. 有关受支持的网络,请参阅" "debugger.introduction": "当使用交易哈希调试时, 如果该合约已被验证, Remix 会试图从 Sourcify 或 Etherscan 获取源码. 在 Remix 中设置您的 Etherscan API key. 有关受支持的网络,请参阅"
} }

@ -14,13 +14,15 @@
"filePanel.workspace.download": "下载工作空间", "filePanel.workspace.download": "下载工作空间",
"filePanel.workspace.restore": "恢复工作空间", "filePanel.workspace.restore": "恢复工作空间",
"filePanel.workspace.clone": "克隆 Git 仓库", "filePanel.workspace.clone": "克隆 Git 仓库",
"filePanel.workspace.cloneMessage": "请提供有效的 git 仓库 url。",
"filePanel.workspace.enterGitUrl": "输入 Git 仓库地址", "filePanel.workspace.enterGitUrl": "输入 Git 仓库地址",
"filePanel.workspace.solghaction": "添加预设 yml 文件以在 github 操作 CI 上运行 solidity 单元测试。", "filePanel.workspace.switch": "切换工作空间",
"filePanel.solghaction": "Solidity Test Workflow", "filePanel.workspace.solghaction": "添加预设的 yml 文件,这样就可以在 github actions CI 上运行 solidity 单元测试。",
"filePanel.workspace.tssoltestghaction": "添加一个预设的 yml 文件以在 github 操作 CI 上运行 mocha 和 chai 测试以确保可靠性", "filePanel.solghaction": "Solidity 测试工作流",
"filePanel.tssoltestghaction": "Mocha Chai Test Workflow", "filePanel.workspace.tssoltestghaction": "添加一个预设的 yml 文件,这样就可以在 github actions CI 上为 solidity 运行 mocha 和 chai 测试",
"filePanel.workspace.slitherghaction": "添加一个预设的 yml 文件以在 github actions CI 上运行 slither 分析", "filePanel.tssoltestghaction": "Mocha Chai 测试工作流",
"filePanel.slitherghaction": "Slither Workflow", "filePanel.workspace.slitherghaction": "添加一个预设的 yml 文件,这样就可以在 github actions CI 上运行 slither 分析",
"filePanel.slitherghaction": "Slither 工作流",
"filePanel.newFile": "新建文件", "filePanel.newFile": "新建文件",
"filePanel.newFolder": "新建文件夹", "filePanel.newFolder": "新建文件夹",
"filePanel.rename": "重命名", "filePanel.rename": "重命名",
@ -38,5 +40,17 @@
"filePanel.createNewFolder": "新建文件夹", "filePanel.createNewFolder": "新建文件夹",
"filePanel.publishToGist": "将当前工作空间下所有文件发布到github gist", "filePanel.publishToGist": "将当前工作空间下所有文件发布到github gist",
"filePanel.uploadFile": "加载本地文件到当前工作空间", "filePanel.uploadFile": "加载本地文件到当前工作空间",
"filePanel.updateGist": "更新当前 [gist] 浏览" "filePanel.updateGist": "更新当前 [gist] 浏览",
"filePanel.viewAllBranches": "查看所有分支",
"filePanel.createBranch": "创建分支",
"filePanel.switchBranches": "切换分支",
"filePanel.checkoutGitBranch": "切换 Git 分支",
"filePanel.findOrCreateABranch": "查找或创建一个分支。",
"filePanel.initGitRepositoryLabel": "初始化工作空间为一个新的 git 仓库",
"filePanel.initGitRepositoryWarning": "请将用户名和电子邮件添加到 Remix GitHub 设置以使用 git 功能。",
"filePanel.workspaceName": "工作空间名称",
"filePanel.customizeTemplate": "自定义模板",
"filePanel.features": "特点",
"filePanel.upgradeability": "可升级性",
"filePanel.ok": "确认"
} }

@ -9,9 +9,10 @@
"home.featured": "精选", "home.featured": "精选",
"home.jumpIntoWeb3": "迎接 WEB3", "home.jumpIntoWeb3": "迎接 WEB3",
"home.jumpIntoWeb3Text": "Remix 项目是一个丰富的工具集,任何知识水平的用户都可以在这上面进行全周期的合约开发,并且可作为以太坊教学和实验的学习实验室。", "home.jumpIntoWeb3Text": "Remix 项目是一个丰富的工具集,任何知识水平的用户都可以在这上面进行全周期的合约开发,并且可作为以太坊教学和实验的学习实验室。",
"home.remixYouTube": "", "home.remixYouTube": "观看学习",
"home.remixYouTubeText1": "", "home.remixYouTubeText1": "来自 Remix 团队的视频小贴士",
"home.remixYouTubeText2": "", "home.remixYouTubeMore": "观看",
"home.remixYouTubeText2": "Remix 有一个不断更新的视频库,其中包含大量的使用技巧。查看并订阅以获取我们最新上传的视频。",
"home.betaTesting": "BETA 测试", "home.betaTesting": "BETA 测试",
"home.betaTestingText1": "我们的社区支持我们", "home.betaTestingText1": "我们的社区支持我们",
"home.betaTestingText2": "每次 Remix IDE 发布版本之前,你都可以参与 Beta 测试。现在就来帮我们测试并且尝鲜新功能吧!", "home.betaTestingText2": "每次 Remix IDE 发布版本之前,你都可以参与 Beta 测试。现在就来帮我们测试并且尝鲜新功能吧!",
@ -21,15 +22,15 @@
"home.solhintPluginDesc": "Solhint 是一个用于检查 Solidity 代码的开源项目", "home.solhintPluginDesc": "Solhint 是一个用于检查 Solidity 代码的开源项目",
"home.sourcifyPluginDesc": "Solidity 合约和元数据验证服务。", "home.sourcifyPluginDesc": "Solidity 合约和元数据验证服务。",
"home.unitTestPluginDesc": "在 Solidity 中为你的合约编写和运行单元测试。", "home.unitTestPluginDesc": "在 Solidity 中为你的合约编写和运行单元测试。",
"home.dgitPluginDesc": "", "home.dgitPluginDesc": "为你的项目添加源码控制。",
"home.getStarted": "开始使用", "home.getStarted": "开始使用",
"home.projectTemplates": "项目模板", "home.projectTemplates": "项目模板",
"home.blankTemplateDesc": "创建一个空的工作空间。", "home.blankTemplateDesc": "创建一个空的工作空间。",
"home.remixDefaultTemplateDesc": "创建一个带有样本文件的工作空间", "home.remixDefaultTemplateDesc": "创建一个带有样本文件的工作空间",
"home.ozerc20TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 ERC20 代币。", "home.ozerc20TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 ERC20 代币。",
"home.ozerc721TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 NFT 代币。", "home.ozerc721TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 NFT 代币。",
"home.ozerc1155TemplateDesc": "", "home.ozerc1155TemplateDesc": "通过导入 OpenZeppelin 库来创建一个 ERC1155 代币。",
"home.gnosisSafeMultisigTemplateDesc": "", "home.gnosisSafeMultisigTemplateDesc": "使用此模板创建多重签名钱包。",
"home.zeroxErc20TemplateDesc": "通过引入 0xProject 合约来创建一个 ERC20 代币。", "home.zeroxErc20TemplateDesc": "通过引入 0xProject 合约来创建一个 ERC20 代币。",
"home.learn": "学习", "home.learn": "学习",
"home.remixBasics": "Remix 基础", "home.remixBasics": "Remix 基础",

@ -8,6 +8,8 @@ import settingsJson from './settings.json';
import solidityJson from './solidity.json'; import solidityJson from './solidity.json';
import terminalJson from './terminal.json'; import terminalJson from './terminal.json';
import udappJson from './udapp.json'; import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json';
import enJson from '../en'; import enJson from '../en';
// There may have some un-translated content. Always fill in the gaps with EN JSON. // There may have some un-translated content. Always fill in the gaps with EN JSON.
@ -23,4 +25,6 @@ export default Object.assign({}, enJson, {
...solidityJson, ...solidityJson,
...terminalJson, ...terminalJson,
...udappJson, ...udappJson,
...solidityUnitTestingJson,
...permissionHandlerJson,
}) })

@ -0,0 +1,13 @@
{
"permissionHandler.allPermissionsReset": "已重置所有权限。",
"permissionHandler.rememberText": "已变更且",
"permissionHandler.permissionHandlerMessage": "\"{from}\" {rememberText}要访问 \"{to}\" 的 \"{method}\"",
"permissionHandler.description": "描述",
"permissionHandler.noDescriptionProvided": "没有提供描述",
"permissionHandler.makeSureYouTrustThisPlugin": "在处理此调用之前,请确保您信任此插件。",
"permissionHandler.rememberThisChoice": "记住此选项",
"permissionHandler.resetAllPermissions": "重置所有权限",
"permissionHandler.permissionNeededFor": "需要 {to} 的权限",
"permissionHandler.accept": "接受",
"permissionHandler.decline": "拒绝"
}

@ -0,0 +1,35 @@
{
"solidityUnitTesting.displayName": "Solidity 单元测试",
"solidityUnitTesting.testDirectory": "测试目录",
"solidityUnitTesting.testYourSmartContract": "在 Solidity 中测试您的智能合约。",
"solidityUnitTesting.selectDirectory": "选择要加载和生成测试文件的目录。",
"solidityUnitTesting.uiPathInputTooltip": "按 \"Enter\" 键更改测试文件的路径。",
"solidityUnitTesting.uiPathInputButtonTooltip": "创建一个测试目录",
"solidityUnitTesting.create": "创建",
"solidityUnitTesting.generateTestsButtonTooltip": "生成示例测试文件",
"solidityUnitTesting.generate": "生成",
"solidityUnitTesting.generateTestsLinkTooltip": "查看文档。",
"solidityUnitTesting.howToUse": "如何使用...",
"solidityUnitTesting.runButtonTitle1": "运行测试",
"solidityUnitTesting.runButtonTitle2": "请选择大于 0.4.12 的 Solidity 编译器版本。",
"solidityUnitTesting.runButtonTitle3": "未选择 solidity 文件",
"solidityUnitTesting.runButtonTitle4": "应该激活 \"Solidity Plugin\"",
"solidityUnitTesting.runButtonTitle5": "未选择测试文件",
"solidityUnitTesting.stopButtonLabel1": "停止",
"solidityUnitTesting.stopButtonLabel2": "正在停止",
"solidityUnitTesting.run": "执行",
"solidityUnitTesting.runTestsTabStopActionTooltip": "停止运行测试",
"solidityUnitTesting.selectAll": "全选",
"solidityUnitTesting.testTabTestsExecutionStopped": "测试执行已停止",
"solidityUnitTesting.testTabTestsExecutionStoppedError": "由于测试文件中的错误,测试执行已停止",
"solidityUnitTesting.progress": "进度:{readyTestsNumber} 已完成(共 {runningTestsNumber})",
"solidityUnitTesting.resultFor": "测试结果",
"solidityUnitTesting.passed": "通过",
"solidityUnitTesting.failed": "失败",
"solidityUnitTesting.timeTaken": "耗时",
"solidityUnitTesting.errorMessage": "错误信息",
"solidityUnitTesting.assertion": "断言",
"solidityUnitTesting.expectedValueShouldBe": "期望值应该是",
"solidityUnitTesting.receivedValue": "实际值",
"solidityUnitTesting.skippingTheRemainingTests": "跳过该方法的其余测试。"
}

@ -1,7 +1,9 @@
{ {
"terminal.listen": "监听所有交易", "terminal.listen": "监听所有交易",
"terminal.listenTitle": "如果选中,Remix 将监听在当前环境中挖掘到的所有交易,而不仅仅是您创建的交易",
"terminal.search": "按交易哈希或地址搜索", "terminal.search": "按交易哈希或地址搜索",
"terminal.used": "已使用", "terminal.used": "已使用",
"terminal.debug": "调试",
"terminal.welcomeText1": "欢迎使用", "terminal.welcomeText1": "欢迎使用",
"terminal.welcomeText2": "您的文件储存在", "terminal.welcomeText2": "您的文件储存在",
"terminal.welcomeText3": "您可使用此终端", "terminal.welcomeText3": "您可使用此终端",

@ -4,6 +4,9 @@
"udapp.account": "账户", "udapp.account": "账户",
"udapp.value": "以太币数量", "udapp.value": "以太币数量",
"udapp.contract": "合约", "udapp.contract": "合约",
"udapp.compiledBy": "由 {compilerName} 编译",
"udapp.infoSyncCompiledContractTooltip": "单击此处导入从外部框架编译的合约。{br}当 Remix 通过 remixd 连接到外部{br}框架 (hardhat、truffle、foundry) 时启用此操作。",
"udapp.remixIpfsUdappTooltip": "将源代码和元数据发布到 IPFS 有助于{br}使用 Sourcify 验证源代码,并将极大地促进{br}合约采用(审计、调试、调用等)",
"udapp.signAMessage": "给一个消息签名", "udapp.signAMessage": "给一个消息签名",
"udapp.enterAMessageToSign": "输入一个需要签名的消息", "udapp.enterAMessageToSign": "输入一个需要签名的消息",
"udapp.hash": "哈希", "udapp.hash": "哈希",
@ -15,10 +18,46 @@
"udapp.publishTo": "发布到", "udapp.publishTo": "发布到",
"udapp.or": "或", "udapp.or": "或",
"udapp.atAddress": "At Address", "udapp.atAddress": "At Address",
"udapp.atAddressOptionsTitle1": "合约地址",
"udapp.atAddressOptionsTitle2": "与已部署的合约交互 - 需要在编辑器中选择 .abi 文件或{br}编译的 .sol 文件{br}(具有相同的编译器配置)",
"udapp.atAddressOptionsTitle3": "编译一个 *.sol 文件或选中一个 *.abi 文件。",
"udapp.atAddressOptionsTitle4": "要与已部署的合约进行交互,{br} 输入其地址并编译其源 *.sol 文件{br}(使用相同的编译器设置)或在编辑器中选择其 .abi 文件。",
"udapp.contractOptionsTitle1": "请编译 *.sol 文件以部署或访问合约",
"udapp.contractOptionsTitle2": "选择要部署或与 At Address 一起使用的已编译合约。",
"udapp.contractOptionsTitle3": "选择并编译 *.sol 文件以部署或访问合约。",
"udapp.contractOptionsTitle4": "当有编译的 .sol 文件时,选择 {br} 合约进行部署或与 AtAddress 一起使用。",
"udapp.checkSumWarning": "您似乎没有使用 checksumed address 。{br} checksumed address 是包含大写字母的地址,如 {a} 中所指定。{br} checksumed address 旨在帮助防止用户将交易发送到错误地址。",
"udapp.isOverSizePrompt": "合约创建初始化返回长度超过24576字节的数据。部署可能会失败。 {br}更多信息:{a}",
"udapp.thisContractMayBeAbstract": "这个合约可能是抽象的,它可能没有完全实现抽象父类的方法,或者它可能没有正确调用继承合约的构造函数。",
"udapp.noCompiledContracts": "没有已编译的合约", "udapp.noCompiledContracts": "没有已编译的合约",
"udapp.addressOfContract": "合约地址",
"udapp.loadContractFromAddress": "加载此地址的合约", "udapp.loadContractFromAddress": "加载此地址的合约",
"udapp.deployedContracts": "已部署的合约", "udapp.deployedContracts": "已部署的合约",
"udapp.deployAndRunClearInstances": "清空合约实例并重置交易记录", "udapp.deployAndRunClearInstances": "清空合约实例并重置交易记录",
"udapp.deployAndRunNoInstanceText": "当前您没有可交互的合约实例.", "udapp.deployAndRunNoInstanceText": "当前您没有可交互的合约实例.",
"udapp.transactionsRecorded": "已记录的交易" "udapp.transactionsRecorded": "已记录的交易",
"udapp.transactionsCountTooltip": "已记录的交易数",
"udapp.transactionSaveTooltip1": "没有可保存的交易",
"udapp.transactionSaveTooltip2": "将 {count} 笔交易保存到一个场景文件",
"udapp.transactionSaveTooltip3": "将 {count} 笔交易保存到一个场景文件",
"udapp.infoRecorderTooltip": "保存交易 (合约部署和方法执行) {br}然后在另一个环境中回放,比如在 Remix VM {br}创建的交易可以在 Injected Provider 中回放。",
"udapp.livemodeRecorderTooltip": "如果记录交易后合约有更新,{br}选中这个复选框就会以最新的{br}合约编译结果执行已记录的交易",
"udapp.livemodeRecorderLabel": "用最新的编译结果执行交易",
"udapp.runRecorderTooltip": "从当前场景文件中执行交易",
"udapp.save": "保存",
"udapp.run": "执行",
"udapp.ok": "确认",
"udapp.alert": "警告",
"udapp.proceed": "继续",
"udapp.cancel": "取消",
"udapp.parameters": "参数",
"udapp.copyParameters": "复制已编码的输入参数到粘贴板",
"udapp.copyCalldata": "复制 calldata 到粘贴板",
"udapp.deployWithProxy": "使用代理部署",
"udapp.upgradeWithProxy": "使用代理升级",
"udapp.useLastDeployedERC1967Contract": "使用最新的已部署的 ERC1967 合约",
"udapp.proxyAddressLabel": "代理地址",
"udapp.proxyAddressPlaceholder": "代理地址",
"udapp.proxyAddressInputTooltip": "输入先前在所选网络上部署的代理地址",
"udapp.proxyAddressTooltip": "选择此选项可使用当前网络上最后部署的 ERC1967 合约。"
} }

@ -13,7 +13,8 @@ import { execution, EventManager, helpers } from '@remix-project/remix-lib'
import { etherScanLink } from './helper' import { etherScanLink } from './helper'
import { logBuilder, cancelUpgradeMsg, cancelProxyMsg, addressToString } from "@remix-ui/helper" import { logBuilder, cancelUpgradeMsg, cancelProxyMsg, addressToString } from "@remix-ui/helper"
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers const { txResultHelper } = helpers
const { resultToRemixTx } = txResultHelper
const packageJson = require('../../../../package.json') const packageJson = require('../../../../package.json')
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = window._paq = window._paq || [] //eslint-disable-line
@ -575,8 +576,11 @@ export class Blockchain extends Plugin {
async (error, result) => { async (error, result) => {
if (error) return reject(error) if (error) return reject(error)
try { try {
const execResult = await this.web3().eth.getExecutionResultFromSimulator(result.transactionHash) if (this.executionContext.isVM()) {
resolve(resultToRemixTx(result, execResult)) const execResult = await this.web3().eth.getExecutionResultFromSimulator(result.transactionHash)
resolve(resultToRemixTx(result, execResult))
} else
resolve(resultToRemixTx(result))
} catch (e) { } catch (e) {
reject(e) reject(e)
} }

@ -1,6 +1,16 @@
'use strict' 'use strict'
import { EventManager } from '../eventManager' import { EventManager } from '../eventManager'
export type Transaction = {
from: string,
to: string,
value: string,
data: string,
gasLimit: number,
useCall: boolean,
timestamp?: number
}
export class TxRunner { export class TxRunner {
event event
runAsync runAsync
@ -19,11 +29,11 @@ export class TxRunner {
this.queusTxs = [] this.queusTxs = []
} }
rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) { rawRun (args: Transaction, confirmationCb, gasEstimationForceSend, promptCb, cb) {
run(this, args, args.timestamp || Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb) run(this, args, args.timestamp || Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb)
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { execute (args: Transaction, confirmationCb, gasEstimationForceSend, promptCb, callback) {
let data = args.data let data = args.data
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data
@ -32,7 +42,7 @@ export class TxRunner {
} }
} }
function run (self, tx, stamp, confirmationCb, gasEstimationForceSend = null, promptCb = null, callback = null) { function run (self, tx: Transaction, stamp, confirmationCb, gasEstimationForceSend = null, promptCb = null, callback = null) {
if (!self.runAsync && Object.keys(self.pendingTxs).length) { if (!self.runAsync && Object.keys(self.pendingTxs).length) {
return self.queusTxs.push({ tx, stamp, callback }) return self.queusTxs.push({ tx, stamp, callback })
} }

@ -6,6 +6,7 @@ import { BN, bufferToHex, Address } from 'ethereumjs-util'
import type { Account } from '@ethereumjs/util' import type { Account } from '@ethereumjs/util'
import { EventManager } from '../eventManager' import { EventManager } from '../eventManager'
import { LogsManager } from './logsManager' import { LogsManager } from './logsManager'
import type { Transaction as InternalTransaction } from './txRunner'
export type VMexecutionResult = { export type VMexecutionResult = {
result: RunTxResult, result: RunTxResult,
@ -52,7 +53,7 @@ export class TxRunnerVM {
this.nextNonceForCall = 0 this.nextNonceForCall = 0
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) { execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback: VMExecutionCallBack) {
let data = args.data let data = args.data
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data

@ -1,5 +1,6 @@
'use strict' 'use strict'
import { EventManager } from '../eventManager' import { EventManager } from '../eventManager'
import type { Transaction as InternalTransaction } from './txRunner'
import Web3 from 'web3' import Web3 from 'web3'
export class TxRunnerWeb3 { export class TxRunnerWeb3 {
@ -79,7 +80,7 @@ export class TxRunnerWeb3 {
} }
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { execute (args: InternalTransaction, confirmationCb, gasEstimationForceSend, promptCb, callback) {
let data = args.data let data = args.data
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data

@ -9,13 +9,13 @@ export interface AppModal {
id: string id: string
timestamp?: number timestamp?: number
hide?: boolean hide?: boolean
title: string title: string | JSX.Element
validationFn?: (value: string) => ValidationResult validationFn?: (value: string) => ValidationResult
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
message: string | JSX.Element message: string | JSX.Element
okLabel: string okLabel: string | JSX.Element
okFn?: (value?:any) => void okFn?: (value?:any) => void
cancelLabel: string cancelLabel: string | JSX.Element
cancelFn?: () => void, cancelFn?: () => void,
modalType?: ModalTypes, modalType?: ModalTypes,
defaultValue?: string defaultValue?: string

@ -41,8 +41,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
props.onReady({ props.onReady({
globalContext: () => { globalContext: () => {
return { return {
block: state.currentBlock, block: state.currentBlock,
tx: state.currentTransaction, tx: state.currentTransaction,
receipt: state.currentReceipt receipt: state.currentReceipt
} }
} }
@ -375,7 +375,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } } return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
}) })
}} type="checkbox" /> }} type="checkbox" />
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (Solidity {'>='} v0.7.2)</label> <label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput"><FormattedMessage id='debugger.useGeneratedSources' /> (Solidity {'>='} v0.7.2)</label>
</span> </span>
) )
return ( return (
@ -386,7 +386,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox"> <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<CustomTooltip <CustomTooltip
tooltipId="debuggerGenSourceCheckbox" tooltipId="debuggerGenSourceCheckbox"
tooltipText={"Debug with generated sources"} tooltipText={<FormattedMessage id='debugger.debugWithGeneratedSources' />}
placement="top-start" placement="top-start"
> >
{customJSX} {customJSX}
@ -398,7 +398,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } } return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } }
}) })
}} type="checkbox" title="Force the debugger to use the current local node" /> }} type="checkbox" title="Force the debugger to use the current local node" />
<label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput">Force using local node</label> <label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput"><FormattedMessage id='debugger.debugLocaNodeLabel' /></label>
</div> </div>
} }
{ state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> } { state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> }

@ -7,13 +7,13 @@ export type ValidationResult = {
export interface ModalDialogProps { export interface ModalDialogProps {
id: string id: string
timestamp?: number, timestamp?: number,
title?: string, title?: string | JSX.Element,
validation?: ValidationResult validation?: ValidationResult
validationFn?: (value: string) => ValidationResult validationFn?: (value: string) => ValidationResult
message?: string | JSX.Element, message?: string | JSX.Element,
okLabel?: string, okLabel?: string | JSX.Element,
okFn?: (value?:any) => void, okFn?: (value?:any) => void,
cancelLabel?: string, cancelLabel?: string | JSX.Element,
cancelFn?: () => void, cancelFn?: () => void,
modalClass?: string, modalClass?: string,
showCancelIcon?: boolean, showCancelIcon?: boolean,

@ -1,4 +1,5 @@
import React, { ChangeEventHandler, useContext, useEffect, useRef, useState } from 'react' // eslint-disable-line import React, { ChangeEventHandler, useContext, useEffect, useRef, useState } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import { PermissionHandlerProps } from '../interface' import { PermissionHandlerProps } from '../interface'
import './permission-dialog.css' import './permission-dialog.css'
@ -6,6 +7,7 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
const { from, to, remember, method, message, sensitiveCall } = props.value const { from, to, remember, method, message, sensitiveCall } = props.value
const [feedback, setFeedback] = useState<string>('') const [feedback, setFeedback] = useState<string>('')
const theme = props.theme const theme = props.theme
const intl = useIntl()
const switchMode = (e: any) => { const switchMode = (e: any) => {
props.plugin.switchMode(from, to, method, e.target.checked) props.plugin.switchMode(from, to, method, e.target.checked)
@ -16,7 +18,7 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
} }
const reset = () => { const reset = () => {
props.plugin.clear() props.plugin.clear()
setFeedback('All permisssions have been reset.') setFeedback(intl.formatMessage({ id: 'permissionHandler.allPermissionsReset' }))
} }
const imgFrom = () => { return <img className={`${theme === 'dark' ? 'invert' : ''}`} alt='' id='permissionModalImagesFrom' src={from.icon} /> } const imgFrom = () => { return <img className={`${theme === 'dark' ? 'invert' : ''}`} alt='' id='permissionModalImagesFrom' src={from.icon} /> }
@ -32,13 +34,13 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
} }
const text = () => { const text = () => {
return <>"{from.displayName}" {(remember ? 'has changed and' : '')} would like to access to "{method}" of "{to.displayName}"`</> return <FormattedMessage id='permissionHandler.permissionHandlerMessage' values={{ from: from.displayName, to: to.displayName, method, rememberText: remember ? intl.formatMessage({ id: 'permissionHandler.rememberText' }) : '' }} />
} }
const pluginMessage = () => { const pluginMessage = () => {
return message return message
? <div> ? <div>
<h6>Description</h6> <h6><FormattedMessage id='permissionHandler.description' /></h6>
<p>{message}</p> <p>{message}</p>
</div> : null </div> : null
} }
@ -48,19 +50,19 @@ const PermissionHandlerDialog = (props: PermissionHandlerProps) => {
<article> <article>
<h4 data-id="permissionHandlerMessage">{text()} :</h4> <h4 data-id="permissionHandlerMessage">{text()} :</h4>
<h6>{from.displayName}</h6> <h6>{from.displayName}</h6>
<p> {from.description || <i>No description Provided</i>}</p> <p> {from.description || <i><FormattedMessage id='permissionHandler.noDescriptionProvided' /></i>}</p>
<h6>{to.displayName} :</h6> <h6>{to.displayName} :</h6>
<p> {to.description || <i>No description Provided</i>}</p> <p> {to.description || <i><FormattedMessage id='permissionHandler.noDescriptionProvided' /></i>}</p>
{pluginMessage()} {pluginMessage()}
{ sensitiveCall ? <p className='text-warning'><i className="fas fa-exclamation-triangle mr-2" aria-hidden="true"></i>Make sure you trust this plugin before processing this call.</p> : '' } { sensitiveCall ? <p className='text-warning'><i className="fas fa-exclamation-triangle mr-2" aria-hidden="true"></i><FormattedMessage id='permissionHandler.makeSureYouTrustThisPlugin' /></p> : '' }
</article> </article>
<article className='remember'> <article className='remember'>
{ !sensitiveCall && <div className='form-check'> { !sensitiveCall && <div className='form-check'>
{rememberSwitch()} {rememberSwitch()}
<label htmlFor='remember' className="form-check-label" data-id="permissionHandlerRememberChoice">Remember this choice</label> <label htmlFor='remember' className="form-check-label" data-id="permissionHandlerRememberChoice"><FormattedMessage id='permissionHandler.rememberThisChoice' /></label>
</div> </div>
} }
<button className="btn btn-sm" onClick={reset}>Reset all Permissions</button> <button className="btn btn-sm" onClick={reset}><FormattedMessage id='permissionHandler.resetAllPermissions' /></button>
</article> </article>
<div>{feedback}</div> <div>{feedback}</div>
</section>) </section>)

@ -18,12 +18,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
content: '' content: ''
}) })
const [atAddressOptions, setAtAddressOptions] = useState<{ title: string | JSX.Element, disabled: boolean }>({ const [atAddressOptions, setAtAddressOptions] = useState<{ title: string | JSX.Element, disabled: boolean }>({
title: 'address of contract', title: <FormattedMessage id='udapp.atAddressOptionsTitle1' />,
disabled: true disabled: true
}) })
const [loadedAddress, setLoadedAddress] = useState<string>('') const [loadedAddress, setLoadedAddress] = useState<string>('')
const [contractOptions, setContractOptions] = useState<{ title: string | JSX.Element, disabled: boolean }>({ const [contractOptions, setContractOptions] = useState<{ title: string | JSX.Element, disabled: boolean }>({
title: 'Please compile *.sol file to deploy or access a contract', title: <FormattedMessage id='udapp.contractOptionsTitle1' />,
disabled: true disabled: true
}) })
const [loadedContractData, setLoadedContractData] = useState<ContractData>(null) const [loadedContractData, setLoadedContractData] = useState<ContractData>(null)
@ -136,12 +136,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (enable) { if (enable) {
setAtAddressOptions({ setAtAddressOptions({
disabled: false, disabled: false,
title: <span className="text-start">Interact with the deployed contract - requires the .abi file or <br /> compiled .sol file to be selected in the editor <br />(with the same compiler configuration)</span> title: <span className="text-start"><FormattedMessage id='udapp.atAddressOptionsTitle2' values={{ br: <br /> }} /></span>
}) })
} else { } else {
setAtAddressOptions({ setAtAddressOptions({
disabled: true, disabled: true,
title: loadedAddress ? 'Compile a *.sol file or select a *.abi file.' : <span className="text-start">To interact with a deployed contract, either<br /> enter its address and compile its source *.sol file <br />(with the same compiler settings) or select its .abi file in the editor. </span> title: loadedAddress ? <FormattedMessage id='udapp.atAddressOptionsTitle3' /> : <span className="text-start"><FormattedMessage id='udapp.atAddressOptionsTitle4' values={{ br: <br /> }} /></span>
}) })
} }
} }
@ -150,12 +150,12 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
if (enable) { if (enable) {
setContractOptions({ setContractOptions({
disabled: false, disabled: false,
title: 'Select a compiled contract to deploy or to use with At Address.' title: <FormattedMessage id='udapp.contractOptionsTitle2' />
}) })
} else { } else {
setContractOptions({ setContractOptions({
disabled: true, disabled: true,
title: loadType === 'sol' ? 'Select and compile *.sol file to deploy or access a contract.' : <span className="text-start">When there is a compiled .sol file, choose the <br /> contract to deploy or to use with AtAddress.'</span> title: loadType === 'sol' ? <FormattedMessage id='udapp.contractOptionsTitle3' /> : <span className="text-start"><FormattedMessage id='udapp.contractOptionsTitle4' values={{ br: <br /> }} /></span>
}) })
} }
} }
@ -166,20 +166,20 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const createInstance = (selectedContract, args, deployMode?: DeployMode[]) => { const createInstance = (selectedContract, args, deployMode?: DeployMode[]) => {
if (selectedContract.bytecodeObject.length === 0) { if (selectedContract.bytecodeObject.length === 0) {
return props.modal('Alert', 'This contract may be abstract, it may not implement an abstract parent\'s methods completely or it may not invoke an inherited contract\'s constructor correctly.', 'OK', () => { }) return props.modal(intl.formatMessage({ id: 'udapp.alert' }), intl.formatMessage({ id: 'udapp.thisContractMayBeAbstract' }), intl.formatMessage({ id: 'udapp.ok' }), () => { })
} }
if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract if ((selectedContract.name !== currentContract) && (selectedContract.name === 'ERC1967Proxy')) selectedContract.name = currentContract
const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy') const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy')
const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy') const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy')
if (isProxyDeployment) { if (isProxyDeployment) {
props.modal('Deploy Implementation & Proxy (ERC1967)', deployWithProxyMsg(), 'Proceed', () => { props.modal('Deploy Implementation & Proxy (ERC1967)', deployWithProxyMsg(), intl.formatMessage({ id: 'udapp.proceed' }), () => {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}, 'Cancel', () => { }) }, intl.formatMessage({ id: 'udapp.cancel' }), () => { })
} else if (isContractUpgrade) { } else if (isContractUpgrade) {
props.modal('Deploy Implementation & Update Proxy', upgradeWithProxyMsg(), 'Proceed', () => { props.modal('Deploy Implementation & Update Proxy', upgradeWithProxyMsg(), intl.formatMessage({ id: 'udapp.proceed' }), () => {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
}, 'Cancel', () => { }) }, intl.formatMessage({ id: 'udapp.cancel' }), () => { })
} else { } else {
props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode)
} }
@ -237,17 +237,15 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const checkSumWarning = () => { const checkSumWarning = () => {
return ( return (
<span className="text-start"> <span className="text-start">
It seems you are not using a checksumed address. <FormattedMessage id='udapp.checkSumWarning' values={{ br: <br />, a: <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank" rel="noreferrer">EIP-55</a> }} />
<br />A checksummed address is an address that contains uppercase letters, as specified in <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank" rel="noreferrer">EIP-55</a>.
<br />Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.
</span> </span>
) )
} }
const isOverSizePrompt = () => { const isOverSizePrompt = () => {
return ( return (
<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br /> <div>
More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank" rel="noreferrer">eip-170</a> <FormattedMessage id='udapp.isOverSizePrompt' values={{ br: <br />, a: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank" rel="noreferrer">eip-170</a> }} />
</div> </div>
) )
} }
@ -259,7 +257,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<label className="udapp_settingsLabel pr-1"> <label className="udapp_settingsLabel pr-1">
<FormattedMessage id='udapp.contract' /> <FormattedMessage id='udapp.contract' />
</label> </label>
<div className="d-flex">{compilerName && compilerName !== '' && <label style={{ maxHeight: '0.6rem', lineHeight: '1rem' }} data-id="udappCompiledBy">(Compiled by <span className="text-capitalize"> {compilerName}</span>)</label>}</div> <div className="d-flex">{compilerName && compilerName !== '' && <label style={{ maxHeight: '0.6rem', lineHeight: '1rem' }} data-id="udappCompiledBy">(<FormattedMessage id='udapp.compiledBy' values={{ compilerName: <span className="text-capitalize"> {compilerName}</span> }} />)</label>}</div>
</div> </div>
{props.remixdActivated ? {props.remixdActivated ?
(<CustomTooltip (<CustomTooltip
@ -267,8 +265,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
tooltipClasses="text-wrap text-left" tooltipClasses="text-wrap text-left"
tooltipId="info-sync-compiled-contract" tooltipId="info-sync-compiled-contract"
tooltipText={<span className="text-left"> tooltipText={<span className="text-left">
Click here to import contracts compiled from an external framework.<br/> <FormattedMessage id='udapp.infoSyncCompiledContractTooltip' values={{ br: <br /> }} />
This action is enabled when Remix is connected to an external<br/> framework (hardhat, truffle, foundry) through remixd.
</span>} </span>}
> >
<button className="btn d-flex py-0" onClick={_ => { <button className="btn d-flex py-0" onClick={_ => {
@ -330,7 +327,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
placement={'right'} placement={'right'}
tooltipClasses="text-wrap text-left" tooltipClasses="text-wrap text-left"
tooltipId="remixIpfsUdappTooltip" tooltipId="remixIpfsUdappTooltip"
tooltipText={<span className="text-start">Publishing the source code and metadata to IPFS facilitates<br/> source code verification using Sourcify and will greatly foster<br/> contract adoption (auditing, debugging, calling it, etc...)</span>} tooltipText={<span className="text-start"><FormattedMessage id='udapp.remixIpfsUdappTooltip' values={{ br: <br /> }} /></span>}
> >
<label <label
htmlFor="deployAndRunPublishToIPFS" htmlFor="deployAndRunPublishToIPFS"
@ -364,7 +361,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
placement={'top-end'} placement={'top-end'}
tooltipClasses="text-wrap text-left" tooltipClasses="text-wrap text-left"
tooltipId="runAndDeployAddressInputtooltip" tooltipId="runAndDeployAddressInputtooltip"
tooltipText={"Address of contract"} tooltipText={<FormattedMessage id='udapp.addressOfContract' />}
> >
<input <input
ref={atAddressValue} ref={atAddressValue}

@ -1,5 +1,6 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import * as remixLib from '@remix-project/remix-lib' import * as remixLib from '@remix-project/remix-lib'
import { ContractGUIProps } from '../types' import { ContractGUIProps } from '../types'
import { CopyToClipboard } from '@remix-ui/clipboard' import { CopyToClipboard } from '@remix-ui/clipboard'
@ -26,6 +27,7 @@ export function ContractGUI (props: ContractGUIProps) {
const multiFields = useRef<Array<HTMLInputElement | null>>([]) const multiFields = useRef<Array<HTMLInputElement | null>>([])
const initializeFields = useRef<Array<HTMLInputElement | null>>([]) const initializeFields = useRef<Array<HTMLInputElement | null>>([])
const basicInputRef = useRef<HTMLInputElement>() const basicInputRef = useRef<HTMLInputElement>()
const intl = useIntl()
useEffect(() => { useEffect(() => {
if (props.deployOption && Array.isArray(props.deployOption)) { if (props.deployOption && Array.isArray(props.deployOption)) {
@ -238,7 +240,7 @@ export function ContractGUI (props: ContractGUIProps) {
const handleSetProxyAddress = (e) => { const handleSetProxyAddress = (e) => {
const value = e.target.value const value = e.target.value
setProxyAddress(value) setProxyAddress(value)
} }
@ -367,7 +369,7 @@ export function ContractGUI (props: ContractGUIProps) {
</div> </div>
<div className="d-flex udapp_group udapp_multiArg"> <div className="d-flex udapp_group udapp_multiArg">
<CopyToClipboard <CopyToClipboard
tip="Copy calldata to clipboard" tip={intl.formatMessage({ id: 'udapp.copyCalldata' })}
icon="fa-clipboard" icon="fa-clipboard"
direction={"bottom"} direction={"bottom"}
getContent={getEncodedCall} getContent={getEncodedCall}
@ -382,7 +384,7 @@ export function ContractGUI (props: ContractGUIProps) {
</button> </button>
</CopyToClipboard> </CopyToClipboard>
<CopyToClipboard <CopyToClipboard
tip="Copy encoded input parameters to clipboard" tip={intl.formatMessage({ id: 'udapp.copyParameters' })}
icon="fa-clipboard" icon="fa-clipboard"
direction={"bottom"} direction={"bottom"}
getContent={getEncodedParams} getContent={getEncodedParams}
@ -393,7 +395,7 @@ export function ContractGUI (props: ContractGUIProps) {
className="m-0 remixui_copyIcon far fa-copy" className="m-0 remixui_copyIcon far fa-copy"
aria-hidden="true" aria-hidden="true"
></i> ></i>
<label htmlFor="copyParameters">Parameters</label> <label htmlFor="copyParameters"><FormattedMessage id='udapp.parameters' /></label>
</button> </button>
</CopyToClipboard> </CopyToClipboard>
<CustomTooltip <CustomTooltip
@ -431,7 +433,7 @@ export function ContractGUI (props: ContractGUIProps) {
data-id="contractGUIDeployWithProxyLabel" data-id="contractGUIDeployWithProxyLabel"
className="m-0 form-check-label w-100 custom-control-label udapp_checkboxAlign" className="m-0 form-check-label w-100 custom-control-label udapp_checkboxAlign"
> >
Deploy with Proxy <FormattedMessage id='udapp.deployWithProxy' />
</label> </label>
</div> </div>
<div> <div>
@ -497,7 +499,7 @@ export function ContractGUI (props: ContractGUIProps) {
data-id="contractGUIUpgradeImplementationLabel" data-id="contractGUIUpgradeImplementationLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
> >
Upgrade with Proxy <FormattedMessage id='udapp.upgradeWithProxy' />
</label> </label>
</div> </div>
<span onClick={handleToggleUpgradeImp}> <span onClick={handleToggleUpgradeImp}>
@ -527,7 +529,7 @@ export function ContractGUI (props: ContractGUIProps) {
checked={useLastProxy} checked={useLastProxy}
/> />
<CustomTooltip <CustomTooltip
tooltipText="Select this option to use the last deployed ERC1967 contract on the current network." tooltipText={<FormattedMessage id='udapp.proxyAddressTooltip' />}
tooltipId="proxyAddressTooltip" tooltipId="proxyAddressTooltip"
placement="auto" placement="auto"
tooltipClasses="text-wrap" tooltipClasses="text-wrap"
@ -538,7 +540,7 @@ export function ContractGUI (props: ContractGUIProps) {
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
style={{ fontSize: 12 }} style={{ fontSize: 12 }}
> >
Use last deployed ERC1967 contract <FormattedMessage id='udapp.useLastDeployedERC1967Contract' />
</label> </label>
</CustomTooltip> </CustomTooltip>
</div> </div>
@ -546,10 +548,10 @@ export function ContractGUI (props: ContractGUIProps) {
!useLastProxy ? !useLastProxy ?
<div className="mb-2"> <div className="mb-2">
<label className="mt-2 text-left d-block"> <label className="mt-2 text-left d-block">
Proxy Address : <FormattedMessage id='udapp.proxyAddressLabel' /> :
</label> </label>
<CustomTooltip placement="right" tooltipText={'Enter previously deployed proxy address on the selected network'}> <CustomTooltip placement="right" tooltipText={<FormattedMessage id='udapp.proxyAddressInputTooltip' />}>
<input style={{ height: 32 }} className="form-control udapp_input" data-id="ERC1967AddressInput" placeholder='proxy address' onChange={handleSetProxyAddress} onBlur={() => validateProxyAddress(proxyAddress) } /> <input style={{ height: 32 }} className="form-control udapp_input" data-id="ERC1967AddressInput" placeholder={intl.formatMessage({ id: 'udapp.proxyAddressPlaceholder' })} onChange={handleSetProxyAddress} onBlur={() => validateProxyAddress(proxyAddress) } />
</CustomTooltip> </CustomTooltip>
{ proxyAddressError && <span className='text-lowercase' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> } { proxyAddressError && <span className='text-lowercase' data-id="errorMsgProxyAddress" style={{ fontSize: '.8em' }}>{ proxyAddressError }</span> }
</div> : </div> :

@ -1,6 +1,6 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, {useRef, useState, useEffect} from 'react' import React, {useRef, useState, useEffect} from 'react'
import { FormattedMessage } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { RecorderProps } from '../types' import { RecorderProps } from '../types'
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
@ -8,6 +8,8 @@ export function RecorderUI (props: RecorderProps) {
const inputLive = useRef<HTMLInputElement>() const inputLive = useRef<HTMLInputElement>()
const [toggleExpander, setToggleExpander] = useState<boolean>(false) const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const [enableRunButton, setEnableRunButton] = useState<boolean>(true) const [enableRunButton, setEnableRunButton] = useState<boolean>(true)
const intl = useIntl()
const triggerRecordButton = () => { const triggerRecordButton = () => {
props.storeScenario(props.scenarioPrompt) props.storeScenario(props.scenarioPrompt)
} }
@ -38,7 +40,7 @@ export function RecorderUI (props: RecorderProps) {
placement={'right'} placement={'right'}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="recordedTransactionsCounttooltip" tooltipId="recordedTransactionsCounttooltip"
tooltipText={'The number of recorded transactions'} tooltipText={<FormattedMessage id='udapp.transactionsCountTooltip' />}
> >
<div className="ml-2 badge badge-pill badge-primary text-center" data-title="The number of recorded transactions">{props.count}</div> <div className="ml-2 badge badge-pill badge-primary text-center" data-title="The number of recorded transactions">{props.count}</div>
</CustomTooltip> </CustomTooltip>
@ -46,7 +48,7 @@ export function RecorderUI (props: RecorderProps) {
placement={'right'} placement={'right'}
tooltipClasses="text-wrap" tooltipClasses="text-wrap"
tooltipId="info-recorder" tooltipId="info-recorder"
tooltipText={<span>Save transactions (deployed contracts and function executions) <br />and replay them in another environment e.g Transactions created <br />in Remix VM can be replayed in the Injected Provider.</span>} tooltipText={<span><FormattedMessage id='udapp.infoRecorderTooltip' values={{ br: <br /> }} /></span>}
> >
<i style={{ fontSize: 'medium' }} className={'ml-2 fal fa-info-circle align-self-center'} aria-hidden="true"></i> <i style={{ fontSize: 'medium' }} className={'ml-2 fal fa-info-circle align-self-center'} aria-hidden="true"></i>
</CustomTooltip> </CustomTooltip>
@ -64,9 +66,11 @@ export function RecorderUI (props: RecorderProps) {
placement={'right'} placement={'right'}
tooltipClasses="text-wrap" tooltipClasses="text-wrap"
tooltipId="tooltip-livemode-recorder" tooltipId="tooltip-livemode-recorder"
tooltipText={<span>If contracts are updated after recording transactions,<br/> checking this box will run recorded transactions <br/>with the latest copy of the compiled contracts</span>} tooltipText={<span><FormattedMessage id='udapp.livemodeRecorderTooltip' values={{ br: <br /> }} /></span>}
> >
<label className="form-check-label custom-control-label" data-id="runtabLivemodeInput" htmlFor="livemode-recorder">Run transactions using the latest compilation result</label> <label className="form-check-label custom-control-label" data-id="runtabLivemodeInput" htmlFor="livemode-recorder">
<FormattedMessage id='udapp.livemodeRecorderLabel' />
</label>
</CustomTooltip> </CustomTooltip>
</div> </div>
<div className="mb-1 mt-1 udapp_transactionActions"> <div className="mb-1 mt-1 udapp_transactionActions">
@ -75,14 +79,14 @@ export function RecorderUI (props: RecorderProps) {
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="remixUdappTransactionSavetooltip" tooltipId="remixUdappTransactionSavetooltip"
tooltipText={ tooltipText={
props.count === 0 ? 'No transactions to save' props.count === 0 ? intl.formatMessage({ id: 'udapp.transactionSaveTooltip1' })
: props.count === 1 ? `Save ${props.count} transaction as scenario file` : props.count === 1 ? intl.formatMessage({ id: 'udapp.transactionSaveTooltip2' }, { count: props.count })
: `Save ${props.count} transactions as scenario file` : intl.formatMessage({ id: 'udapp.transactionSaveTooltip3' }, { count: props.count })
} }
> >
<span> <span>
<button className="btn btn-sm btn-info savetransaction udapp_recorder" disabled={props.count === 0 ? true: false} onClick={triggerRecordButton} style={{ pointerEvents: props.count === 0 ? 'none' : 'auto' }}> <button className="btn btn-sm btn-info savetransaction udapp_recorder" disabled={props.count === 0 ? true: false} onClick={triggerRecordButton} style={{ pointerEvents: props.count === 0 ? 'none' : 'auto' }}>
Save <FormattedMessage id='udapp.save' />
</button> </button>
</span> </span>
</CustomTooltip> </CustomTooltip>
@ -90,11 +94,11 @@ export function RecorderUI (props: RecorderProps) {
placement={'right'} placement={'right'}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="tooltip-run-recorder" tooltipId="tooltip-run-recorder"
tooltipText="Run transaction(s) from the current scenario file" tooltipText={<FormattedMessage id='udapp.runRecorderTooltip' />}
> >
<span> <span>
<button className="btn btn-sm btn-info runtransaction udapp_runTxs" data-id="runtransaction" disabled={enableRunButton} onClick={handleClickRunButton} style={{ pointerEvents: enableRunButton ? 'none' : 'auto' }}> <button className="btn btn-sm btn-info runtransaction udapp_runTxs" data-id="runtransaction" disabled={enableRunButton} onClick={handleClickRunButton} style={{ pointerEvents: enableRunButton ? 'none' : 'auto' }}>
Run <FormattedMessage id='udapp.run' />
</button> </button>
</span> </span>
</CustomTooltip> </CustomTooltip>

@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect, ReactElement } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, ReactElement } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import * as semver from 'semver' import * as semver from 'semver'
import { eachOfSeries } from 'async' // eslint-disable-line import { eachOfSeries } from 'async' // eslint-disable-line
import type Web3 from 'web3' import type Web3 from 'web3'
@ -45,14 +46,16 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
const { helper, testTab, initialPath } = props const { helper, testTab, initialPath } = props
const { testTabLogic } = testTab const { testTabLogic } = testTab
const intl = useIntl()
const [toasterMsg, setToasterMsg] = useState<string>('') const [toasterMsg, setToasterMsg] = useState<string>('')
const [disableCreateButton, setDisableCreateButton] = useState<boolean>(true) const [disableCreateButton, setDisableCreateButton] = useState<boolean>(true)
const [disableGenerateButton, setDisableGenerateButton] = useState<boolean>(false) const [disableGenerateButton, setDisableGenerateButton] = useState<boolean>(false)
const [disableStopButton, setDisableStopButton] = useState<boolean>(true) const [disableStopButton, setDisableStopButton] = useState<boolean>(true)
const [disableRunButton, setDisableRunButton] = useState<boolean>(false) const [disableRunButton, setDisableRunButton] = useState<boolean>(false)
const [runButtonTitle, setRunButtonTitle] = useState<string>('Run tests') const [runButtonTitle, setRunButtonTitle] = useState<string>(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle1' }))
const [stopButtonLabel, setStopButtonLabel] = useState<string>('Stop') const [stopButtonLabel, setStopButtonLabel] = useState<string>(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel1' }))
const [checkSelectAll, setCheckSelectAll] = useState<boolean>(true) const [checkSelectAll, setCheckSelectAll] = useState<boolean>(true)
const [testsOutput, setTestsOutput] = useState<ReactElement[]>([]) const [testsOutput, setTestsOutput] = useState<ReactElement[]>([])
@ -170,7 +173,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) { if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) {
setDisableRunButton(true) setDisableRunButton(true)
setRunButtonTitle('Please select Solidity compiler version greater than 0.4.12.') setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle2' }))
} }
}) })
@ -395,17 +398,17 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
<span> {test.value}</span> <span> {test.value}</span>
{debugBtn} {debugBtn}
</div> </div>
<span className="text-dark">Error Message:</span> <span className="text-dark"><FormattedMessage id='solidityUnitTesting.errorMessage' />:</span>
<span className="pb-2 text-break">"{test.errMsg}"</span> <span className="pb-2 text-break">"{test.errMsg}"</span>
<span className="text-dark">Assertion:</span> <span className="text-dark"><FormattedMessage id='solidityUnitTesting.assertion' />:</span>
<div className="d-flex flex-wrap"> <div className="d-flex flex-wrap">
<span>Expected value should be</span> <span><FormattedMessage id='solidityUnitTesting.expectedValueShouldBe' /></span>
<div className="mx-1 font-weight-bold">{method}</div> <div className="mx-1 font-weight-bold">{method}</div>
<div>{preposition} {expected}</div> <div>{preposition} {expected}</div>
</div> </div>
<span className="text-dark">Received value:</span> <span className="text-dark"><FormattedMessage id='solidityUnitTesting.receivedValue' />:</span>
<span>{test.returned}</span> <span>{test.returned}</span>
<span className="text-dark text-sm pb-2">Skipping the remaining tests of the function.</span> <span className="text-dark text-sm pb-2"><FormattedMessage id='solidityUnitTesting.skippingTheRemainingTests' /></span>
</div>) </div>)
setTestsOutput(prevCards => ([...prevCards, testFailCard2])) setTestsOutput(prevCards => ([...prevCards, testFailCard2]))
} }
@ -457,10 +460,10 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
const testSummary = fileTestsResult['summary'] const testSummary = fileTestsResult['summary']
if (testSummary && testSummary.filename && !testSummary.rendered) { if (testSummary && testSummary.filename && !testSummary.rendered) {
const summaryCard: ReactElement = (<div className="d-flex alert-secondary mb-3 p-3 flex-column"> const summaryCard: ReactElement = (<div className="d-flex alert-secondary mb-3 p-3 flex-column">
<span className="font-weight-bold">Result for {testSummary.filename}</span> <span className="font-weight-bold"><FormattedMessage id='solidityUnitTesting.resultFor' /> {testSummary.filename}</span>
<span className="text-success">Passed: {testSummary.passed}</span> <span className="text-success"><FormattedMessage id='solidityUnitTesting.passed' />: {testSummary.passed}</span>
<span className="text-danger">Failed: {testSummary.failed}</span> <span className="text-danger"><FormattedMessage id='solidityUnitTesting.failed' />: {testSummary.failed}</span>
<span>Time Taken: {testSummary.timeTaken}s</span> <span><FormattedMessage id='solidityUnitTesting.timeTaken' />: {testSummary.timeTaken}s</span>
</div>) </div>)
setTestsOutput(prevCards => ([...prevCards, summaryCard])) setTestsOutput(prevCards => ([...prevCards, summaryCard]))
fileTestsResult['summary']['rendered'] = true fileTestsResult['summary']['rendered'] = true
@ -523,7 +526,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
if (_errors || hasBeenStopped.current || readyTestsNumber === runningTestsNumber) { if (_errors || hasBeenStopped.current || readyTestsNumber === runningTestsNumber) {
// All tests are ready or the operation has been canceled or there was a compilation error in one of the test files. // All tests are ready or the operation has been canceled or there was a compilation error in one of the test files.
setDisableStopButton(true) setDisableStopButton(true)
setStopButtonLabel('Stop') setStopButtonLabel(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel1' }))
if (selectedTests.current?.length !== 0) { if (selectedTests.current?.length !== 0) {
setDisableRunButton(false) setDisableRunButton(false)
} }
@ -598,16 +601,16 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
if (!isSolidityActive || !selectedTests.current.length) { if (!isSolidityActive || !selectedTests.current.length) {
setDisableRunButton(true) setDisableRunButton(true)
if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) { if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) {
setRunButtonTitle('No solidity file selected') setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle3' }))
} else { } else {
setRunButtonTitle('The "Solidity Plugin" should be activated') setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle4' }))
} }
} else setDisableRunButton(false) } else setDisableRunButton(false)
} }
const stopTests = () => { const stopTests = () => {
hasBeenStopped.current = true hasBeenStopped.current = true
setStopButtonLabel('Stopping') setStopButtonLabel(intl.formatMessage({ id: 'solidityUnitTesting.stopButtonLabel2' }))
setDisableStopButton(true) setDisableStopButton(true)
setDisableRunButton(true) setDisableRunButton(true)
} }
@ -625,12 +628,12 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
setCheckSelectAll(true) setCheckSelectAll(true)
setDisableRunButton(false) setDisableRunButton(false)
if ((readyTestsNumber === runningTestsNumber || hasBeenStopped.current) && stopButtonLabel.trim() === 'Stop') { if ((readyTestsNumber === runningTestsNumber || hasBeenStopped.current) && stopButtonLabel.trim() === 'Stop') {
setRunButtonTitle('Run tests') setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle1' }))
} }
} else if (!selectedTests.current.length) { } else if (!selectedTests.current.length) {
setCheckSelectAll(false) setCheckSelectAll(false)
setDisableRunButton(true) setDisableRunButton(true)
setRunButtonTitle('No test file selected') setRunButtonTitle(intl.formatMessage({ id: 'solidityUnitTesting.runButtonTitle5' }))
} else setCheckSelectAll(false) } else setCheckSelectAll(false)
} }
@ -661,9 +664,9 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
<div className="px-2" id="testView"> <div className="px-2" id="testView">
<Toaster message={toasterMsg} /> <Toaster message={toasterMsg} />
<div className="infoBox"> <div className="infoBox">
<p className="text-lg"> Test your smart contract in Solidity.</p> <p className="text-lg"> <FormattedMessage id='solidityUnitTesting.testYourSmartContract' /></p>
<p> Select directory to load and generate test files.</p> <p> <FormattedMessage id='solidityUnitTesting.selectDirectory' /></p>
<label>Test directory:</label> <label><FormattedMessage id='solidityUnitTesting.testDirectory' />:</label>
<div> <div>
<div className="d-flex p-2"> <div className="d-flex p-2">
<datalist id="utPathList">{ <datalist id="utPathList">{
@ -676,7 +679,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
placement="top-end" placement="top-end"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="uiPathInputtooltip" tooltipId="uiPathInputtooltip"
tooltipText={"Press 'Enter' to change the path for test files."} tooltipText={<FormattedMessage id='solidityUnitTesting.uiPathInputTooltip' />}
> >
<input <input
list="utPathList" list="utPathList"
@ -695,7 +698,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
placement="top-end" placement="top-end"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="uiPathInputButtontooltip" tooltipId="uiPathInputButtontooltip"
tooltipText="Create a test folder" tooltipText={<FormattedMessage id='solidityUnitTesting.uiPathInputButtonTooltip' />}
> >
<button <button
className="btn border ml-2" className="btn border ml-2"
@ -703,7 +706,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
disabled={disableCreateButton} disabled={disableCreateButton}
onClick={handleCreateFolder} onClick={handleCreateFolder}
> >
Create <FormattedMessage id='solidityUnitTesting.create' />
</button> </button>
</CustomTooltip> </CustomTooltip>
</div> </div>
@ -714,7 +717,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
<CustomTooltip <CustomTooltip
tooltipId="generateTestsButtontooltip" tooltipId="generateTestsButtontooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText="Generate a sample test file" tooltipText={<FormattedMessage id='solidityUnitTesting.generateTestsButtonTooltip' />}
placement={'bottom-start'} placement={'bottom-start'}
> >
<button <button
@ -726,17 +729,17 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
await updateForNewCurrent() await updateForNewCurrent()
}} }}
> >
Generate <FormattedMessage id='solidityUnitTesting.generate' />
</button> </button>
</CustomTooltip> </CustomTooltip>
<CustomTooltip <CustomTooltip
tooltipId="generateTestsLinktooltip" tooltipId="generateTestsLinktooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText="Check out documentation." tooltipText={<FormattedMessage id='solidityUnitTesting.generateTestsLinkTooltip' />}
placement={'bottom-start'} placement={'bottom-start'}
> >
<a className="btn border text-decoration-none pr-0 d-flex w-50 ml-2" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/unittesting.html#test-directory"> <a className="btn border text-decoration-none pr-0 d-flex w-50 ml-2" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/unittesting.html#test-directory">
<label className="btn p-1 ml-2 m-0">How to use...</label> <label className="btn p-1 ml-2 m-0"><FormattedMessage id='solidityUnitTesting.howToUse' /></label>
</a> </a>
</CustomTooltip> </CustomTooltip>
</div> </div>
@ -749,7 +752,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
> >
<button id="runTestsTabRunAction"data-id="testTabRunTestsTabRunAction" className="w-50 btn btn-primary" disabled={disableRunButton} onClick={runTests}> <button id="runTestsTabRunAction"data-id="testTabRunTestsTabRunAction" className="w-50 btn btn-primary" disabled={disableRunButton} onClick={runTests}>
<span className="fas fa-play ml-2"></span> <span className="fas fa-play ml-2"></span>
<label className="labelOnBtn btn btn-primary p-1 ml-2 m-0">Run</label> <label className="labelOnBtn btn btn-primary p-1 ml-2 m-0"><FormattedMessage id='solidityUnitTesting.run' /></label>
</button> </button>
</CustomTooltip> </CustomTooltip>
<button id="runTestsTabStopAction" data-id="testTabRunTestsTabStopAction" className="w-50 pl-2 ml-2 btn btn-secondary" disabled={disableStopButton} onClick={stopTests}> <button id="runTestsTabStopAction" data-id="testTabRunTestsTabStopAction" className="w-50 pl-2 ml-2 btn btn-secondary" disabled={disableStopButton} onClick={stopTests}>
@ -757,7 +760,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
placement={'top-start'} placement={'top-start'}
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipId="info-recorder" tooltipId="info-recorder"
tooltipText="Stop running tests" tooltipText={<FormattedMessage id='solidityUnitTesting.runTestsTabStopActionTooltip' />}
> >
<span> <span>
<span className="fas fa-stop ml-2"></span> <span className="fas fa-stop ml-2"></span>
@ -774,7 +777,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
checked={checkSelectAll} checked={checkSelectAll}
onChange={() => { }} // eslint-disable-line onChange={() => { }} // eslint-disable-line
/> />
<label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> Select all </label> <label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> <FormattedMessage id='solidityUnitTesting.selectAll' /> </label>
</div> </div>
<div className="testList py-2 mt-0 border-bottom">{testFiles.length ? testFiles.map((testFileObj: TestObject, index) => { <div className="testList py-2 mt-0 border-bottom">{testFiles.length ? testFiles.map((testFileObj: TestObject, index) => {
const elemId = `singleTest${testFileObj.fileName}` const elemId = `singleTest${testFileObj.fileName}`
@ -787,9 +790,9 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
}) })
: "No test file available"} </div> : "No test file available"} </div>
<div className="align-items-start flex-column mt-2 mx-3 mb-0"> <div className="align-items-start flex-column mt-2 mx-3 mb-0">
<span className='text-info h6' hidden={progressBarHidden}>Progress: {readyTestsNumber} finished (of {runningTestsNumber})</span> <span className='text-info h6' hidden={progressBarHidden}><FormattedMessage id='solidityUnitTesting.progress' values={{ readyTestsNumber, runningTestsNumber }} /></span>
<label className="text-warning h6" data-id="testTabTestsExecutionStopped" hidden={testsExecutionStoppedHidden}>The test execution has been stopped</label> <label className="text-warning h6" data-id="testTabTestsExecutionStopped" hidden={testsExecutionStoppedHidden}><FormattedMessage id='solidityUnitTesting.testTabTestsExecutionStopped' /></label>
<label className="text-danger h6" data-id="testTabTestsExecutionStoppedError" hidden={testsExecutionStoppedErrorHidden}>The test execution has been stopped because of error(s) in your test file</label> <label className="text-danger h6" data-id="testTabTestsExecutionStoppedError" hidden={testsExecutionStoppedErrorHidden}><FormattedMessage id='solidityUnitTesting.testTabTestsExecutionStoppedError' /></label>
</div> </div>
<div className="mx-3 mb-2 pb-4 border-primary" id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput">{testsOutput}</div> <div className="mx-3 mb-2 pb-4 border-primary" id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput">{testsOutput}</div>
</div> </div>

@ -1,5 +1,6 @@
import React from 'react' // eslint-disable-line import React from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
import Context from './Context' // eslint-disable-line import Context from './Context' // eslint-disable-line
import showTable from './Table' import showTable from './Table'
@ -31,7 +32,7 @@ const RenderKnownTransactions = ({ tx, receipt, resolvedData, logs, index, plugi
data-shared='txLoggerDebugButton' data-shared='txLoggerDebugButton'
data-id={`txLoggerDebugButton${tx.hash}`} data-id={`txLoggerDebugButton${tx.hash}`}
onClick={(event) => debug(event, tx)} onClick={(event) => debug(event, tx)}
>Debug</div> ><FormattedMessage id='terminal.debug' /></div>
</div> </div>
<i className={`remix_ui_terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i> <i className={`remix_ui_terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i>
</div> </div>

@ -1,4 +1,5 @@
import React, { useState } from 'react' // eslint-disable-line import React, { useState } from 'react' // eslint-disable-line
import { FormattedMessage } from 'react-intl'
import CheckTxStatus from './ChechTxStatus' // eslint-disable-line import CheckTxStatus from './ChechTxStatus' // eslint-disable-line
import Context from './Context' // eslint-disable-line import Context from './Context' // eslint-disable-line
import showTable from './Table' import showTable from './Table'
@ -27,7 +28,7 @@ const RenderUnKnownTransactions = ({ tx, receipt, index, plugin, showTableHash,
data-shared='txLoggerDebugButton' data-shared='txLoggerDebugButton'
data-id={`txLoggerDebugButton${tx.hash}`} data-id={`txLoggerDebugButton${tx.hash}`}
onClick={(event) => debug(event, tx)} onClick={(event) => debug(event, tx)}
>Debug</div> ><FormattedMessage id='terminal.debug' /></div>
</div> </div>
<i className = {`remix_ui_terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i> <i className = {`remix_ui_terminal_arrow fas ${(showTableHash.includes(tx.hash)) ? 'fa-angle-up' : 'fa-angle-down'}`}></i>
</div> </div>

@ -464,11 +464,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
id="listenNetworkCheck" id="listenNetworkCheck"
onChange={listenOnNetwork} onChange={listenOnNetwork}
type="checkbox" type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you" title={intl.formatMessage({ id: 'terminal.listenTitle' })}
/> />
<label <label
className="pt-1 form-check-label custom-control-label text-nowrap" className="pt-1 form-check-label custom-control-label text-nowrap"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you" title={intl.formatMessage({ id: 'terminal.listenTitle' })}
htmlFor="listenNetworkCheck" htmlFor="listenNetworkCheck"
data-id="listenNetworkCheckInput" data-id="listenNetworkCheckInput"
> >

@ -296,6 +296,15 @@ export const renamePath = async (oldPath: string, newPath: string) => {
} }
} }
export const downloadPath = async (path: string) => {
const fileManager = plugin.fileManager
try {
await fileManager.download(path)
} catch (error) {
dispatch(displayPopUp('Oops! An error ocurred while downloading.' + error))
}
}
export const copyFile = async (src: string, dest: string) => { export const copyFile = async (src: string, dest: string) => {
const fileManager = plugin.fileManager const fileManager = plugin.fileManager

@ -13,7 +13,7 @@ declare global {
const _paq = window._paq = window._paq || [] //eslint-disable-line const _paq = window._paq = window._paq || [] //eslint-disable-line
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => { export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, copyFileName, copyPath, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props const { actions, createNewFile, createNewFolder, deletePath, renamePath, downloadPath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, copyFileName, copyPath, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props
const contextMenuRef = useRef(null) const contextMenuRef = useRef(null)
const intl = useIntl() const intl = useIntl()
useEffect(() => { useEffect(() => {
@ -88,6 +88,10 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
deletePath(getPath()) deletePath(getPath())
_paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'delete']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'delete'])
break break
case 'Download':
downloadPath(path)
_paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'download'])
break
case 'Push changes to gist': case 'Push changes to gist':
_paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'pushToChangesoGist']) _paq.push(['trackEvent', 'fileExplorer', 'contextMenu', 'pushToChangesoGist'])
pushChangesToGist(path, type) pushChangesToGist(path, type)

@ -174,6 +174,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
} }
const downloadPath = async (path: string) => {
try {
props.dispatchDownloadPath(path)
} catch (error) {
props.modal('Download Failed', 'Unexpected error while downloading: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
const uploadFile = (target) => { const uploadFile = (target) => {
const parentFolder = getFocusedFolder() const parentFolder = getFocusedFolder()
const expandPath = [...new Set([...props.expandPath, parentFolder])] const expandPath = [...new Set([...props.expandPath, parentFolder])]
@ -488,6 +496,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
createNewFile={handleNewFileInput} createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput} createNewFolder={handleNewFolderInput}
deletePath={deletePath} deletePath={deletePath}
downloadPath={downloadPath}
renamePath={editModeOn} renamePath={editModeOn}
runScript={runScript} runScript={runScript}
copy={handleCopyClick} copy={handleCopyClick}

@ -22,6 +22,7 @@ export const FileSystemContext = createContext<{
dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>, dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>,
dispatchDeletePath: (path: string[]) => Promise<void>, dispatchDeletePath: (path: string[]) => Promise<void>,
dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>, dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>,
dispatchDownloadPath: (path:string) => Promise<void>,
dispatchCopyFile: (src: string, dest: string) => Promise<void>, dispatchCopyFile: (src: string, dest: string) => Promise<void>,
dispatchCopyFolder: (src: string, dest: string) => Promise<void>, dispatchCopyFolder: (src: string, dest: string) => Promise<void>,
dispatchRunScript: (path: string) => Promise<void>, dispatchRunScript: (path: string) => Promise<void>,

@ -6,7 +6,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import { FileSystemContext } from '../contexts' import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace' import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction
} from '../actions' } from '../actions'
@ -95,6 +95,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await renamePath(oldPath, newPath) await renamePath(oldPath, newPath)
} }
const dispatchDownloadPath = async (path: string) => {
await downloadPath(path)
}
const dispatchCopyFile = async (src: string, dest: string) => { const dispatchCopyFile = async (src: string, dest: string) => {
await copyFile(src, dest) await copyFile(src, dest)
} }
@ -261,6 +265,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchCreateNewFolder, dispatchCreateNewFolder,
dispatchDeletePath, dispatchDeletePath,
dispatchRenamePath, dispatchRenamePath,
dispatchDownloadPath,
dispatchCopyFile, dispatchCopyFile,
dispatchCopyFolder, dispatchCopyFolder,
dispatchRunScript, dispatchRunScript,

@ -73,18 +73,18 @@ export function Workspace () {
}, [currentWorkspace]) }, [currentWorkspace])
const renameCurrentWorkspace = () => { const renameCurrentWorkspace = () => {
global.modal(intl.formatMessage({ id: 'filePanel.workspace.rename' }), renameModalMessage(), 'OK', onFinishRenameWorkspace, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.rename' }), renameModalMessage(), intl.formatMessage({ id: 'filePanel.ok' }), onFinishRenameWorkspace, '')
} }
const createWorkspace = () => { const createWorkspace = () => {
global.modal(intl.formatMessage({ id: 'filePanel.workspace.create' }), createModalMessage(), 'OK', onFinishCreateWorkspace, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.create' }), createModalMessage(), intl.formatMessage({ id: 'filePanel.ok' }), onFinishCreateWorkspace, '')
} }
const deleteCurrentWorkspace = () => { const deleteCurrentWorkspace = () => {
global.modal( global.modal(
intl.formatMessage({ id: 'filePanel.workspace.delete' }), intl.formatMessage({ id: 'filePanel.workspace.delete' }),
intl.formatMessage({ id: 'filePanel.workspace.deleteConfirm' }), intl.formatMessage({ id: 'filePanel.workspace.deleteConfirm' }),
'OK', intl.formatMessage({ id: 'filePanel.ok' }),
onFinishDeleteWorkspace, onFinishDeleteWorkspace,
'' ''
) )
@ -94,7 +94,7 @@ export function Workspace () {
global.modal( global.modal(
intl.formatMessage({ id: 'filePanel.workspace.clone' }), intl.formatMessage({ id: 'filePanel.workspace.clone' }),
cloneModalMessage(), cloneModalMessage(),
'OK', intl.formatMessage({ id: 'filePanel.ok' }),
handleTypingUrl, handleTypingUrl,
'' ''
) )
@ -136,7 +136,7 @@ export function Workspace () {
try { try {
await global.dispatchRenameWorkspace(currentWorkspace, workspaceName) await global.dispatchRenameWorkspace(currentWorkspace, workspaceName)
} catch (e) { } catch (e) {
global.modal('Rename Workspace', e.message, 'OK', () => {}, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.rename' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {}, '')
console.error(e) console.error(e)
} }
} }
@ -162,7 +162,7 @@ export function Workspace () {
try { try {
await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName, opts, initGitRepo) await global.dispatchCreateWorkspace(workspaceName, workspaceTemplateName, opts, initGitRepo)
} catch (e) { } catch (e) {
global.modal('Create Workspace', e.message, 'OK', () => {}, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.create' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {}, '')
console.error(e) console.error(e)
} }
} }
@ -171,7 +171,7 @@ export function Workspace () {
try { try {
await global.dispatchDeleteWorkspace(global.fs.browser.currentWorkspace) await global.dispatchDeleteWorkspace(global.fs.browser.currentWorkspace)
} catch (e) { } catch (e) {
global.modal('Delete Workspace', e.message, 'OK', () => {}, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.delete' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {}, '')
console.error(e) console.error(e)
} }
} }
@ -186,7 +186,7 @@ export function Workspace () {
await global.dispatchSwitchToWorkspace(name) await global.dispatchSwitchToWorkspace(name)
global.dispatchHandleExpandPath([]) global.dispatchHandleExpandPath([])
} catch (e) { } catch (e) {
global.modal('Switch To Workspace', e.message, 'OK', () => {}, '') global.modal(intl.formatMessage({ id: 'filePanel.workspace.switch' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {}, '')
console.error(e) console.error(e)
} }
} }
@ -217,7 +217,13 @@ export function Workspace () {
if (url) { if (url) {
global.dispatchCloneRepository(url) global.dispatchCloneRepository(url)
} else { } else {
global.modal('Clone Git Repository', 'Please provide a valid git repository url.', 'OK', () => {}, '') global.modal(
intl.formatMessage({ id: 'filePanel.workspace.clone' }),
intl.formatMessage({ id: 'filePanel.workspace.cloneMessage' }),
intl.formatMessage({ id: 'filePanel.ok' }),
() => {},
''
)
} }
} }
@ -255,7 +261,7 @@ export function Workspace () {
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
global.modal('Checkout Git Branch', e.message, 'OK', () => {}) global.modal(intl.formatMessage({ id: 'filePanel.checkoutGitBranch' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {})
} }
} }
@ -264,7 +270,7 @@ export function Workspace () {
await global.dispatchCreateNewBranch(branchFilter) await global.dispatchCreateNewBranch(branchFilter)
_paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_new_branch']) _paq.push(['trackEvent', 'Workspace', 'GIT', 'switch_to_new_branch'])
} catch (e) { } catch (e) {
global.modal('Checkout Git Branch', e.message, 'OK', () => {}) global.modal(intl.formatMessage({ id: 'filePanel.checkoutGitBranch' }), e.message, intl.formatMessage({ id: 'filePanel.ok' }), () => {})
} }
} }
@ -291,9 +297,9 @@ export function Workspace () {
</select> </select>
<div id="ozcustomization" data-id="ozCustomization" ref={displayOzCustomRef} style={{display: 'none'}} className="mb-2"> <div id="ozcustomization" data-id="ozCustomization" ref={displayOzCustomRef} style={{display: 'none'}} className="mb-2">
<label className="form-check-label d-block mb-2" style={{fontWeight: "bolder"}}>Customize template</label> <label className="form-check-label d-block mb-2" style={{fontWeight: "bolder"}}><FormattedMessage id='filePanel.customizeTemplate' /></label>
<label id="wsName" className="form-check-label d-block mb-1">Features</label> <label id="wsName" className="form-check-label d-block mb-1"><FormattedMessage id='filePanel.features' /></label>
<div className="mb-2"> <div className="mb-2">
<div className="d-flex ml-2 custom-control custom-checkbox"> <div className="d-flex ml-2 custom-control custom-checkbox">
<input className="custom-control-input" type="checkbox" name="feature" value="mintable" id="mintable" ref={mintableCheckboxRef} /> <input className="custom-control-input" type="checkbox" name="feature" value="mintable" id="mintable" ref={mintableCheckboxRef} />
@ -309,7 +315,7 @@ export function Workspace () {
</div> </div>
</div> </div>
<label id="wsName" className="form-check-label d-block mb-1">Upgradeability</label> <label id="wsName" className="form-check-label d-block mb-1"><FormattedMessage id='filePanel.upgradeability' /></label>
<div onChange={handleUpgradeability}> <div onChange={handleUpgradeability}>
<div className="d-flex ml-2 custom-control custom-radio"> <div className="d-flex ml-2 custom-control custom-radio">
<input className="custom-control-input" type="radio" name="upgradeability" value="transparent" id="transparent" ref={transparentRadioRef} /> <input className="custom-control-input" type="radio" name="upgradeability" value="transparent" id="transparent" ref={transparentRadioRef} />
@ -323,7 +329,7 @@ export function Workspace () {
</div> </div>
<label id="wsName" className="form-check-label" style={{fontWeight: "bolder"}} >Workspace name</label> <label id="wsName" className="form-check-label" style={{fontWeight: "bolder"}} ><FormattedMessage id='filePanel.workspaceName' /></label>
<input type="text" data-id="modalDialogCustomPromptTextCreate" defaultValue={`remixDefault_${Date.now()}`} ref={workspaceCreateInput} className="form-control" /> <input type="text" data-id="modalDialogCustomPromptTextCreate" defaultValue={`remixDefault_${Date.now()}`} ref={workspaceCreateInput} className="form-control" />
<div className="d-flex py-2 align-items-center custom-control custom-checkbox"> <div className="d-flex py-2 align-items-center custom-control custom-checkbox">
@ -342,12 +348,12 @@ export function Workspace () {
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title="Check option to initialize workspace as a new git repository" title="Check option to initialize workspace as a new git repository"
> >
Initialize workspace as a new git repository <FormattedMessage id='filePanel.initGitRepositoryLabel' />
</label> </label>
</div> </div>
{!global.fs.gitConfig.username || !global.fs.gitConfig.email ? {!global.fs.gitConfig.username || !global.fs.gitConfig.email ?
( (
<div className='text-warning'>Please add username and email to Remix GitHub Settings to use git features.</div>) <div className='text-warning'><FormattedMessage id='filePanel.initGitRepositoryWarning' /></div>)
:<></> :<></>
} }
@ -510,6 +516,7 @@ export function Workspace () {
toast={global.toast} toast={global.toast}
dispatchDeletePath={global.dispatchDeletePath} dispatchDeletePath={global.dispatchDeletePath}
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
@ -549,6 +556,7 @@ export function Workspace () {
toast={global.toast} toast={global.toast}
dispatchDeletePath={global.dispatchDeletePath} dispatchDeletePath={global.dispatchDeletePath}
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
@ -584,14 +592,14 @@ export function Workspace () {
<Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown'> <Dropdown.Menu as={CustomMenu} className='custom-dropdown-items branches-dropdown'>
<div data-id="custom-dropdown-menu"> <div data-id="custom-dropdown-menu">
<div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}> <div className='d-flex text-dark' style={{ fontSize: 14, fontWeight: 'bold' }}>
<span className='mt-2 ml-2 mr-auto'>Switch branches</span> <span className='mt-2 ml-2 mr-auto'><FormattedMessage id='filePanel.switchBranches' /></span>
<div className='pt-2 pr-2' onClick={() => { toggleBranches(false) }}><i className='fa fa-close'></i> <div className='pt-2 pr-2' onClick={() => { toggleBranches(false) }}><i className='fa fa-close'></i>
</div> </div>
</div> </div>
<div className='border-top py-2'> <div className='border-top py-2'>
<input <input
className='form-control border checkout-input bg-light' className='form-control border checkout-input bg-light'
placeholder='Find or create a branch.' placeholder={intl.formatMessage({ id: 'filePanel.findOrCreateABranch' })}
style={{ minWidth: 225 }} style={{ minWidth: 225 }}
onChange={handleBranchFilterChange} onChange={handleBranchFilterChange}
data-id='workspaceGitInput' data-id='workspaceGitInput'
@ -614,13 +622,13 @@ export function Workspace () {
}) : }) :
<Dropdown.Item onClick={switchToNewBranch}> <Dropdown.Item onClick={switchToNewBranch}>
<div className="pl-1 pr-1" data-id="workspaceGitCreateNewBranch"> <div className="pl-1 pr-1" data-id="workspaceGitCreateNewBranch">
<i className="fas fa-code-branch pr-2"></i><span>Create branch: { branchFilter } from '{currentBranch}'</span> <i className="fas fa-code-branch pr-2"></i><span><FormattedMessage id='filePanel.createBranch' />: { branchFilter } from '{currentBranch}'</span>
</div> </div>
</Dropdown.Item> </Dropdown.Item>
} }
</div> </div>
{ {
(selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><label style={{ fontSize: 12, cursor: 'pointer' }} onClick={showAllBranches}>View all branches</label></div> (selectedWorkspace.branches || []).length > 4 && <div className='text-center border-top pt-2'><label style={{ fontSize: 12, cursor: 'pointer' }} onClick={showAllBranches}><FormattedMessage id='filePanel.viewAllBranches' /></label></div>
} }
</div> </div>
</Dropdown.Menu> </Dropdown.Menu>

@ -96,6 +96,7 @@ export interface FileExplorerProps {
toast: (toasterMsg: string) => void, toast: (toasterMsg: string) => void,
dispatchDeletePath: (path: string[]) => Promise<void>, dispatchDeletePath: (path: string[]) => Promise<void>,
dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>, dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>,
dispatchDownloadPath: (path: string) => Promise<void>,
dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>, dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCopyFile: (src: string, dest: string) => Promise<void>, dispatchCopyFile: (src: string, dest: string) => Promise<void>,
dispatchCopyFolder: (src: string, dest: string) => Promise<void>, dispatchCopyFolder: (src: string, dest: string) => Promise<void>,
@ -127,6 +128,7 @@ export interface FileExplorerContextMenuProps {
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,
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,

@ -24,6 +24,12 @@ export const contextMenuActions: MenuItems = [{
type: ['file', 'folder', 'gist'], type: ['file', 'folder', 'gist'],
multiselect: false, multiselect: false,
label: '' label: ''
},{
id: 'download',
name: 'Download',
type: ['file'],
multiselect: false,
label: ''
}, { }, {
id: 'run', id: 'run',
name: 'Run', name: 'Run',

Loading…
Cancel
Save