Merge branch 'master' of https://github.com/ethereum/remix-project into desktop-master-git

pull/4991/head
bunsenstraat 6 months ago
commit 5fe967e667
  1. 2
      .github/workflows/pr-reminder.yml
  2. 2
      apps/etherscan/src/app/utils/networks.ts
  3. 8
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  4. 2
      apps/etherscan/src/app/views/VerifyView.tsx
  5. 8
      apps/learneth/src/components/BackButton/index.tsx
  6. 90
      apps/learneth/src/pages/StepDetail/index.tsx
  7. 5
      apps/learneth/src/redux/models/remixide.ts
  8. 5
      apps/learneth/src/redux/models/workshop.ts
  9. 2
      apps/learneth/src/remix-client.ts
  10. 34
      apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts
  11. 20
      apps/remix-ide-e2e/src/commands/pinGrid.ts
  12. 5
      apps/remix-ide-e2e/src/commands/selectFiles.ts
  13. 12
      apps/remix-ide-e2e/src/commands/setupMetamask.ts
  14. 2
      apps/remix-ide-e2e/src/githttpbackend/setup.sh
  15. 10
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  16. 35
      apps/remix-ide-e2e/src/tests/circom.test.ts
  17. 23
      apps/remix-ide-e2e/src/tests/dgit_github.test.ts
  18. 108
      apps/remix-ide-e2e/src/tests/dgit_local.test.ts
  19. 12
      apps/remix-ide-e2e/src/tests/erc721.test.ts
  20. 77
      apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts
  21. 36
      apps/remix-ide-e2e/src/tests/grid.test.ts
  22. 55
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  23. 11
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  24. 1
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  25. 13
      apps/remix-ide-e2e/src/tests/url.test.ts
  26. 132
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  27. 55
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  28. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  29. 4
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  30. 4
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  31. 4
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  32. 43
      apps/remix-ide/src/app.js
  33. 2
      apps/remix-ide/src/app/editor/editor.js
  34. 18
      apps/remix-ide/src/app/files/fileManager.ts
  35. 2
      apps/remix-ide/src/app/panels/file-panel.js
  36. 13
      apps/remix-ide/src/app/panels/layout.ts
  37. 4
      apps/remix-ide/src/app/plugins/git.tsx
  38. 2
      apps/remix-ide/src/app/plugins/matomo.ts
  39. 2
      apps/remix-ide/src/app/plugins/remixGuide.tsx
  40. 12
      apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.css
  41. 269
      apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx
  42. 357
      apps/remix-ide/src/app/plugins/templates-selection/templates.ts
  43. 207
      apps/remix-ide/src/app/providers/environment-explorer.tsx
  44. 5
      apps/remix-ide/src/app/providers/style/environment-explorer.css
  45. 48
      apps/remix-ide/src/app/udapp/run-tab.js
  46. BIN
      apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp
  47. BIN
      apps/remix-ide/src/assets/img/Walletconnect-logo.png
  48. BIN
      apps/remix-ide/src/assets/img/arbitrum-arb-logo.png
  49. BIN
      apps/remix-ide/src/assets/img/brave.png
  50. BIN
      apps/remix-ide/src/assets/img/foundry.png
  51. BIN
      apps/remix-ide/src/assets/img/hardhat.png
  52. BIN
      apps/remix-ide/src/assets/img/metamask.png
  53. BIN
      apps/remix-ide/src/assets/img/optimism-ethereum-op-logo.png
  54. BIN
      apps/remix-ide/src/assets/img/trust-wallet.png
  55. 55
      apps/remix-ide/src/blockchain/blockchain.tsx
  56. 5
      apps/remix-ide/src/blockchain/execution-context.js
  57. 22
      apps/remix-ide/src/remixAppManager.js
  58. 2
      apps/remix-ide/src/remixEngine.js
  59. 8
      libs/ghaction-helper/package.json
  60. 8
      libs/remix-analyzer/package.json
  61. 13
      libs/remix-api/src/lib/plugins/layout-api.ts
  62. 10
      libs/remix-api/src/lib/plugins/matomo-api.ts
  63. 11
      libs/remix-api/src/lib/plugins/pinned-panel-api.ts
  64. 11
      libs/remix-api/src/lib/plugins/sidePanel-api.ts
  65. 8
      libs/remix-api/src/lib/remix-api.ts
  66. 6
      libs/remix-astwalker/package.json
  67. 10
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  68. 12
      libs/remix-debug/package.json
  69. 6
      libs/remix-lib/package.json
  70. 2
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  71. 6
      libs/remix-simulator/package.json
  72. 6
      libs/remix-solidity/package.json
  73. 10
      libs/remix-tests/package.json
  74. 4
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  75. 4
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  76. 4
      libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts
  77. 1
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  78. 65
      libs/remix-ui/git/src/components/branchHeader.tsx
  79. 4
      libs/remix-ui/git/src/components/buttons/commitmessage.tsx
  80. 26
      libs/remix-ui/git/src/components/buttons/gituibutton.tsx
  81. 5
      libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx
  82. 3
      libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx
  83. 2
      libs/remix-ui/git/src/components/github/branchselect.tsx
  84. 48
      libs/remix-ui/git/src/components/github/devicecode.tsx
  85. 40
      libs/remix-ui/git/src/components/github/repositoryselect.tsx
  86. 9
      libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx
  87. 145
      libs/remix-ui/git/src/components/gitui.tsx
  88. 39
      libs/remix-ui/git/src/components/navigation/branchedetails.tsx
  89. 4
      libs/remix-ui/git/src/components/navigation/branches.tsx
  90. 4
      libs/remix-ui/git/src/components/navigation/clone.tsx
  91. 9
      libs/remix-ui/git/src/components/navigation/commands.tsx
  92. 6
      libs/remix-ui/git/src/components/navigation/commitdetails.tsx
  93. 5
      libs/remix-ui/git/src/components/navigation/commits.tsx
  94. 14
      libs/remix-ui/git/src/components/navigation/github.tsx
  95. 4
      libs/remix-ui/git/src/components/navigation/log.tsx
  96. 4
      libs/remix-ui/git/src/components/navigation/remotes.tsx
  97. 24
      libs/remix-ui/git/src/components/navigation/remotesdetails.tsx
  98. 4
      libs/remix-ui/git/src/components/navigation/settings.tsx
  99. 9
      libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx
  100. 6
      libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -14,4 +14,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-07-15T18:00:00Z'
freeze-date: '2024-08-12T18:00:00Z'

@ -18,6 +18,7 @@ export const scanAPIurls = {
59144: 'https://api.lineascan.build/api',
8453: 'https://api.basescan.org/api',
534352: 'https://api.scrollscan.com/api',
1116: 'https://openapi.coredao.org/api',
// all testnet
17000: 'https://api-holesky.etherscan.io/api',
@ -39,4 +40,5 @@ export const scanAPIurls = {
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
534351: 'https://api-sepolia.scrollscan.com/api',
1115: 'https://api.test.btcs.network/api',
}

@ -13,7 +13,7 @@ export const CaptureKeyView = () => {
const context = React.useContext(AppContext)
useEffect(() => {
if (!context.apiKey) setMsg('Please provide a 34-character API key to continue')
if (!context.apiKey) setMsg('Please provide a 34 or 32 character API key to continue')
}, [context.apiKey])
return (
@ -24,14 +24,14 @@ export const CaptureKeyView = () => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) {
errors.apiKey = 'API key should be 34 characters long'
} else if (values.apiKey.length !== 34 && values.apiKey.length !== 32) {
errors.apiKey = 'API key should be 34 or 32 characters long'
}
return errors
}}
onSubmit={(values) => {
const apiKey = values.apiKey
if (apiKey.length === 34) {
if (apiKey.length === 34 || apiKey.length === 32) {
context.setAPIKey(values.apiKey)
navigate(location && location.state ? location.state : '/')
}

@ -211,7 +211,7 @@ export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, netwo
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
etherscanScripts({}, client)
}}
>
Generate Verification Scripts

@ -31,9 +31,11 @@ function BackButton({entity}: any) {
</li>
{isDetailPage && (
<li className="nav-item">
<Link className="btn" to={`/list?id=${entity.id}`} title="Tutorial menu" onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-rightTutorialMenu">Tutorial menu</Tooltip>}>
<Link className="btn" to={`/list?id=${entity.id}`} onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
</OverlayTrigger>
</li>
)}
</ul>

@ -1,37 +1,57 @@
import React, {useEffect} from 'react'
import {useLocation, useNavigate} from 'react-router-dom'
import React, { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import BackButton from '../../components/BackButton'
import {useAppSelector, useAppDispatch} from '../../redux/hooks'
import { useAppSelector, useAppDispatch } from '../../redux/hooks'
import './index.scss'
import remixClient from '../../remix-client'
function StepDetailPage() {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useAppDispatch()
const [clonedStep, setClonedStep] = React.useState(null)
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId'))
const {
workshop: {detail, selectedId},
remixide: {errorLoadingFile, errors, success},
workshop: { detail, selectedId },
remixide: { errorLoadingFile, errors, success },
} = useAppSelector((state: any) => state)
const entity = detail[selectedId].entities[id]
const steps = entity.steps
const step = steps[stepId]
console.log(step)
useEffect(() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
dispatch({
type: 'remixide/save',
payload: {errors: [], success: false},
setClonedStep(null)
const clonedStep = JSON.parse(JSON.stringify(step))
const loadFiles = async () => {
async function loadFile(step, fileType) {
if (step[fileType] && step[fileType].file && !step[fileType].content) {
clonedStep[fileType].content = (await remixClient.call('contentImport', 'resolve', step[fileType].file)).content;
}
}
const fileTypes = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy'];
for (const fileType of fileTypes) {
await loadFile(step, fileType);
}
}
loadFiles().then(() => {
setClonedStep(clonedStep)
dispatch({
type: 'remixide/displayFile',
payload: clonedStep,
})
dispatch({
type: 'remixide/save',
payload: { errors: [], success: false },
})
window.scrollTo(0, 0)
})
window.scrollTo(0, 0)
}, [step])
useEffect(() => {
@ -40,8 +60,20 @@ function StepDetailPage() {
}
}, [errors, success])
if (!clonedStep) {
return (<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
loading...
</div>
)
}
return (
<>
<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
@ -51,13 +83,13 @@ function StepDetailPage() {
{errorLoadingFile ? (
<>
<div className="errorloadingspacer"></div>
<h1 className="pl-3 pr-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pl-3 pr-3 pt-3 pb-1">{clonedStep.name}</h1>
<button
className="w-100nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -68,13 +100,13 @@ function StepDetailPage() {
) : (
<>
<div className="menuspacer"></div>
<h1 className="pr-3 pl-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pr-3 pl-3 pt-3 pb-1">{clonedStep.name}</h1>
</>
)}
<div className="container-fluid">
<Markdown rehypePlugins={[rehypeRaw]}>{step.markdown?.content}</Markdown>
<Markdown rehypePlugins={[rehypeRaw]}>{clonedStep.markdown?.content}</Markdown>
</div>
{step.test?.content ? (
{clonedStep.test?.content ? (
<>
<nav className="nav nav-pills nav-fill">
{errorLoadingFile ? (
@ -83,7 +115,7 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -98,19 +130,19 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/testStep',
payload: step,
payload: clonedStep,
})
}}
>
Check Answer
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -130,13 +162,13 @@ function StepDetailPage() {
>
Next
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -185,13 +217,13 @@ function StepDetailPage() {
) : (
<>
<nav className="nav nav-pills nav-fill">
{!errorLoadingFile && step.answer?.content && (
{!errorLoadingFile && clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -223,7 +255,7 @@ function StepDetailPage() {
)}
</>
)}
</>
</div>
)
}

@ -84,7 +84,6 @@ const Model: ModelType = {
const { detail, selectedId } = yield select((state) => state.workshop)
const workshop = detail[selectedId]
console.log('loading ', step, workshop)
path = `.learneth/${workshop.name}/${step.name}/${path}`
try {
@ -138,14 +137,11 @@ const Model: ModelType = {
yield remixClient.call('fileManager', 'switchFile', `${path}`)
}
console.log('testing ', step.test.content)
path = getFilePath(step.test.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, step.test.content)
const result = yield remixClient.call('solidityUnitTesting', 'testFromPath', path)
console.log('result ', result);
if (!result) {
yield put({
@ -196,7 +192,6 @@ const Model: ModelType = {
toast.info('loading answer into IDE')
try {
console.log('loading ', step)
const content = step.answer.content
let path = getFilePath(step.answer.file)

@ -23,7 +23,7 @@ const Model: ModelType = {
},
effects: {
*init(_, { put }) {
const cache = localStorage.getItem('workshop.state')
const cache = null // don't use cache because remote might change
if (cache) {
const workshopState = JSON.parse(cache)
@ -54,7 +54,6 @@ const Model: ModelType = {
const { list, detail } = yield select((state) => state.workshop)
const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${payload.branch}?${Math.random()}`
console.log('loading ', url)
const { data } = yield axios.get(url)
const repoId = `${payload.name}-${payload.branch}`
@ -90,7 +89,7 @@ const Model: ModelType = {
const key = stepKeysWithFile[k]
if (step[key]) {
try {
step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
step[key].content = null // we load this later
} catch (error) {
console.error(error)
}

@ -10,7 +10,6 @@ class RemixClient extends PluginClient {
}
startTutorial(name: any, branch: any, id: any): void {
console.log('start tutorial', name, branch, id)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
@ -23,7 +22,6 @@ class RemixClient extends PluginClient {
}
addRepository(name: any, branch: any) {
console.log('add repo', name, branch)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',

@ -0,0 +1,34 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class HideMetaMaskPopup extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
suppressNotFoundErrors: true,
timeout: 2000
}, (okVisible) => {
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 2000
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = HideMetaMaskPopup

@ -0,0 +1,20 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class pinGrid extends EventEmitter {
command (this: NightwatchBrowser, provider: string, status: boolean): NightwatchBrowser {
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-another-chain"]`)
.click(`[data-id="dropdown-item-another-chain"]`)
.waitForElementVisible(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`)
.click(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = pinGrid

@ -8,15 +8,14 @@ class SelectFiles extends EventEmitter {
browser.perform(function () {
const actions = this.actions({ async: true })
actions.keyDown(this.Keys.SHIFT)
for(let i = 0; i < selectedElements.length; i++) {
for (let i = 0; i < selectedElements.length; i++) {
actions.click(selectedElements[i].value)
}
return actions.contextClick(selectedElements[0].value)
return actions//.contextClick(selectedElements[0].value)
})
this.emit('complete')
return this
}
}
module.exports = SelectFiles

@ -16,6 +16,7 @@ class MetaMask extends EventEmitter {
function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password: string, done: VoidFunction) {
const words = passphrase.split(' ')
console.log('setup metamask')
browser
.switchBrowserTab(1)
.waitForElementVisible('input[data-testid="onboarding-terms-checkbox"]')
@ -49,6 +50,7 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
.click('button[data-testid="pin-extension-next"]')
.waitForElementVisible('button[data-testid="pin-extension-done"]')
.click('button[data-testid="pin-extension-done"]')
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
@ -58,14 +60,22 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
}else{
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 3000
})
.saveScreenshot('./reports/screenshots/metamask.png')
.click('[data-testid="network-display"]')
.click('.mm-modal-content label.toggle-button--off') // show test networks
.click('div[data-testid="Sepolia"]') // switch to sepolia
.perform(() => {
console.log('MetaMask setup complete')
done()
})
}

@ -1,7 +1,9 @@
cd /tmp/
rm -rf git/bare.git
rm -rf git/bare2.git
rm -rf git
mkdir -p git
cd git
git clone --bare https://github.com/ethereum/awesome-remix bare.git
git clone --bare https://github.com/ethereum/awesome-remix bare2.git

@ -97,12 +97,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.addFile('contracts/lib/storage/src/Storage.sol', { content: storageContract})

@ -13,13 +13,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=semaphore]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementPresent('*[data-id="create-semaphore"]')
.scrollAndClick('*[data-id="create-semaphore"]')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
@ -155,12 +151,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=hashchecker]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-hashchecker"]')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
@ -190,8 +183,8 @@ module.exports = {
.journalLastChildIncludes('newZkey')
.pause(25000)
.journalLastChildIncludes('setup done.')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/groth16/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/groth16/zkey_final.txt"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/keys/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/keys/zkey_final.txt"]')
},
'Should run groth16 zkproof script for hash checker #group5': function (browser: NightwatchBrowser) {
browser
@ -210,8 +203,8 @@ module.exports = {
.journalLastChildIncludes('WITNESS CHECKING FINISHED SUCCESSFULLY')
.pause(2000)
.journalLastChildIncludes('zk proof validity')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/groth16/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/groth16/input.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/build/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/build/input.json"]')
},
'Should run plonk trusted setup script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
@ -228,8 +221,8 @@ module.exports = {
.journalLastChildIncludes('plonk setup')
.pause(10000)
.journalLastChildIncludes('setup done')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/plonk/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/plonk/zkey_final.txt"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/zkey_final.txt"]')
},
'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
@ -246,8 +239,8 @@ module.exports = {
.pause(5000)
.journalLastChildIncludes('zk proof validity')
.journalLastChildIncludes('proof done.')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/plonk/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/plonk/input.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/build/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/build/input.json"]')
}
}

@ -101,8 +101,8 @@ module.exports = {
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
@ -115,7 +115,8 @@ module.exports = {
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
locateStrategy: 'xpath',
timeout: 10000
})
},
@ -136,24 +137,24 @@ module.exports = {
},
'switch to branch links #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="branches-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-toggle-branch-links"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-toggle-current-branch-links"]',
locateStrategy: 'xpath'
})
},
'check the local branches #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="branches-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="branches-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]',
selector: '//*[@data-id="branches-panel-content-local-branches"]//*[@data-id="branches-toggle-current-branch-links"]',
locateStrategy: 'xpath'
})
},
@ -232,7 +233,7 @@ module.exports = {
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote-default"]',
locateStrategy: 'xpath'
})
},
@ -263,7 +264,7 @@ module.exports = {
}
})
},
'remove the remove #group2': function (browser: NightwatchBrowser) {
'remove the remote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
@ -278,7 +279,7 @@ module.exports = {
})
.pause(1000)
.waitForElementNotPresent({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote-default"]',
locateStrategy: 'xpath'
})
},

@ -24,14 +24,14 @@ module.exports = {
})
},
'run server #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'run server #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
gitserver = await spawnGitServer('/tmp/')
console.log('working directory', process.cwd())
done()
})
},
'Update settings for git #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'Update settings for git #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
@ -42,7 +42,7 @@ module.exports = {
.modalFooterOKClick('github-credentials-error')
.pause(2000)
},
'clone a repo #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'clone a repo #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="clone-panel"]')
.click('*[data-id="clone-panel"]')
@ -56,10 +56,11 @@ module.exports = {
// GROUP 1
'check file added #group1 #group3': function (browser: NightwatchBrowser) {
'check file added #group1 #group3 #group4': function (browser: NightwatchBrowser) {
browser.
addFile('test.txt', { content: 'hello world' }, 'README.md')
.clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']",
@ -75,7 +76,7 @@ module.exports = {
.setValue('*[data-id="commitMessage"]', 'testcommit')
.click('*[data-id="commitButton"]')
},
'look at the commit #group1': function (browser: NightwatchBrowser) {
'look at the commit #group1 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
@ -187,6 +188,7 @@ module.exports = {
'stage renamed file #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible({
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
@ -228,6 +230,7 @@ module.exports = {
'create a branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="branches-panel"]')
.waitForElementVisible('*[data-id="newbranchname"]')
.setValue('*[data-id="newbranchname"]', 'testbranch')
@ -244,6 +247,7 @@ module.exports = {
'publish the branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.pause(1000)
@ -321,9 +325,97 @@ module.exports = {
},
'check if test file is gone #group2': function (browser: NightwatchBrowser) {
browser
.pause()
.clickLaunchIcon('filePanel')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]')
}
},
'add second remote #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="add-manual-remoteurl"]')
.setValue('*[data-id="add-manual-remoteurl"]', 'http://localhost:6868/bare2.git')
.waitForElementVisible('*[data-id="add-manual-remotename"]')
.setValue('*[data-id="add-manual-remotename"]', 'origin2')
.waitForElementVisible('*[data-id="add-manual-remotebtn"]')
.click('*[data-id="add-manual-remotebtn"]')
},
'check the buttons #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="default-remote-check-origin"]')
.waitForElementVisible('*[data-id="set-as-default-origin2"]')
},
'check the commands #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
},
'switch to origin2 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="set-as-default-origin2"]')
.click('*[data-id="set-as-default-origin2"]')
},
'check the commands for origin2 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin2')]",
locateStrategy: 'xpath'
})
},
'sync the commit #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible('*[data-id="syncButton"]')
.click('*[data-id="syncButton"]')
.waitForElementVisible('*[data-id="commitButton"]')
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-"]',
locateStrategy: 'xpath'
})
},
'check the log #group4': async function (browser: NightwatchBrowser) {
const logs = await getGitLog('/tmp/git/bare2.git')
console.log(logs)
browser.assert.ok(logs.includes('testcommit'))
const logs2 = await getGitLog('/tmp/git/bare.git')
console.log(logs2)
browser.assert.fail(logs2.includes('testcommit'))
},
'switch to origin #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="set-as-default-origin"]')
.click('*[data-id="set-as-default-origin"]')
},
'check the commands for origin #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
},
'check the commit ahead #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible('*[data-id="syncButton"]')
// do not sync
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-ahead"]',
locateStrategy: 'xpath'
})
},
}
async function getBranches(path: string): Promise<string> {
@ -360,10 +452,10 @@ async function getGitLog(path: string): Promise<string> {
})
}
async function cloneOnServer(repo: string, path: string) {
async function cloneOnServer(repo: string, path: string, name: string = 'bare') {
console.log('cloning', repo, path)
return new Promise((resolve, reject) => {
const git = spawn('rm -rf bare && git', ['clone', repo], { cwd: path, shell: true, detached: true });
const git = spawn(`rm -rf ${name} && git`, ['clone', repo], { cwd: path, shell: true, detached: true });
git.stdout.on('data', function (data) {
console.log('stdout data cloning', data.toString());

@ -17,14 +17,12 @@ module.exports = {
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
// create contract
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-ozerc721"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')

@ -2,6 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
"@disabled": true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -10,11 +11,11 @@ module.exports = {
const selectedElements = []
browser
.openFile('contracts')
.click({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
.click({ selector: '//*[@data-id="treeViewDivtreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' },
browser.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemtests"]', locateStrategy: 'xpath' },
(el: any) => {
selectedElements.push(el)
})
@ -22,6 +23,74 @@ module.exports = {
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemtests"]')
.end()
},
'Should drag and drop multiple files in file explorer to tests folder #group1': function (browser: NightwatchBrowser) {
const selectedElements = []
if (browser.options.desiredCapabilities?.browserName === 'firefox') {
console.log('Skipping test for firefox')
browser.end()
return;
} else {
browser
.click({ selector: '//*[@data-id="treeViewUltreeViewMenu"]', locateStrategy: 'xpath' })
.click({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.selectFiles(selectedElements)
.perform((done) => {
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' },
(el: any) => {
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests/1_Storage.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests/2_Owner.sol"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.perform(() => done())
})
})
}
},
'should drag and drop multiple files and folders in file explorer to contracts folder #group3': function (browser: NightwatchBrowser) {
const selectedElements = []
if (browser.options.desiredCapabilities?.browserName === 'firefox') {
console.log('Skipping test for firefox')
browser.end()
return;
} else {
browser
.clickLaunchIcon('filePanel')
.click({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemscripts"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemREADME.txt"]', locateStrategy: 'xpath' },
(el: any) => {
selectedElements.push(el)
})
browser.selectFiles(selectedElements)
.perform((done) => {
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts"]', locateStrategy: 'xpath' },
(el: any) => {
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemtests"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/tests"]', 5000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]', 5000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]', 5000)
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemtests"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.perform(() => done())
})
})
}
}
}

@ -0,0 +1,36 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080?plugins=solidity,udapp', false)
},
'pin chain': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.pinGrid('vm-custom-fork', true)
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-vm-custom-fork"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
.pinGrid('vm-sepolia-fork', true)
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-vm-sepolia-fork"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
},
'unpin chain': function (browser: NightwatchBrowser) {
browser
.pinGrid('vm-custom-fork', false)
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementNotPresent(`[data-id="dropdown-item-vm-custom-fork"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
.pinGrid('vm-sepolia-fork', false)
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementNotPresent(`[data-id="dropdown-item-vm-sepolia-fork"]`)
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
}
}

@ -11,7 +11,7 @@ const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const checkAlerts = function (browser: NightwatchBrowser){
const checkAlerts = function (browser: NightwatchBrowser) {
browser.isVisible({
selector: '//*[contains(.,"not have enough")]',
locateStrategy: 'xpath',
@ -38,7 +38,7 @@ const tests = {
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -50,13 +50,14 @@ const tests = {
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
},
@ -83,6 +84,7 @@ const tests = {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -90,7 +92,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
},
'Should run low level interaction (fallback function) on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -102,14 +104,15 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
})
},
'Should connect to Ethereum Main Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -162,6 +165,8 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_4.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -169,7 +174,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.clearConsole()
.clickInstance(0)
@ -177,6 +182,8 @@ const tests = {
.perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_5.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -199,11 +206,11 @@ const tests = {
*/
'Should debug Sepolia transaction with source highlighting MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('pluginManager') // load debugger and source verification
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.clickLaunchIcon('udapp')
.perform((done) => {
browser.getLastTransactionHash((hash) => {
@ -213,15 +220,17 @@ const tests = {
})
.perform((done) => {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.saveScreenshot('./reports/screenshots/metamask_2.png')
.click('*[data-id="debuggerTransactionStartButton"]')
.saveScreenshot('./reports/screenshots/metamask_3.png')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
})
},
'Call web3.eth.getAccounts() using Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
@ -229,14 +238,14 @@ const tests = {
browser
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('["0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f"]')
}
}
}
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
...{} //(branch ? (isMasterBranch ? tests : {}) : tests),
};
const localsCheck = {
@ -250,7 +259,7 @@ const sources = [
{
'Greet.sol': {
content:
`
`
pragma solidity ^0.8.0;
contract HelloWorld {
string public message;

@ -180,16 +180,17 @@ module.exports = {
// creating a new workspace
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new')
.pause(2000)
.getValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', (result) => {
.getValue('input[data-id="modalDialogCustomPromptTextCreate"]', (result) => {
console.log(result)
browser.assert.equal(result.value, 'workspace_new')
})
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.click('*[data-id="fileSystem-modal-footer-ok-react"]')
.modalFooterOKClick('TemplatesSelection')
.pause(3000)
.currentWorkspaceIs('workspace_new')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')

@ -313,6 +313,7 @@ module.exports = {
'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
if (runMasterTests)
browser
.pinGrid('vm-custom-fork', true)
.switchEnvironment('vm-custom-fork')
.waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
.execute(() => {

@ -336,5 +336,18 @@ module.exports = {
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating fileManager and calling "open" ...')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating terminal and calling "log" ...')
},
'Import Github folder from URL params #group4': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#ghfolder=https://github.com/ethereum/remix-project/tree/master/apps/remix-ide/contracts/hardhat')
.refreshPage()
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]', 40000)
.currentWorkspaceIs('code-sample')
.openFile('contracts')
.openFile('contracts/Lock.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Lock {') !== -1, 'content does contain "contract Lock {"')
})
}
}

@ -38,12 +38,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
@ -110,14 +110,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-blank"]')
.scrollAndClick('*[data-id="create-blank"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=blank]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementPresent('*[data-id="treeViewUltreeViewMenu"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -133,14 +131,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc20"]')
.scrollAndClick('*[data-id="create-ozerc20"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc20]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc20')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -194,14 +190,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc721"]')
.scrollAndClick('*[data-id="create-ozerc721"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -255,14 +249,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc1155')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -316,17 +308,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`)
.scrollAndClick(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`)
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.waitForElementPresent('*[data-id="ozCustomization"]')
.click('*[data-id="featureTypeMintable"]')
.click('*[data-id="featureTypeBurnable"]')
.click('*[data-id="featureTypePausable"]')
.click('*[data-id="upgradeTypeUups"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -386,12 +371,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-hashchecker"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=hashchecker]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
@ -424,11 +407,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.addFile('test.sol', { content: 'test' })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
@ -438,11 +422,12 @@ module.exports = {
})
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.switchWorkspace('workspace_name')
@ -494,13 +479,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'sometestworkspace' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'sometestworkspace')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -521,14 +505,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_db_test' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_db_test')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -551,14 +533,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]')
.scrollAndClick('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4HookBookMultiSigSwapHook]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'multisig cookbook' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'multisig cookbook')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('[data-id="PermissionHandler-modal-footer-ok-react"]', 300000)
.click('[data-id="PermissionHandler-modal-footer-ok-react"]')
// click on lib to close it

@ -13,8 +13,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.waitForElementVisible({
selector: "//*[@class='text-warning' and contains(.,'add username and email')]",
locateStrategy: 'xpath'
@ -23,10 +24,10 @@ module.exports = {
selector: '//*[@data-id="initGitRepository"][@disabled]',
locateStrategy: 'xpath'
})
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.click('[data-id="initGitRepositoryLabel"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItem.git"]')
@ -47,15 +48,13 @@ module.exports = {
.waitForElementNotVisible('[data-id="workspaceGitPanel"]')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-blank"]')
.scrollAndClick('*[data-id="create-blank"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=blank]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.click('[data-id="initGitRepositoryLabel"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('[data-id="workspaceGitPanel"]')
.waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main')
@ -391,12 +390,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4Template"]')
.scrollAndClick('*[data-id="create-uniswapV4Template"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
@ -413,14 +410,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc20"]')
.scrollAndClick('*[data-id="create-ozerc20"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc20]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'new_workspace' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'new_workspace')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemtests/MyToken_test.sol"]')
},
'Update settings for git #group5': function (browser: NightwatchBrowser) {
@ -468,13 +463,11 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4Template"]')
.scrollAndClick('*[data-id="create-uniswapV4Template"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
.openFile('src/Counter.sol')
@ -491,11 +484,11 @@ module.exports = {
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin" and contains(.,"v4-template")]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default" and contains(.,"v4-template")]',
locateStrategy: 'xpath'
})
},

@ -48,6 +48,7 @@ declare module 'nightwatch' {
removeFile(path: string, workspace: string): NightwatchBrowser
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser
setupMetamask(passphrase: string, password: string): NightwatchBrowser
hideMetaMaskPopup(): NightwatchBrowser
signMessage(msg: string, callback: (hash: {value: string}, signature: {value: string}) => void): NightwatchBrowser
setSolidityCompilerVersion(version: string): NightwatchBrowser
clickElementAtPosition(cssSelector: string, index: number, opt?: {forceSelectIfUnselected: boolean}): NightwatchBrowser
@ -69,6 +70,7 @@ declare module 'nightwatch' {
currentSelectedFileIs(name: string): NightwatchBrowser
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
pinGrid: (provider: string, status: boolean) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
waitForElementNotContainsText: (id: string, value: string, timeout: number = 10000) => NightwatchBrowser
hideToolTips: (this: NightwatchBrowser) => NightwatchBrowser

@ -3,13 +3,13 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init
git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR"

@ -3,13 +3,13 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init
git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR"

@ -3,13 +3,13 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
cd dist/apps/remix-ide
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows/gh-actions-deploy.yml
cd dist/apps/remix-ide
git init
git checkout -b gh-pages
git config user.name "$COMMIT_AUTHOR"

@ -25,21 +25,22 @@ import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import { Registry } from '@remix-project/remix-lib'
import { ConfigPlugin } from './app/plugins/config'
import { StoragePlugin } from './app/plugins/storage'
import { Layout } from './app/panels/layout'
import { NotificationPlugin } from './app/plugins/notification'
import { Blockchain } from './blockchain/blockchain'
import { MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider, CancunVMProvider } from './app/providers/vm-provider'
import { MainnetForkVMProvider } from './app/providers/mainnet-vm-fork-provider'
import { SepoliaForkVMProvider } from './app/providers/sepolia-vm-fork-provider'
import { GoerliForkVMProvider } from './app/providers/goerli-vm-fork-provider'
import { CustomForkVMProvider } from './app/providers/custom-vm-fork-provider'
import { HardhatProvider } from './app/providers/hardhat-provider'
import { GanacheProvider } from './app/providers/ganache-provider'
import { FoundryProvider } from './app/providers/foundry-provider'
import { ExternalHttpProvider } from './app/providers/external-http-provider'
import {Registry} from '@remix-project/remix-lib'
import {ConfigPlugin} from './app/plugins/config'
import {StoragePlugin} from './app/plugins/storage'
import {Layout} from './app/panels/layout'
import {NotificationPlugin} from './app/plugins/notification'
import {Blockchain} from './blockchain/blockchain'
import {MergeVMProvider, LondonVMProvider, BerlinVMProvider, ShanghaiVMProvider, CancunVMProvider} from './app/providers/vm-provider'
import {MainnetForkVMProvider} from './app/providers/mainnet-vm-fork-provider'
import {SepoliaForkVMProvider} from './app/providers/sepolia-vm-fork-provider'
import {GoerliForkVMProvider} from './app/providers/goerli-vm-fork-provider'
import {CustomForkVMProvider} from './app/providers/custom-vm-fork-provider'
import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider'
import { EnvironmentExplorer } from './app/providers/environment-explorer'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
@ -69,6 +70,8 @@ import { Matomo } from './app/plugins/matomo'
import { TemplatesSelectionPlugin } from './app/plugins/templates-selection/templates-selection-plugin'
const isElectron = require('is-electron')
const remixLib = require('@remix-project/remix-lib')
@ -293,6 +296,8 @@ class AppComponent {
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
const environmentExplorer = new EnvironmentExplorer()
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
@ -329,6 +334,8 @@ class AppComponent {
// ----------------- run script after each compilation results -----------
const pluginStateLogger = new PluginStateLogger()
const templateSelection = new TemplatesSelectionPlugin()
this.engine.register([
permissionHandler,
this.layout,
@ -367,6 +374,7 @@ class AppComponent {
ganacheProvider,
foundryProvider,
externalHttpProvider,
environmentExplorer,
this.walkthroughService,
search,
solidityumlgen,
@ -379,7 +387,8 @@ class AppComponent {
solcoder,
git,
pluginStateLogger,
matomo
matomo,
templateSelection
])
//---- fs plugin
@ -533,7 +542,7 @@ class AppComponent {
])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgit'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgitApi', 'dgit'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()) {

@ -348,7 +348,6 @@ class Editor extends Plugin {
}
async openDiff(change) {
console.log('openDiff', change)
const hashedPathModified = change.readonly ? change.path + change.hashModified : change.path
const hashedPathOriginal = change.path + change.hashOriginal
const session = await this._createSession(hashedPathModified, change.modified, this._getMode(change.path), change.readonly)
@ -458,7 +457,6 @@ class Editor extends Plugin {
revealRange (startLineNumber, startColumn, endLineNumber, endColumn) {
if (!this.activated) return
this.emit('focus')
console.log(startLineNumber, startColumn, endLineNumber, endColumn)
this.emit('revealRange', startLineNumber, startColumn, endLineNumber, endColumn)
}

@ -40,6 +40,7 @@ const errorMsg = {
const createError = (err) => {
return new Error(`${errorMsg[err.code]} ${err.message || ''}`)
}
const _paq = (window._paq = window._paq || [])
class FileManager extends Plugin {
mode: string
openedFiles: any
@ -215,6 +216,11 @@ class FileManager extends Plugin {
} else {
const ret = await this.setFileContent(path, data, options)
this.emit('fileAdded', path)
// Temporary solution to tracking scripts execution for zk in matomo
if (path === 'scripts/groth16/zk/keys/zkey_final.txt' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'groth16', 'zk trusted setup done'])
if (path === 'scripts/groth16/zk/build/zk_verifier.sol' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'groth16', 'zk proof done'])
if (path === 'scripts/plonk/zk/keys/zkey_final.txt' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'plonk', 'zk trusted setup done'])
if (path === 'scripts/plonk/zk/build/zk_verifier.sol' && data) _paq.push(['trackEvent', 'circuit-compiler', 'script', 'plonk', 'zk proof done'])
return ret
}
} catch (e) {
@ -962,6 +968,12 @@ class FileManager extends Plugin {
return exists
}
/**
* Check if a file can be moved
* @param src source file
* @param dest destination file
* @returns {boolean} true if the file is allowed to be moved
*/
async moveFileIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
@ -984,6 +996,12 @@ class FileManager extends Plugin {
}
}
/**
* Check if a folder can be moved
* @param src source folder
* @param dest destination folder
* @returns {boolean} true if the folder is allowed to be moved
*/
async moveDirIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)

@ -189,7 +189,7 @@ module.exports = class Filepanel extends ViewPlugin {
if (err) reject(err)
else resolve(data || true)
})
})
}, false)
}
renameWorkspace(oldName, workspaceName) {

@ -33,7 +33,9 @@ export class Layout extends Plugin {
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.maximised = {
'dgit': true
}
this.event = new EventEmitter()
}
@ -65,7 +67,6 @@ export class Layout extends Plugin {
this.event.emit('change', null)
})
this.on('tabs', 'openDiff', () => {
console.log('openDiff')
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
@ -126,15 +127,15 @@ export class Layout extends Plugin {
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
this.event.emit('maximisesidepanel')
}
async maximisePinnedPanel () {
this.event.emit('maximisepinnedpanel')
const current = await this.call('pinnedPanel', 'currentFocus')
this.maximised[current] = true
this.event.emit('maximisepinnedpanel')
}
async maximizeTerminal() {
@ -144,14 +145,14 @@ export class Layout extends Plugin {
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
this.event.emit('resetsidepanel')
}
async resetPinnedPanel () {
this.event.emit('resetpinnedpanel')
const current = await this.call('pinnedPanel', 'currentFocus')
this.maximised[current] = false
this.event.emit('resetpinnedpanel')
}
}

@ -1,7 +1,7 @@
'use strict'
import { ViewPlugin } from '@remixproject/engine-web';
import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react' // eslint-disable-line
import { gitState, GitUI } from '@remix-ui/git';
import { gitState, GitUI } from '@remix-ui/git'
import * as packageJson from '../../../../../package.json'
const profile = {

@ -11,7 +11,7 @@ const profile = {
version: '1.0.0'
}
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner']
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'dgit']
export class Matomo extends Plugin {

@ -123,6 +123,8 @@ export class RemixGuidePlugin extends ViewPlugin {
expandViewEl={
cell.expandViewElement
}
key={cell.title}
id={cell.title}
handleExpand={() => {
this.showVideo = true
this.videoID = cell.expandViewElement.videoID

@ -0,0 +1,12 @@
.TSCellStyle {
min-height: 8.5rem;
max-width: 13rem;
min-width: 13rem;
max-height: 8.5rem;
}
.badgeForCell {
max-width: fit-content;
padding-right: 0.5rem;
font-size: smaller;
}

@ -0,0 +1,269 @@
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { CustomTooltip } from "@remix-ui/helper"
import { AppModal } from '@remix-ui/app'
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell'
import isElectron from 'is-electron'
import type { TemplateGroup } from '@remix-ui/workspace'
import './templates-selection-plugin.css'
import { templates } from './templates'
//@ts-ignore
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'templateSelection',
displayName: 'Template Selection',
description: 'templateSelection',
location: 'mainPanel',
methods: [],
events: [],
maintainedBy: 'Remix',
}
export class TemplatesSelectionPlugin extends ViewPlugin {
templates: Array<TemplateGroup>
dispatch: React.Dispatch<any> = () => { }
constructor() {
super(profile)
}
async onActivation() {
this.handleThemeChange()
await this.call('tabs', 'focus', 'templateSelection')
this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide'])
}
onDeactivation(): void {
}
private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => {
this.renderComponent()
})
}
setDispatch(dispatch: React.Dispatch<any>): void {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div className="bg-dark" id="remixGuide">
<PluginViewWrapper plugin={this} />
</div>
)
}
renderComponent() {
this.dispatch({
...this,
})
}
updateComponent() {
/*
This represents the different options available from the openzeppelin library.
const opts = {
// @ts-ignore: Object is possibly 'null'.
mintable: mintableCheckboxRef.current.checked,
// @ts-ignore: Object is possibly 'null'.
burnable: burnableCheckboxRef.current.checked,
// @ts-ignore: Object is possibly 'null'.
pausable: pausableCheckboxRef.current.checked,
// @ts-ignore: Object is possibly 'null'.
upgradeable: transparentRadioRef.current.checked ? transparentRadioRef.current.value : uupsRadioRef.current.checked ? uupsRadioRef.current.value : false
}
*/
const createWorkspace = async (item) => {
const defaultName = await this.call('filePanel', 'getAvailableWorkspaceName', item.displayName)
const username = await this.call('settings', 'get', 'settings/github-user-name')
const email = await this.call('settings', 'get', 'settings/github-email')
const gitNotSet = !username || !email
let workspaceName = defaultName
let initGit = false
const modal: AppModal = {
id: 'TemplatesSelection',
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }),
message: await createModalMessage(defaultName, gitNotSet, (value) => workspaceName = value, (value) => initGit = !!value),
okLabel: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.ok':'filePanel.selectFolder' }),
}
const modalResult = await this.call('notification', 'modal', modal)
if (!modalResult) return
this.emit('createWorkspaceReducerEvent', workspaceName, item.value, item.opts, false, async (e, data) => {
if (e) {
const modal: AppModal = {
id: 'TemplatesSelection',
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }),
message: e.message,
okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }),
cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' })
}
await this.call('notification', 'modal', modal)
console.error(e)
}
}, initGit)
}
const addToCurrentWorkspace = async (item) => {
this.emit('addTemplateToWorkspaceReducerEvent', item.value, item.opts, false, async (e, data) => {
if (e) {
const modal: AppModal = {
id: 'TemplatesSelection',
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }),
message: e.message,
okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }),
cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' })
}
await this.call('notification', 'modal', modal)
console.error(e)
} else {
this.call('notification', 'toast', 'Files Added.')
}
})
}
return (
<RemixUIGridView
plugin={this}
styleList={""}
logo='/assets/img/bgRemi.webp'
enableFilter={true}
showUntagged={true}
showPin={false}
tagList={[
['Solidity', 'danger'],
['ZKP', 'warning'],
['ERC20', 'success'],
['ERC721', 'secondary'],
['ERC1155', 'primary'],
]}
title='Template explorer'
description="Select a template to create a workspace or to add it to current workspace"
>
{
templates(window._intl).map(template => {
return <RemixUIGridSection
plugin={this}
key={template.name}
title={template.name}
hScrollable={false}
>
{template.items.map(item => {
return <RemixUIGridCell
plugin={this}
title={item.displayName}
key={item.name}
id={item.name}
searchKeywords={[item.displayName, item.description, template.name]}
tagList={item.tagList}
classList='TSCellStyle'
>
<div className='d-flex justify-content-between h-100 flex-column'>
<div className='d-flex flex-column'>
<div>
{item.description && <span className='text-dark'>{item.description}</span>}
</div>
<div className='d-flex flex-wrap'>
{(item.opts && item.opts.upgradeable && item.opts.upgradeable === 'uupds') && <span className='badgeForCell badge text-secondary'>Upgradeable-UUPS</span>}
{(item.opts && item.opts.mintable) && <span className='badgeForCell text-secondary'>mintable</span>}
{(item.opts && item.opts.burnable) && <span className='badgeForCell text-secondary'>burnable</span>}
{(item.opts && item.opts.pausable) && <span className='badgeForCell text-secondary'>pausable</span>}
</div>
</div>
<div className='align-items-center justify-content-between w-100 d-flex pt- flex-row'>
{(!template.IsArtefact || !item.isArtefact) && <CustomTooltip
placement="auto"
tooltipId={`overlay-tooltip-new${item.name}`}
tooltipText="Create a new workspace"
>
<span
data-id={`create-${item.value}${item.opts ? JSON.stringify(item.opts) : ''}`}
onClick={async () => createWorkspace(item)}
className="btn btn-sm mr-2 border border-primary"
>
Create
</span>
</CustomTooltip>}
<CustomTooltip
placement="auto"
tooltipId={`overlay-tooltip-add${item.name}`}
tooltipText="Add template files to current workspace"
>
<span
data-id={`add-${item.value}`}
onClick={async () => addToCurrentWorkspace(item)}
className="btn btn-sm border"
>
Add to current
</span>
</CustomTooltip>
</div>
</div>
</RemixUIGridCell>
})}
</RemixUIGridSection>
})}
</RemixUIGridView>
)
}
}
const createModalMessage = async (
defaultName: string,
gitConfigNotSet: boolean,
onChangeTemplateName: (name: string) => void,
onChangeInitGit: (name: string) => void) => {
return (
<>
<label id="wsName" className="form-check-label" style={{ fontWeight: 'bolder' }}>
<FormattedMessage id="filePanel.workspaceName" />
</label>
<input
type="text"
data-id="modalDialogCustomPromptTextCreate"
defaultValue={defaultName}
className="form-control"
onChange={(e) => onChangeTemplateName(e.target.value)}
onInput={(e) => onChangeTemplateName((e.target as any).value)}
/>
<div className="d-flex py-2 align-items-center custom-control custom-checkbox">
<input
id="initGitRepository"
data-id="initGitRepository"
className="form-check-input custom-control-input"
type="checkbox"
disabled={gitConfigNotSet}
onChange={(e) => onChangeInitGit(e.target.value)}
onInput={(e) => onChangeInitGit((e.target as any).value)}
/>
<label
htmlFor="initGitRepository"
data-id="initGitRepositoryLabel"
className="m-0 form-check-label custom-control-label udapp_checkboxAlign"
title={window._intl.formatMessage({ id: 'filePanel.initGitRepoTitle' })}
>
<FormattedMessage id="filePanel.initGitRepositoryLabel" />
</label>
</div>
{gitConfigNotSet ? (
<div className="text-warning">
<FormattedMessage id="filePanel.initGitRepositoryWarning" />
</div>
) : (
<></>
)}
</>
)
}

@ -0,0 +1,357 @@
export const templates = (intl) => {
return [
{
name: "Generic",
items: [
{ value: "remixDefault", tagList: ["Solidity"], displayName: intl.formatMessage({ id: 'filePanel.basic' }), description: 'A default project' },
{ value: "blank", displayName: intl.formatMessage({ id: 'filePanel.blank' }), IsArtefact: true, description: 'A blank project' }
]
},
{
name: "OpenZeppelin",
items: [
{
value: "ozerc20",
displayName: "ERC20",
tagList: ["ERC20", "Solidity"],
description: 'A simple ERC20 project'
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
tagList: ["ERC721", "Solidity"],
description: 'A simple ERC721 (aka NFT) project'
},
{
value: "ozerc1155",
tagList: ["Solidity"],
displayName: "ERC1155",
description: 'A simple ERC1155 (multi token) project'
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
tagList: ["Solidity"],
opts: {
mintable: true
}
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
tagList: ["Solidity", "ERC721"],
opts: {
mintable: true
}
},
{
value: "ozerc1155",
displayName: "ERC1155",
tagList: ["Solidity"],
description: "A standard interface for contracts that manage multiple token types",
opts: {
mintable: true
}
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
tagList: ["Solidity", "ERC20"],
opts: {
mintable: true,
burnable: true
},
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
mintable: true,
burnable: true
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
mintable: true,
burnable: true
},
tagList: ["ERC1155", "Solidity"]
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
opts: {
mintable: true,
pausable: true
},
tagList: ["ERC20", "Solidity"]
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
mintable: true,
pausable: true
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
tagList: ["ERC20"],
opts: {
mintable: true,
pausable: true
}
}
]
},
{
name: "OpenZeppelin Proxy",
items: [
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
opts: {
upgradeable: 'uups'
},
tagList: ["ERC20", "Solidity"]
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
upgradeable: 'uups'
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
upgradeable: 'uups'
},
tagList: ["ERC1155", "Solidity"]
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
opts: {
upgradeable: 'uups',
mintable: true
},
tagList: ["ERC20", "Solidity"]
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
upgradeable: 'uups',
mintable: true
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
upgradeable: 'uups',
mintable: true
},
tagList: ["ERC1155", "Solidity"]
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
opts: {
upgradeable: 'uups',
mintable: true,
burnable: true
},
tagList: ["ERC20", "Solidity"]
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
upgradeable: 'uups',
mintable: true,
burnable: true
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
upgradeable: 'uups',
mintable: true,
burnable: true
},
tagList: ["ERC1155", "Solidity"]
},
{
value: "ozerc20",
displayName: "ERC20",
description: "A standard interface for fungible tokens",
opts: {
upgradeable: 'uups',
mintable: true,
pausable: true
},
tagList: ["ERC20", "Solidity"]
},
{
value: "ozerc721",
displayName: "ERC721 (NFT)",
description: "Non-fungible Token Standard",
opts: {
upgradeable: 'uups',
mintable: true,
pausable: true
},
tagList: ["ERC721", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
upgradeable: 'uups',
mintable: true,
pausable: true
},
tagList: ["ERC1155", "Solidity"]
},
{
value: "ozerc1155",
displayName: "ERC1155",
description: "A standard interface for contracts that manage multiple token types",
opts: {
upgradeable: 'uups',
mintable: true,
burnable: true,
pausable: true
},
tagList: ["ERC1155", "Solidity"]
}
]
},
{
name: "OxProject",
items: [
{ value: "zeroxErc20", displayName: "ERC20", tagList: ["ERC20", "Solidity"], description: "A standard interface for fungible tokens by 0xProject" }
]
},
{
name: "Gnosis Safe",
items: [
{ value: "gnosisSafeMultisig", tagList: ["Solidity"], displayName: intl.formatMessage({ id: 'filePanel.multiSigWallet' }), description: 'Deploy or Customize the Gnosis Safe.' }
]
},
{
name: "Circom ZKP",
items: [
{ value: "semaphore", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.semaphore' }), description: 'Run a ZK Semaphore circom circuit.' },
{ value: "hashchecker", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.hashchecker' }), description: 'Run a ZK Hash checker circom circuit.' },
{ value: "rln", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.rln' }), description: 'Run a Rate Limiting Nullifier circom circuit.' }
]
},
{
name: "Generic ZKP",
items: [
{
value: "sindriScripts",
tagList: ["ZKP"],
displayName: intl.formatMessage({ id: 'filePanel.addscriptsindri' }),
description: 'Use the Sindri API to compile and generate proof.'
},
],
},
{
name: "Uniswap V4",
items: [
{ value: "uniswapV4Template",
displayName: intl.formatMessage({ id: 'filePanel.uniswapV4Template' }),
description: 'Use an Uniswap hook'
},
{
value: "breakthroughLabsUniswapv4Hooks",
displayName: intl.formatMessage({ id: 'filePanel.breakthroughLabsUniswapv4Hooks' }),
description: 'Use an Uniswap hook developed by Breakthrough Labs'
},
{
value: "uniswapV4HookBookMultiSigSwapHook",
displayName: intl.formatMessage({ id: 'filePanel.uniswapV4HookBookMultiSigSwapHook' }),
description: 'Use a MultiSigSwapHook developed by Breakthrough Labs'
}
]
},
{
name: "Solidity CREATE2",
items: [
{
value: "contractCreate2Factory",
tagList: ["Solidity"],
displayName: intl.formatMessage({ id: 'filePanel.addcreate2solidityfactory' }),
description: 'Factory for deploying a Contract using the CREATE2 opcode.'
},
{
value: "contractDeployerScripts",
displayName: intl.formatMessage({ id: 'filePanel.addscriptdeployer' }),
description: 'Script for deploying a Contract using the CREATE2 opcode.'
}
]
},
{
name: "Contract Verification",
items: [
{
value: "etherscanScripts",
displayName: intl.formatMessage({ id: 'filePanel.addscriptetherscan' }),
description: 'Script for verifying a Contract in Etherscan.'
},
],
},
{
name: 'Github Actions',
items: [
{ value: "runJsTestAction",
displayName: intl.formatMessage({ id: 'filePanel.tssoltestghaction' }),
description: 'A Mocha Chai Test Workflow in a GitHub CI.'
},
{ value: "runSolidityUnittestingAction",
displayName: intl.formatMessage({ id: 'filePanel.solghaction' }),
description: 'Run a Solidity Unittest Workflow in a GitHub CI.'
},
{
value: "runSlitherAction",
displayName: intl.formatMessage({ id: 'filePanel.slitherghaction' }),
description: 'Run a Slither Security Analysis in a GitHub CI.'
}
],
IsArtefact: true
}
]
}

@ -0,0 +1,207 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import { PluginViewWrapper } from '@remix-ui/helper'
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view'
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section'
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell'
import './style/environment-explorer.css'
import type { Provider } from '../../blockchain/blockchain'
import * as packageJson from '../../../../../package.json'
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'environmentExplorer',
displayName: 'Environment Explorer',
icon: 'assets/img/EnvironmentExplorerLogo.webp',
description: 'Customize the Environments list in Deploy & Run',
location: 'mainPanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html',
version: packageJson.version,
maintainedBy: 'Remix',
permission: true,
events: [],
methods: []
}
type ProvidersSection = `Injected` | 'Remix VMs' | 'Externals'
export class EnvironmentExplorer extends ViewPlugin {
providers: { [key in ProvidersSection]: Provider[] }
providersFlat: { [key: string]: Provider }
pinnedProviders: string[]
dispatch: React.Dispatch<any> = () => {}
constructor() {
super(profile)
this.providersFlat = {}
this.providers = {
'Injected': [],
'Remix VMs': [],
'Externals': []
}
}
async onActivation(): Promise<void> {
this.providersFlat = await this.call('blockchain', 'getAllProviders')
this.pinnedProviders = await this.call('blockchain', 'getPinnedProviders')
this.renderComponent()
}
addProvider (provider: Provider) {
if (provider.isInjected) {
this.providers['Injected'].push(provider)
} else if (provider.isVM) {
this.providers['Remix VMs'].push(provider)
} else {
this.providers['Externals'].push(provider)
}
}
setDispatch(dispatch: React.Dispatch<any>): void {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div className="bg-dark" id="environmentExplorer">
<PluginViewWrapper plugin={this} />
</div>
)
}
renderComponent() {
this.dispatch({
...this
})
}
updateComponent(state: any) {
this.providers = {
'Injected': [],
'Remix VMs': [],
'Externals': []
}
for (const [key, provider] of Object.entries(this.providersFlat)) {
this.addProvider(provider)
}
return (
<RemixUIGridView
plugin={this}
styleList={""}
logo={profile.icon}
enableFilter={true}
showUntagged={true}
showPin={true}
title={profile.description}
description="Select the providers and chains to include them in the ENVIRONMENT select box of the Deploy & Run Transactions plugin."
>
<RemixUIGridSection
plugin={this}
title='Deploy using a Browser Extension.'
hScrollable={false}
>
{this.providers['Injected'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.displayName}
logos={provider.logos}
classList='EECellStyle'
searchKeywords={['Injected', provider.name, provider.displayName, provider.title, provider.description]}
pinned={this.pinnedProviders.includes(provider.name)}
key={provider.name}
id={provider.name}
pinStateCallback={async (pinned: boolean) => {
if (pinned) {
this.emit('providerPinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`)
return true
}
const providerName = await this.call('blockchain', 'getProvider')
if (providerName !== provider.name) {
this.emit('providerUnpinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`)
return true
} else {
this.call('notification', 'toast', 'Cannot unpin the current selected provider')
return false
}
}}
>
<div>{provider.description}</div>
</RemixUIGridCell>
})}
</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Deploy to an In-browser Virtual Machine.'
hScrollable={false}
>{this.providers['Remix VMs'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.displayName}
logos={provider.logos}
classList='EECellStyle'
searchKeywords={['Remix VMs', provider.name, provider.displayName, provider.title, provider.description]}
pinned={this.pinnedProviders.includes(provider.name)}
key={provider.name}
id={provider.name}
pinStateCallback={async (pinned: boolean) => {
if (pinned) {
this.emit('providerPinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`)
return true
}
const providerName = await this.call('blockchain', 'getProvider')
if (providerName !== provider.name) {
this.emit('providerUnpinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`)
return true
} else {
this.call('notification', 'toast', 'Cannot unpin the current selected provider')
return false
}
}}
>
<div>{provider.description}</div>
</RemixUIGridCell>
})}</RemixUIGridSection>
<RemixUIGridSection
plugin={this}
title='Deploy to an external Provider.'
hScrollable={false}
>{this.providers['Externals'].map(provider => {
return <RemixUIGridCell
plugin={this}
title={provider.displayName}
logos={provider.logos}
classList='EECellStyle'
searchKeywords={['Externals', provider.name, provider.displayName, provider.title, provider.description]}
pinned={this.pinnedProviders.includes(provider.name)}
key={provider.name}
id={provider.name}
pinStateCallback={async (pinned: boolean) => {
if (pinned) {
this.emit('providerPinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`)
return true
}
const providerName = await this.call('blockchain', 'getProvider')
if (providerName !== provider.name) {
this.emit('providerUnpinned', provider.name, provider)
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`)
return true
} else {
this.call('notification', 'toast', 'Cannot unpin the current selected provider')
return false
}
}}
>
<div>{provider.description}</div>
</RemixUIGridCell>
})}</RemixUIGridSection>
</RemixUIGridView>
)
}
}

@ -0,0 +1,5 @@
.EECellStyle {
min-height: 6rem;
max-width: 12rem;
min-width: 10rem;
}

@ -129,6 +129,42 @@ export class RunTab extends ViewPlugin {
async onInitDone() {
const udapp = this // eslint-disable-line
const descriptions = {
'vm-cancun': 'Deploy to the in-browser virtual machine running the Cancun fork.',
'vm-shanghai': 'Deploy to the in-browser virtual machine running the Shanghai fork.',
'vm-paris': 'Deploy to the in-browser virtual machine running the Paris fork.',
'vm-london': 'Deploy to the in-browser virtual machine running the London fork.',
'vm-berlin': 'Deploy to the in-browser virtual machine running the Berlin fork.',
'vm-mainnet-fork': 'Deploy to a fork of the Ethereum mainnet in the in-browser virtual machine.',
'vm-sepolia-fork': 'Deploy to a fork of the Sepolia testnet in the in-browser virtual machine.',
'vm-custom-fork': 'Deploy to a fork of a custom network in the in-browser virtual machine.',
'walletconnect': 'Deploy using WalletConnect.',
'basic-http-provider': 'Deploy to a Custom local network.',
'hardhat-provider': 'Deploy to the local Hardhat dev chain.',
'ganache-provider': 'Deploy to the local Ganache dev chain.',
'foundry-provider': 'Deploy to the local Foundry dev chain.',
'injected-MetaMask': 'Deploy through the Metamask browser extension.',
'injected-Brave Wallet': 'Deploy through the Brave Wallet extension.',
'injected-Brave': 'Deploy through the Brave browser extension.',
'injected-metamask-optimism': 'Deploy to Optimism through the Metamask browser extension.',
'injected-metamask-arbitrum': 'Deploy to Arbitrum through the Metamask browser extension.',
'injected-metamask-sepolia': 'Deploy to the Sepolia testnet through the Metamask browser extension.',
'injected-metamask-ephemery': 'Deploy to the Ephemery testnet through the Metamask browser extension.'
}
const logos = {
'injected-metamask-optimism': ['assets/img/optimism-ethereum-op-logo.png', 'assets/img/metamask.png'],
'injected-metamask-arbitrum': ['assets/img/arbitrum-arb-logo.png', 'assets/img/metamask.png'],
'injected-metamask-sepolia': ['assets/img/metamask.png'],
'injected-metamask-ephemery': ['assets/img/metamask.png'],
'injected-MetaMask': ['assets/img/metamask.png'],
'injected-Brave Wallet': ['assets/img/brave.png'],
'injected-Trust Wallet': ['assets/img/trust-wallet.png'],
'hardhat-provider': ['assets/img/hardhat.png'],
'walletconnect': ['assets/img/Walletconnect-logo.png'],
'foundry-provider': ['assets/img/foundry.png']
}
const addProvider = async (position, name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
position,
@ -136,6 +172,8 @@ export class RunTab extends ViewPlugin {
dataId,
name,
displayName,
description: descriptions[name] || displayName,
logos: logos[name],
fork,
isInjected,
isVM,
@ -167,15 +205,15 @@ export class RunTab extends ViewPlugin {
await addProvider(0, name, displayName, true, false, false)
if (event.detail.info.name === 'MetaMask') {
await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism', '0xa', ['https://mainnet.optimism.io'])
await addCustomInjectedProvider(8, event, 'injected-metamask-arbitrum', 'L2 - Arbitrum', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
await addCustomInjectedProvider(5, event, 'injected-metamask-sepolia', 'Testnet - Sepolia', '0xaa36a7', [],
await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism - ' + event.detail.info.name, '0xa', ['https://mainnet.optimism.io'])
await addCustomInjectedProvider(8, event, 'injected-metamask-arbitrum', 'L2 - Arbitrum - ' + event.detail.info.name, '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
await addCustomInjectedProvider(5, event, 'injected-metamask-sepolia', 'Sepolia Testnet - ' + event.detail.info.name, '0xaa36a7', [],
{
"name": "Sepolia ETH",
"symbol": "ETH",
"decimals": 18
})
await addCustomInjectedProvider(9, event, 'injected-metamask-ephemery', 'Ephemery Testnet', '', ['https://otter.bordel.wtf/erigon', 'https://eth.ephemeral.zeus.fyi'],
await addCustomInjectedProvider(9, event, 'injected-metamask-ephemery', 'Ephemery Testnet - ' + event.detail.info.name, '', ['https://otter.bordel.wtf/erigon', 'https://eth.ephemeral.zeus.fyi'],
{
"name": "Ephemery ETH",
"symbol": "ETH",
@ -192,7 +230,7 @@ export class RunTab extends ViewPlugin {
}
}
// VM
// VM
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider(1, 'vm-cancun', 'Remix VM (Cancun)', false, true, 'cancun', 'settingsVMCancunMode', titleVM)
await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', false, true, 'shanghai', 'settingsVMShanghaiMode', titleVM)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@ -23,7 +23,7 @@ const profile = {
name: 'blockchain',
displayName: 'Blockchain',
description: 'Blockchain - Logic',
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus'],
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus', 'getAllProviders', 'getPinnedProviders'],
version: packageJson.version
}
@ -44,6 +44,24 @@ export type Transaction = {
timestamp?: number
}
export type Provider = {
options: { [key: string]: string }
dataId: string
name: string
displayName: string
logo?: string,
logos?: string[],
fork: string
description?: string
isInjected: boolean
isVM: boolean
title: string
init: () => Promise<void>
provider:{
sendAsync: (payload: any) => Promise<void>
}
}
export class Blockchain extends Plugin {
active: boolean
event: EventManager
@ -62,6 +80,7 @@ export class Blockchain extends Plugin {
providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider}
transactionContextAPI: TransactionContextAPI
registeredPluginEvents: string[]
pinnedProviders: string[]
// NOTE: the config object will need to be refactored out in remix-lib
constructor(config: Config) {
@ -93,6 +112,7 @@ export class Blockchain extends Plugin {
this.networkcallid = 0
this.networkStatus = { network: { name: ' - ', id: ' - ' } }
this.registeredPluginEvents = []
this.pinnedProviders = ['vm-cancun', 'vm-shanghai', 'vm-mainnet-fork', 'vm-london', 'vm-berlin', 'vm-paris', 'walletconnect', 'injected-MetaMask', 'basic-http-provider', 'ganache-provider', 'hardhat-provider', 'foundry-provider']
this.setupEvents()
this.setupProviders()
}
@ -116,6 +136,14 @@ export class Blockchain extends Plugin {
})
}
})
this.on('environmentExplorer', 'providerPinned', (name, provider) => {
this.emit('shouldAddProvidertoUdapp', name, provider)
})
this.on('environmentExplorer', 'providerUnpinned', (name, provider) => {
this.emit('shouldRemoveProviderFromUdapp', name, provider)
})
}
onDeactivation() {
@ -136,12 +164,12 @@ export class Blockchain extends Plugin {
})
})
this.executionContext.event.register('addProvider', (network) => {
this._triggerEvent('addProvider', [network])
this.executionContext.event.register('providerAdded', (network) => {
this._triggerEvent('providerAdded', [network])
})
this.executionContext.event.register('removeProvider', (name) => {
this._triggerEvent('removeProvider', [name])
this.executionContext.event.register('providerRemoved', (name) => {
this._triggerEvent('providerRemoved', [name])
})
setInterval(() => {
@ -504,7 +532,11 @@ export class Blockchain extends Plugin {
}
changeExecutionContext(context, confirmCb, infoCb, cb) {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
if (context.context === 'item-another-chain') {
this.call('manager', 'activatePlugin', 'environmentExplorer').then(() => this.call('tabs', 'focus', 'environmentExplorer'))
} else {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
}
}
detectNetwork(cb) {
@ -611,7 +643,8 @@ export class Blockchain extends Plugin {
this.executionContext.listenOnLastBlock()
}
addProvider(provider) {
addProvider(provider: Provider) {
if (this.pinnedProviders.includes(provider.name)) this.emit('shouldAddProvidertoUdapp', provider.name, provider)
this.executionContext.addProvider(provider)
}
@ -619,6 +652,14 @@ export class Blockchain extends Plugin {
this.executionContext.removeProvider(name)
}
getAllProviders() {
return this.executionContext.getAllProviders()
}
getPinnedProviders() {
return this.pinnedProviders
}
// TODO : event should be triggered by Udapp instead of TxListener
/** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
startListening(txlistener) {

@ -123,10 +123,13 @@ export class ExecutionContext {
addProvider (network) {
if (network && network.name && !this.customNetWorks[network.name]) {
this.customNetWorks[network.name] = network
this.event.trigger('addProvider', [network])
}
}
getAllProviders () {
return this.customNetWorks
}
internalWeb3 () {
return web3
}

@ -82,11 +82,12 @@ let requiredModules = [ // services + layout views + system views
'pinnedPanel',
'pluginStateLogger',
'remixGuide',
'matomo'
'environmentExplorer',
'templateSelection',
'matomo',
'walletconnect'
]
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
@ -133,7 +134,9 @@ export function isNative(name) {
'circuit-compiler',
'compilationDetails',
'vyperCompilationDetails',
'remixGuide',
//'remixGuide',
'environmentExplorer',
'templateSelection',
'walletconnect'
]
return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name)
@ -389,7 +392,16 @@ class PluginLoader {
constructor() {
const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
// some plugins should not be activated at page load.
this.donotAutoReload = [
'remixd',
'environmentExplorer',
'templateSelection',
'compilationDetails',
'walletconnect',
'dapp-draft',
'solidityumlgen'
]
this.loaders = {}
this.loaders.localStorage = {
set: (plugin, actives) => {

@ -28,6 +28,8 @@ export class RemixEngine extends Engine {
if (name === 'fileManager') return { queueTimeout: 60000 * 20 }
if (name === 'solcoder') return { queueTimeout: 60000 * 2 }
if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 }
if (name === 'contentImport') return { queueTimeout: 60000 * 3 }
return { queueTimeout: 10000 }
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/ghaction-helper",
"version": "0.1.32",
"version": "0.1.33",
"description": "Solidity Tests GitHub Action Helper",
"main": "src/index.js",
"scripts": {
@ -19,17 +19,17 @@
},
"homepage": "https://github.com/ethereum/remix-project#readme",
"devDependencies": {
"@remix-project/remix-solidity": "^0.5.38",
"@remix-project/remix-solidity": "^0.5.39",
"@types/chai": "^4.3.4",
"typescript": "^4.9.3"
},
"dependencies": {
"@ethereum-waffle/chai": "^3.4.4",
"@remix-project/remix-simulator": "^0.2.52",
"@remix-project/remix-simulator": "^0.2.53",
"chai": "^4.3.7",
"ethers": "^5.7.2",
"web3": "^4.1.1"
},
"types": "./src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55"
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.61",
"version": "0.5.62",
"description": "Tool to perform static analysis on Solidity smart contracts",
"scripts": {
"test": "./../../node_modules/.bin/ts-node --project ../../tsconfig.base.json --require tsconfig-paths/register ./../../node_modules/.bin/tape ./test/tests.ts"
@ -25,8 +25,8 @@
"@ethereumjs/tx": "5.3.0",
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@remix-project/remix-astwalker": "^0.0.82",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-astwalker": "^0.0.83",
"@remix-project/remix-lib": "^0.5.60",
"async": "^2.6.2",
"ethers": "^5.4.2",
"ethjs-util": "^0.1.6",
@ -50,6 +50,6 @@
"typescript": "^3.7.5"
},
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55",
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c",
"main": "./src/index.js"
}

@ -0,0 +1,13 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface ILayoutApi {
events:{
} & StatusEvents
methods: {
maximisePinnedPanel: () => void
maximiseSidePanel: () => void
resetPinnedPanel: () => void
resetSidePanel: () => void
}
}

@ -0,0 +1,10 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface IMatomoApi {
events:{
} & StatusEvents
methods: {
track: (data: string[]) => void
}
}

@ -0,0 +1,11 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface IPinnedPanelApi {
events:{
} & StatusEvents
methods: {
currentFocus(): Promise<string>
}
}

@ -0,0 +1,11 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface ISidePanelApi {
events:{
focusChanged: (name: string) => void;
} & StatusEvents
methods: {
}
}

@ -9,6 +9,10 @@ import { INotificationApi } from "./plugins/notification-api"
import { ISettings } from "./plugins/settings-api"
import { IExtendedTerminalApi } from "./plugins/terminal-api"
import { IFilePanelApi } from "./plugins/filePanel-api"
import { ISidePanelApi } from "./plugins/sidePanel-api"
import { IPinnedPanelApi } from "./plugins/pinned-panel-api"
import { ILayoutApi } from "./plugins/layout-api"
import { IMatomoApi } from "./plugins/matomo-api"
export interface ICustomRemixApi extends IRemixApi {
dgitApi: IGitApi
@ -21,6 +25,10 @@ export interface ICustomRemixApi extends IRemixApi {
terminal: IExtendedTerminalApi
fs: IFs
filePanel: IFilePanelApi
sidePanel: ISidePanelApi
pinnedPanel: IPinnedPanelApi
layout: ILayoutApi
matomo: IMatomoApi
}
export declare type CustomRemixApi = Readonly<ICustomRemixApi>

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.82",
"version": "0.0.83",
"description": "Tool to walk through Solidity AST",
"main": "src/index.js",
"scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/tx": "5.3.0",
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-lib": "^0.5.60",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethers": "^5.4.2",
@ -53,6 +53,6 @@
"tap-spec": "^5.0.0"
},
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55",
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c",
"types": "./src/index.d.ts"
}

@ -1,12 +1,12 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { RemixURLResolver } from '@remix-project/remix-url-resolver'
import { RemixURLResolver, githubFolderResolver } from '@remix-project/remix-url-resolver'
const profile = {
name: 'contentImport',
displayName: 'content import',
version: '0.0.1',
methods: ['resolve', 'resolveAndSave', 'isExternalUrl']
methods: ['resolve', 'resolveAndSave', 'isExternalUrl', 'resolveGithubFolder']
}
export type ResolvedImport = {
@ -212,4 +212,10 @@ export class CompilerImports extends Plugin {
throw new Error(e)
}
}
async resolveGithubFolder (url) {
const ghFolder = {}
await githubFolderResolver(url, ghFolder, 3)
return ghFolder
}
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.52",
"version": "0.5.53",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -26,10 +26,10 @@
"@ethereumjs/tx": "5.3.0",
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@remix-project/remix-astwalker": "^0.0.82",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-simulator": "^0.2.52",
"@remix-project/remix-solidity": "^0.5.38",
"@remix-project/remix-astwalker": "^0.0.83",
"@remix-project/remix-lib": "^0.5.60",
"@remix-project/remix-simulator": "^0.2.53",
"@remix-project/remix-solidity": "^0.5.39",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"color-support": "^1.1.3",
@ -69,6 +69,6 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55",
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c",
"types": "./src/index.d.ts"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.59",
"version": "0.5.60",
"description": "Library to various Remix tools",
"contributors": [
{
@ -55,6 +55,6 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55",
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c",
"types": "./src/index.d.ts"
}
}

@ -49,8 +49,6 @@ export class TxRunnerWeb3 {
const receipt = await tryTillReceiptAvailable(resp, this.getWeb3())
tx = await tryTillTxAvailable(resp, this.getWeb3())
currentDateTime = new Date();
const end = currentDateTime.getTime() / 1000
console.log('tx duration', end - start)
resolve({
receipt,
tx,

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.52",
"version": "0.2.53",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -23,7 +23,7 @@
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@metamask/eth-sig-util": "^7.0.2",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-lib": "^0.5.60",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
@ -71,6 +71,6 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55",
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c",
"types": "./src/index.d.ts"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.5.38",
"version": "0.5.39",
"description": "Tool to load and run Solidity compiler",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -19,7 +19,7 @@
"@ethereumjs/tx": "5.3.0",
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-lib": "^0.5.60",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethers": "^5.4.2",
@ -57,5 +57,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55"
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.52",
"version": "0.2.53",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -41,9 +41,9 @@
"@ethereumjs/tx": "5.3.0",
"@ethereumjs/util": "9.0.3",
"@ethereumjs/vm": "8.0.0",
"@remix-project/remix-lib": "^0.5.59",
"@remix-project/remix-simulator": "^0.2.52",
"@remix-project/remix-solidity": "^0.5.38",
"@remix-project/remix-lib": "^0.5.60",
"@remix-project/remix-simulator": "^0.2.53",
"@remix-project/remix-solidity": "^0.5.39",
"@remix-project/remix-url-resolver": "^0.0.42",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
@ -89,5 +89,5 @@
"@ethereumjs/trie": "6.2.0"
},
"typings": "src/index.d.ts",
"gitHead": "35e1469e94bb370f5427d4ab230fcbd47c665e55"
"gitHead": "cc2850c7fb3f1e50b0d65437a74ccae19c74b87c"
}

@ -41,7 +41,6 @@ const DragBar = (props: IRemixDragBarUi) => {
}, [props.hidden, offset])
useEffect(() => {
initialWidth.current = props.refObject.current.clientWidth
if (props.maximiseTrigger > 0) {
if (props.layoutPosition === 'left') {
const width = 0.4 * window.innerWidth
@ -106,6 +105,7 @@ const DragBar = (props: IRemixDragBarUi) => {
setTimeout(() => {
props.setHideStatus(false)
setDragBarPosX(offset + props.refObject.current.offsetWidth)
initialWidth.current = props.refObject.current.clientWidth
}, 300)
}
} else if (props.layoutPosition === 'right') {
@ -117,9 +117,11 @@ const DragBar = (props: IRemixDragBarUi) => {
setTimeout(() => {
props.setHideStatus(false)
setDragBarPosX(props.refObject.current.offsetLeft)
initialWidth.current = props.refObject.current.clientWidth
}, 300)
}
}
}
function startDrag() {

@ -14,9 +14,10 @@ interface ICopyToClipboard {
title?: string
children?: JSX.Element
getContent?: () => any
callback?: () => void
}
export const CopyToClipboard = (props: ICopyToClipboard) => {
const { tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, ...otherProps } = props
const { tip = 'Copy', icon = 'fa-copy', direction = 'right', getContent, children, callback, ...otherProps } = props
let { content } = props
const [message, setMessage] = useState(tip)
@ -29,6 +30,7 @@ export const CopyToClipboard = (props: ICopyToClipboard) => {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
}
callback && callback()
copy(content)
setMessage('Copied')
} catch (e) {

@ -117,7 +117,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
};
this.completionEnabled = false
const handleCompletionTimer = new CompletionTimer(1000, () => { this.completionEnabled = true });
const handleCompletionTimer = new CompletionTimer(100, () => { this.completionEnabled = true });
handleCompletionTimer.start()
return {
@ -150,7 +150,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
// handle the completion timer by locking suggestions request for 2 seconds
this.completionEnabled = false
const handleCompletionTimer = new CompletionTimer(2000, () => { this.completionEnabled = true });
const handleCompletionTimer = new CompletionTimer(100, () => { this.completionEnabled = true });
handleCompletionTimer.start()
return {

@ -699,6 +699,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true
_paq.push(['trackEvent', 'editor', 'onDidPaste', 'more_than_10_lines'])
}
})

@ -33,7 +33,7 @@ export const BranchHeader = () => {
}
}
}
}, [context.currentBranch, context.commits, context.branches, context.remotes, context.currentHead])
}, [context.currentBranch, context.commits, context.branches, context.remotes, context.currentHead, context.defaultRemote])
useEffect(() => {
if (context.fileStatusResult) {
@ -43,6 +43,14 @@ export const BranchHeader = () => {
}
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted])
const getName = () => {
const url = context.currentBranch?.remote?.url
if (!url) return
const regex = /https:\/\/github\.com\/[^/]+\/([^/]+)(?:\.git)?/;
const match = url.match(regex)
return match ? match[1] : null
}
const showDetachedWarningText = async () => {
await pluginActions.showAlert({
message: `You are in 'detached HEAD' state. This means you are not on a branch because you checkout a tag or a specific commit. If you want to commit changes, you will need to create a new branch.`,
@ -50,21 +58,50 @@ export const BranchHeader = () => {
})
}
const Heading = () => {
return (
<div className="container-fluid px-0">
<div className="d-flex flex-column pt-1 mb-1">
<div className="d-flex flex-column justify-content-start align-items-start w-100">
{getName() ? (
<span className={`text-truncate overflow-hidden whitespace-nowrap w-100`}>
{getName() ?? ''}
{context.currentBranch && context.currentBranch.remote && context.currentBranch.remote.name ? ` on ${context.currentBranch.remote.name}` : ''}
</span>
) : null
}
{context.currentBranch && context.currentBranch.name ?
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
<i className="fa fa-code-branch mr-1"></i>{context.currentBranch && context.currentBranch.name}{changed?'*':''}
</span> : null}
{(latestCommit && latestCommit.commit && latestCommit.commit.message) ?
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
{latestCommit ?
latestCommit.commit && latestCommit.commit.message ? `"${latestCommit.commit.message}"` : '' : null}
</span>
: null}
{isDetached ?
<span className="text-secondary text-truncate overflow-hidden whitespace-nowrap w-100">
{isDetached ?
<>You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1"></i></> : null}
</span>
: null}
{context.storage.enabled ?
<span className="text-secondary text-sm text-truncate overflow-hidden whitespace-nowrap w-100">
{context.storage.used} MB used
({context.storage.percentUsed} %)
</span>
: null}
</div>
</div>
</div>
)
}
return (<>
<div className='text-sm w-100'>
<div className='text-secondary long-and-truncated'>
<i className="fa fa-code-branch mr-1 pl-2"></i>
{changed ? '*' : ''}{context.currentBranch && context.currentBranch.name}
</div>
{latestCommit ?
<div className='text-secondary long-and-truncated'>
{latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : ''}
</div> : null}
{isDetached ?
<div className='text-warning long-and-truncated'>
You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i>
</div> : null}
<Heading />
</div>
<hr></hr>
</>)
}
}

@ -126,7 +126,7 @@ export const CommitMessage = () => {
return (
<>
<div className="form-group">
<div className="form-group pt-3">
<input placeholder={commitMessagePlaceholder()} data-id='commitMessage' disabled={!messageEnabled()} className="form-control" type="text" onChange={handleChange} value={message.value} />
</div>
<button data-id='commitButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Commit ? '' : 'd-none'}`} disabled={commitNotAllowed()} onClick={async () => await commit()} >
@ -144,4 +144,4 @@ export const CommitMessage = () => {
<hr></hr>
</>
);
}
}

@ -1,5 +1,6 @@
import React, { useContext } from 'react'
import { gitPluginContext } from '../gitui'
import { CustomTooltip } from '@remix-ui/helper';
interface ButtonWithContextProps {
onClick: React.MouseEventHandler<HTMLButtonElement>;
@ -7,18 +8,31 @@ interface ButtonWithContextProps {
disabledCondition?: boolean; // Optional additional disabling condition
// You can add other props if needed, like 'type', 'className', etc.
[key: string]: any; // Allow additional props to be passed
tooltip?: string;
}
// This component extends a button, disabling it when loading is true
const GitUIButton = ({ children, disabledCondition = false, ...rest }:ButtonWithContextProps) => {
const GitUIButton = ({ children, disabledCondition = false, ...rest }: ButtonWithContextProps) => {
const { loading } = React.useContext(gitPluginContext)
const isDisabled = loading || disabledCondition
return (
<button disabled={isDisabled} {...rest}>
{children}
</button>
);
if (rest.tooltip) {
return (
<CustomTooltip tooltipText={rest.tooltip} placement="top">
<button disabled={isDisabled} {...rest}>
{children}
</button>
</CustomTooltip>
);
} else {
return (
<button disabled={isDisabled} {...rest}>
{children}
</button>
);
}
};
export default GitUIButton;

@ -44,7 +44,10 @@ export const SourceControlBase = (props: SourceControlButtonsProps) => {
}, [context.branchDifferences, context.currentBranch, branch, remote])
const setDefaultRemote = () => {
if (context.defaultRemote) {
setRemote(context.defaultRemote)
return
}
if (context.remotes.length > 0) {
// find remote called origin
const origin = context.remotes.find(remote => remote.name === 'origin')

@ -5,9 +5,11 @@ import React, { useEffect, useState } from "react"
import { FormattedMessage } from "react-intl"
import { gitActionsContext } from "../../state/context"
import { branch, remote } from "@remix-api"
import { gitMatomoEventTypes } from "../../types"
import { gitPluginContext } from "../gitui"
import GitUIButton from "./gituibutton"
import { syncStateContext } from "./sourceControlBase"
import { sendToMatomo } from "../../lib/pluginActions"
export const SourceControlButtons = () => {
const context = React.useContext(gitPluginContext)
@ -51,6 +53,7 @@ export const SourceControlButtons = () => {
}
const refresh = async() => {
await sendToMatomo(gitMatomoEventTypes.REFRESH)
await actions.getFileStatusMatrix(null)
await actions.gitlog()
}

@ -38,7 +38,7 @@ export const BranchSelect = (props: BranchySelectProps) => {
return (<>{branchOptions && branchOptions.length ?
<Select
options={branchOptions}
className="mt-1"
className="mt-2"
id="branch-select"
onChange={(e: any) => selectRemoteBranch(e)}
theme={selectTheme}

@ -4,6 +4,8 @@ import { gitPluginContext } from "../gitui";
import axios from "axios";
import { CopyToClipboard } from "@remix-ui/clipboard";
import { Card } from "react-bootstrap";
import { sendToMatomo } from "../../lib/pluginActions";
import { gitMatomoEventTypes } from "../../types";
export const GetDeviceCode = () => {
const context = React.useContext(gitPluginContext)
@ -13,7 +15,7 @@ export const GetDeviceCode = () => {
const [authorized, setAuthorized] = React.useState<boolean>(false)
const getDeviceCodeFromGitHub = async () => {
await sendToMatomo(gitMatomoEventTypes.GETGITHUBDEVICECODE)
setAuthorized(false)
// Send a POST request
const response = await axios({
@ -36,6 +38,7 @@ export const GetDeviceCode = () => {
}
const connectApp = async () => {
await sendToMatomo(gitMatomoEventTypes.CONNECTTOGITHUB)
// poll https://github.com/login/oauth/access_token
const accestokenresponse = await axios({
method: 'post',
@ -56,13 +59,17 @@ export const GetDeviceCode = () => {
if (response.access_token) {
setAuthorized(true)
await sendToMatomo(gitMatomoEventTypes.CONNECTTOGITHUBSUCCESS)
await pluginActions.saveToken(response.access_token)
await actions.loadGitHubUserFromToken()
} else {
await sendToMatomo(gitMatomoEventTypes.CONNECTTOGITHUBFAIL)
}
}
const disconnect = async () => {
await sendToMatomo(gitMatomoEventTypes.DISCONNECTFROMGITHUB)
setAuthorized(false)
setGitHubResponse(null)
await pluginActions.saveToken(null)
@ -71,10 +78,11 @@ export const GetDeviceCode = () => {
return (
<>
{(context.gitHubUser && context.gitHubUser.login) ? null :
<button className='btn btn-primary mt-1 w-100' onClick={async () => {
getDeviceCodeFromGitHub();
}}><i className="fab fa-github mr-1"></i>Login in with github</button>
{(context.gitHubUser && context.gitHubUser.login) ? null : <>
<label className="text-uppercase">Connect to GitHub</label>
<button className='btn btn-secondary mt-1 w-100' onClick={async () => {
await getDeviceCodeFromGitHub()
}}><i className="fab fa-github mr-1"></i>Login in with github</button></>
}
{gitHubResponse && !authorized &&
<div className="pt-2">
@ -83,7 +91,7 @@ export const GetDeviceCode = () => {
<div className="input-group text-secondary mb-0 h6">
<input disabled type="text" className="form-control" value={gitHubResponse.user_code} />
<div className="input-group-append">
<CopyToClipboard content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} />
<CopyToClipboard callback={() => sendToMatomo(gitMatomoEventTypes.COPYGITHUBDEVICECODE)} content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} />
</div>
</div>
<br></br>
@ -107,21 +115,29 @@ export const GetDeviceCode = () => {
{
(context.gitHubUser && context.gitHubUser.login) ?
<div className="pt-2">
<Card>
<Card.Body>
<Card.Title data-id={`connected-as-${context.gitHubUser.login}`}>Connected as {context.gitHubUser.login}</Card.Title>
<Card.Text>
<div className="mb-1" data-id={`connected-as-${context.gitHubUser.login}`}>Connected as {context.gitHubUser.login}</div>
<div className="row">
{context.gitHubUser.avatar_url ?
<div className="col-6">
<img data-id={`connected-img-${context.gitHubUser.login}`} src={context.gitHubUser.avatar_url} className="w-100" />
<a data-id={`connected-link-${context.gitHubUser.login}`} href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a>
</div> : null}
</div>
<div className="row mt-2">
<div className="col-6">
{context.gitHubUser.html_url ? <>
<label className="text-uppercase">user on github:</label>
<a data-id={`connected-link-${context.gitHubUser.login}`} href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a> </> : null}
{context.userEmails && context.userEmails.length > 0 ? <>
<label className="text-uppercase mt-2">email:</label>
{context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => {
return <span key={email.email}><br></br>{email.email}</span>
})}
</Card.Text>
</Card.Body>
</Card>
})}</> : null}
</div>
</div>
</div> : null
}
</>)
}
}

@ -1,14 +1,15 @@
import React, { useState, useEffect } from 'react';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import { gitActionsContext } from '../../state/context';
import { repository } from '@remix-api';
import { gitMatomoEventTypes } from '../../types';
import { selectStyles, selectTheme } from '../../types/styles';
import { gitPluginContext } from '../gitui';
import { TokenWarning } from '../panels/tokenWarning';
import { sendToMatomo } from '../../lib/pluginActions';
interface RepositorySelectProps {
select: (repo: repository) => void;
title: string;
}
const RepositorySelect = (props: RepositorySelectProps) => {
@ -17,6 +18,7 @@ const RepositorySelect = (props: RepositorySelectProps) => {
const actions = React.useContext(gitActionsContext)
const [loading, setLoading] = useState(false)
const [show, setShow] = useState(false)
const [selected, setSelected] = useState<any>(null)
useEffect(() => {
if (context.repositories && context.repositories.length > 0) {
@ -38,6 +40,7 @@ const RepositorySelect = (props: RepositorySelectProps) => {
const selectRepo = async (e: any) => {
if (!e || !e.value) {
props.select(null)
setSelected(null)
return
}
const value = e && e.value
@ -48,11 +51,13 @@ const RepositorySelect = (props: RepositorySelectProps) => {
if (repo) {
props.select(repo)
setSelected(repo)
await actions.remoteBranches(repo.owner.login, repo.name)
}
}
const fetchRepositories = async () => {
await sendToMatomo(gitMatomoEventTypes.LOADREPOSITORIESFROMGITHUB)
try {
setShow(true)
setLoading(true)
@ -64,22 +69,25 @@ const RepositorySelect = (props: RepositorySelectProps) => {
};
return (
<><Button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1">
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub
</Button>
<><button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1 btn btn-secondary mb-2">
<i className="fab fa-github mr-1"></i>{props.title}
</button>
{
show ?
<Select
options={repoOtions}
className="mt-1"
id="repository-select"
onChange={(e: any) => selectRepo(e)}
theme={selectTheme}
styles={selectStyles}
isClearable={true}
placeholder="Type to search for a repository..."
isLoading={loading}
/> : null
<>
<Select
options={repoOtions}
className="mt-1"
id="repository-select"
onChange={(e: any) => selectRepo(e)}
theme={selectTheme}
styles={selectStyles}
isClearable={true}
placeholder="Type to search for a repository..."
isLoading={loading}
/>
{ selected ? null : <label className="text-warning mt-2">Please select a repository</label> }
</>: null
}</>
);
};

@ -18,7 +18,7 @@ export const SelectAndCloneRepositories = (props: RepositoriesProps) => {
const [branch, setBranch] = useState({ name: "" });
const [repo, setRepo] = useState<repository>(null);
const selectRemoteBranch = async (branch:{ name: string }) => {
const selectRemoteBranch = async (branch: { name: string }) => {
setBranch(branch)
}
@ -43,16 +43,19 @@ export const SelectAndCloneRepositories = (props: RepositoriesProps) => {
return (
<>
<RepositorySelect title="Load from GitHub" select={selectRepo} />
<TokenWarning />
<RepositorySelect select={selectRepo} />
{repo &&<BranchSelect select={selectRemoteBranch} />}
{repo && <BranchSelect select={selectRemoteBranch} />}
{repo && branch && branch.name && branch.name !== '0' ?
<button data-id={`clonebtn-${repo.full_name}-${branch.name}`} className='btn btn-primary mt-1 w-100' onClick={async () => {
await clone()
}}>clone {repo.full_name}:{branch.name}</button> : null}
{repo && (!branch || branch.name === '0') ?
<label className="text-warning">Please select a branch to clone</label> : null}
</>
)
}

@ -1,10 +1,10 @@
import React, { useEffect, useReducer, useState, useContext } from 'react'
import { add, addall, checkout, checkoutfile, clone, commit, createBranch, remoteBranches, repositories, rm, getCommitChanges, diff, resolveRef, getBranchCommits, setUpstreamRemote, loadGitHubUserFromToken, getBranches, getRemotes, remoteCommits, saveGitHubCredentials, getGitHubCredentialsFromLocalStorage, fetch, pull, push, setDefaultRemote, addRemote, removeRemote, sendToGitLog, clearGitLog, getBranchDifferences, getFileStatusMatrix, init, showAlert, gitlog } from '../lib/gitactions'
import { loadFiles, setCallBacks } from '../lib/listeners'
import { openDiff, openFile, openFolderInSameWindow, saveToken, setModifiedDecorator, setPlugin, setUntrackedDecorator, statusChanged } from '../lib/pluginActions'
import { openDiff, openFile, openFolderInSameWindow, sendToMatomo, saveToken, setModifiedDecorator, setPlugin, setUntrackedDecorator, statusChanged } from '../lib/pluginActions'
import { gitActionsContext, pluginActionsContext } from '../state/context'
import { gitReducer } from '../state/gitreducer'
import { defaultGitState, defaultLoaderState, gitState, loaderState } from '../types'
import { defaultGitState, defaultLoaderState, gitMatomoEventTypes, gitState, gitUIPanels, loaderState } from '../types'
import { Accordion } from "react-bootstrap";
import { CommitMessage } from './buttons/commitmessage'
import { Commits } from './panels/commits'
@ -31,8 +31,8 @@ import { SourceControl } from './panels/sourcontrol'
import { GitHubCredentials } from './panels/githubcredentials'
import { Setup } from './panels/setup'
import { Init } from './panels/init'
import { CustomRemixApi } from "@remix-api";
import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from "@remix-api"
import { Plugin } from "@remixproject/engine"
import { Disabled } from './disabled'
import { platformContext } from '@remix-ui/app'
import { Version } from './panels/version'
@ -122,6 +122,13 @@ export const GitUI = (props: IGitUi) => {
}, [gitState.gitHubUser, gitState.currentBranch, gitState.remotes, gitState.gitHubAccessToken, gitState.currentHead])
useEffect(() => {
const panelName = Object.keys(gitUIPanels)
.filter(k => gitUIPanels[k] === activePanel);
if (!(panelName && panelName[0])) return
sendToMatomo(gitMatomoEventTypes.OPENPANEL, [panelName && panelName[0]])
}, [activePanel])
const gitActionsProviderValue = {
commit,
addall,
@ -168,9 +175,8 @@ export const GitUI = (props: IGitUi) => {
}
return (
<>{(!gitState.canUseApp) ?
<Disabled></Disabled> :
<div className="m-1">
<>{(!gitState.canUseApp) ? <Disabled></Disabled> :
<div className="px-3">
<gitPluginContext.Provider value={gitState}>
<loaderContext.Provider value={loaderState}>
<gitActionsContext.Provider value={gitActionsProviderValue}>
@ -180,68 +186,67 @@ export const GitUI = (props: IGitUi) => {
{setup && !needsInit ? <Setup></Setup> : null}
{needsInit ? <Init></Init> : null}
{!setup && !needsInit ?
<>
<Accordion activeKey={activePanel} defaultActiveKey="0">
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="0">
<>
<SourceControlBase><CommitMessage /></SourceControlBase>
<SourceControl />
</>
</Accordion.Collapse>
<hr></hr>
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="1">
<>
<Commands></Commands>
</>
</Accordion.Collapse>
<hr></hr>
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} />
<Accordion.Collapse className='bg-light' eventKey="3">
<>
<Commits />
</>
</Accordion.Collapse>
<hr></hr>
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="2">
<>
<Branches /></>
</Accordion.Collapse>
<hr></hr>
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="5">
<>
<Remotes></Remotes>
</>
</Accordion.Collapse>
<hr></hr>
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="4">
<>
<Clone /></>
</Accordion.Collapse>
<hr></hr>
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="7">
<>
<GetDeviceCode></GetDeviceCode>
<hr></hr>
<GitHubCredentials></GitHubCredentials>
</>
</Accordion.Collapse>
<hr></hr>
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="6">
<>
<LogViewer />
</>
</Accordion.Collapse>
</Accordion>
<Version/></>
<><Accordion activeKey={activePanel} defaultActiveKey="0" className="">
<SourceControlNavigation eventKey={gitUIPanels.SOURCECONTROL} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.SOURCECONTROL}>
<div className="px-2 py-2">
<SourceControlBase><CommitMessage /></SourceControlBase>
<SourceControl />
</div>
</Accordion.Collapse>
<hr></hr>
<CommandsNavigation eventKey={gitUIPanels.COMMANDS} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className="bg-light" eventKey={gitUIPanels.COMMANDS}>
<div className="px-2 py-2">
<Commands></Commands>
</div>
</Accordion.Collapse>
<hr></hr>
<CommitsNavigation title={`COMMITS`} eventKey={gitUIPanels.COMMITS} activePanel={activePanel} callback={setActivePanel} showButtons={true} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.COMMITS}>
<div className="px-2 py-2">
<Commits />
</div>
</Accordion.Collapse>
<hr></hr>
<BranchesNavigation eventKey={gitUIPanels.BRANCHES} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.BRANCHES}>
<div className="px-2 py-2">
<Branches />
</div>
</Accordion.Collapse>
<hr></hr>
<RemotesNavigation eventKey={gitUIPanels.REMOTES} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.REMOTES}>
<div className="px-2 py-2">
<Remotes></Remotes>
</div>
</Accordion.Collapse>
<hr></hr>
<CloneNavigation eventKey={gitUIPanels.CLONE} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.CLONE}>
<div className="px-2 py-2">
<Clone /></div>
</Accordion.Collapse>
<hr></hr>
<GitHubNavigation eventKey={gitUIPanels.GITHUB} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.GITHUB}>
<div className="px-2 py-2">
<GetDeviceCode></GetDeviceCode>
<hr></hr>
<GitHubCredentials></GitHubCredentials>
</div>
</Accordion.Collapse>
<hr></hr>
<LogNavigation eventKey={gitUIPanels.LOG} activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey={gitUIPanels.LOG}>
<div className="px-2 py-2">
<LogViewer />
</div>
</Accordion.Collapse>
</Accordion><Version /></>
: null}
</pluginActionsContext.Provider>
</gitActionsContext.Provider>
@ -250,4 +255,4 @@ export const GitUI = (props: IGitUi) => {
</div>}
</>
)
}
}

@ -5,6 +5,7 @@ import { gitActionsContext } from "../../state/context";
import { branch } from "@remix-api";
import GitUIButton from "../buttons/gituibutton";
import { gitPluginContext } from "../gitui";
import { removeGitFromUrl } from "../../utils";
interface BrancheDetailsNavigationProps {
eventKey: string;
@ -12,10 +13,11 @@ interface BrancheDetailsNavigationProps {
callback: (eventKey: string) => void;
branch: branch;
checkout: (branch: branch) => void;
allowCheckout: boolean;
}
export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) => {
const { eventKey, activePanel, callback, branch, checkout } = props;
const { eventKey, activePanel, callback, branch, checkout, allowCheckout } = props;
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const handleClick = () => {
@ -33,7 +35,7 @@ export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) =
const openRemote = () => {
const remote = branch.remote || getRemote()
window.open(`${remote.url}/tree/${branch.name}`, '_blank');
window.open(`${removeGitFromUrl(remote.url)}/tree/${branch.name}`, '_blank');
}
const reloadBranch = () => {
@ -55,6 +57,10 @@ export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) =
}
if (branch.name === 'HEAD'){
return null;
}
return (
<>
<div className="d-flex flex-row w-100 mb-2 mt-2">
@ -63,30 +69,31 @@ export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) =
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<i className="fa fa-code-branch ml-1"></i>
<div className={`ml-1 ${context.currentBranch.name === branch.name ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div>
<div className={`ml-1 ${context.currentBranch.name === branch.name && allowCheckout ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div>
</div>
{context.currentBranch && context.currentBranch.name === branch.name ?
<GitUIButton data-id={`branches-toggle-current-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => { }}>
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon>
</GitUIButton>
:
<GitUIButton data-id={`branches-toggle-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}>
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon>
</GitUIButton>
}
{allowCheckout ?
context.currentBranch && context.currentBranch.name === branch.name ?
<GitUIButton data-id={`branches-toggle-current-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => { }}>
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon>
</GitUIButton>
:
<GitUIButton tooltip="checkout branch" data-id={`branches-toggle-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}>
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon>
</GitUIButton>
: null}
{!branch.remote && canFetch() && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>
<GitUIButton tooltip="fetch branch" className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton>
<GitUIButton tooltip="open on remote" className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>
</>}
{branch.remote?.url && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}>
<GitUIButton tooltip="fetch branch" className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}>
<FontAwesomeIcon icon={faSync} ></FontAwesomeIcon>
</GitUIButton>
</>}
{branch.remote?.url && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}>
<GitUIButton tooltip="open remote" className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}>
<FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon>
</GitUIButton>
</>}

@ -23,10 +23,10 @@ export const BranchesNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">BRANCHES</label>
<label className="pl-2 nav form-check-label">BRANCHES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -20,10 +20,10 @@ export const CloneNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">CLONE</label>
<label className="pl-2 nav form-check-label ">CLONE</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -21,15 +21,14 @@ export const CommandsNavigation = ({ eventKey, activePanel, callback }) => {
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='commands-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
<span data-id='commands-panel' onClick={() => handleClick()} role={'button'} className="nav d-flex justify-content-start align-items-center w-75">
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">COMMANDS</label>
<LoaderIndicator></LoaderIndicator>
<label className="pl-2 nav form-check-label">COMMANDS</label>
</span>
<LoaderIndicator></LoaderIndicator>
</div>
</>
);
}
}

@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect } from "react";
import { CommitSummary } from "../panels/commits/commitsummary";
import { ReadCommitResult } from "isomorphic-git"
import { branch } from "../../types";
interface CommitDetailsNavigationProps {
commit: ReadCommitResult,
@ -11,10 +12,11 @@ interface CommitDetailsNavigationProps {
activePanel: string
callback: (eventKey: string) => void
isAheadOfRepo: boolean
branch: branch
}
export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) => {
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo } = props;
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo, branch } = props;
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
@ -30,7 +32,7 @@ export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) =>
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<CommitSummary isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary>
<CommitSummary branch={branch} isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary>
</div>
</>
);

@ -48,9 +48,8 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran
}
{ahead? <FontAwesomeIcon className='ml-1' icon={faCloudArrowUp}></FontAwesomeIcon> : null}
{behind? <FontAwesomeIcon className='ml-1' icon={faCloudArrowDown}></FontAwesomeIcon> : null}
<label className={`pl-1 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label>
<label className={`pl-2 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label>
<LoaderIndicator></LoaderIndicator>
</span>
{showButtons ?
<SourceControlBase branch={branch} remote={remote}>
@ -60,4 +59,4 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran
</div>
</>
);
}
}

@ -1,7 +1,7 @@
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { } from "react";
import { pluginActionsContext } from "../../state/context";
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import React, { } from "react"
import { pluginActionsContext } from "../../state/context"
export const GitHubNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
@ -21,9 +21,9 @@ export const GitHubNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">GITHUB SETUP</label>
<label className="pl-2 nav form-check-label">GITHUB SETUP</label>
</span>
</div>
</>
);
}
)
}

@ -50,7 +50,7 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label mr-2">LOG</label>
<label className="pl-2 nav form-check-label mr-2">LOG</label>
{logState.errorCount > 0 && (
<div className="text-danger mr-1">
{logState.errorCount}
@ -84,4 +84,4 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => {
</div>
</>
);
}
}

@ -23,10 +23,10 @@ export const RemotesNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">REMOTES</label>
<label className="pl-2 nav form-check-label">REMOTES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}
}

@ -4,8 +4,10 @@ import { CustomTooltip } from "@remix-ui/helper";
import React, { useContext, useEffect } from "react";
import { gitActionsContext } from "../../state/context";
import { remote } from "@remix-api";
import { gitMatomoEventTypes } from "../../types";
import GitUIButton from "../buttons/gituibutton";
import { gitPluginContext } from "../gitui";
import { removeGitFromUrl } from "../../utils";
interface RemotesDetailsNavigationProps {
eventKey: string;
@ -29,39 +31,43 @@ export const RemotesDetailsNavigation = (props: RemotesDetailsNavigationProps) =
}
const openRemote = () => {
window.open(`${remote.url}`, '_blank');
window.open(`${removeGitFromUrl(remote.url)}`, '_blank');
}
const setAsDefault = () => {
const setAsDefault = async () => {
actions.setDefaultRemote(remote)
}
const isDefault = () => {
return (context.defaultRemote && context.defaultRemote?.url === remote.url) || (context.upstream && context.upstream?.url === remote.url)
}
return (
<>
<div className="d-flex flex-row w-100 mb-2 mt-2">
<div data-id={`remote-detail-${remote.name}${context.defaultRemote && context.defaultRemote?.url === remote.url ? '-default' : ''}`} onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'>
<div data-id={`remote-detail-${remote.name}${isDefault() ? '-default' : ''}`} onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<CustomTooltip tooltipText={remote.url} placement="top">
<div className={`long-and-truncated ml-1 ${context.defaultRemote && context.defaultRemote?.url === remote.url ? 'text-success' : ''}`}>
<div className={`long-and-truncated ml-1 ${isDefault() ? 'text-success' : ''}`}>
{remote.name} <FontAwesomeIcon className='' icon={faArrowRightArrowLeft}></FontAwesomeIcon> {remote.url}
</div>
</CustomTooltip>
</div>
{context.defaultRemote && context.defaultRemote?.url === remote.url ?
<GitUIButton className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton>
{isDefault() ?
<GitUIButton data-id={`default-remote-check-${remote.name}`}className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton>
:
<GitUIButton className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton>
<GitUIButton data-id={`set-as-default-${remote.name}`} tooltip="set as default" className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton>
}
<GitUIButton data-id={`remote-sync-${remote.name}`} className="btn btn-sm" onClick={async () => {
<GitUIButton tooltip="Fetch remote" data-id={`remote-sync-${remote.name}`} className="btn btn-sm" onClick={async () => {
await actions.fetch({
remote
})
}}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton>
<GitUIButton data-id={`remote-rm-${remote.name}`} className="btn btn-sm" onClick={() => actions.removeRemote(remote)}><FontAwesomeIcon className='text-danger' icon={faTrash} ></FontAwesomeIcon></GitUIButton>
{remote?.url && <GitUIButton className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>}
{remote?.url && <GitUIButton tooltip="open on remote" className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>}
</div>
</>
);

@ -24,7 +24,7 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="nav pl-1 form-check-label">SETTINGS</label>
<label className="nav pl-2 form-check-label">SETTINGS</label>
</span>
@ -38,4 +38,4 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
</div>
</>
);
}
}

@ -24,12 +24,13 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) =>
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='sourcecontrol-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
<div className={'d-flex align-items-center justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='sourcecontrol-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'
>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="nav pl-1 form-check-label">SOURCE CONTROL</label>
<label className="nav pl-2 form-check-label">SOURCE CONTROL</label>
<LoaderIndicator></LoaderIndicator>
</span>
@ -39,4 +40,4 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) =>
</div>
</>
);
}
}

@ -38,10 +38,10 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
</span>
{
activePanel === eventKey ?
<span className='d-flex justify-content-end align-items-center w-25'>
<span className='d-flex justify-content-end align-items-center w-25 py-2'>
{group.name === 'Changes' ?
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}>
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button>
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm' style={{ marginLeft: '1rem', marginRight: '1.3rem' }}><FontAwesomeIcon icon={faPlus} className="" /></button>
</CustomTooltip>: null}
</span> : null
@ -49,4 +49,4 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
</div>
</>
);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save