diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml index de3e43802a..73e2950107 100644 --- a/.github/workflows/pr-reminder.yml +++ b/.github/workflows/pr-reminder.yml @@ -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' diff --git a/apps/etherscan/src/app/utils/networks.ts b/apps/etherscan/src/app/utils/networks.ts index fdb28e50a8..d2feb30f06 100644 --- a/apps/etherscan/src/app/utils/networks.ts +++ b/apps/etherscan/src/app/utils/networks.ts @@ -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', } diff --git a/apps/etherscan/src/app/views/CaptureKeyView.tsx b/apps/etherscan/src/app/views/CaptureKeyView.tsx index e63ec4278d..d2f860ca05 100644 --- a/apps/etherscan/src/app/views/CaptureKeyView.tsx +++ b/apps/etherscan/src/app/views/CaptureKeyView.tsx @@ -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 : '/') } diff --git a/apps/etherscan/src/app/views/VerifyView.tsx b/apps/etherscan/src/app/views/VerifyView.tsx index ca70dba74c..f41e9203af 100644 --- a/apps/etherscan/src/app/views/VerifyView.tsx +++ b/apps/etherscan/src/app/views/VerifyView.tsx @@ -211,7 +211,7 @@ export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, netwo type="button" className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block" onClick={async () => { - etherscanScripts(client) + etherscanScripts({}, client) }} > Generate Verification Scripts diff --git a/apps/learneth/src/components/BackButton/index.tsx b/apps/learneth/src/components/BackButton/index.tsx index 63b8c39975..908df5e378 100644 --- a/apps/learneth/src/components/BackButton/index.tsx +++ b/apps/learneth/src/components/BackButton/index.tsx @@ -31,9 +31,11 @@ function BackButton({entity}: any) { {isDetailPage && (
  • - (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}> - - + Tutorial menu}> + (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}> + + +
  • )} diff --git a/apps/learneth/src/pages/StepDetail/index.tsx b/apps/learneth/src/pages/StepDetail/index.tsx index c689add02d..f235b885eb 100644 --- a/apps/learneth/src/pages/StepDetail/index.tsx +++ b/apps/learneth/src/pages/StepDetail/index.tsx @@ -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 (
    +
    +
    + +
    +
    + loading... +
    + ) + } + return ( - <> +
    @@ -51,13 +83,13 @@ function StepDetailPage() { {errorLoadingFile ? ( <>
    -

    {step.name}

    +

    {clonedStep.name}

    - {step.answer?.content && ( + {clonedStep.answer?.content && ( - {step.answer?.content && ( + {clonedStep.answer?.content && (
    ) } diff --git a/apps/learneth/src/redux/models/remixide.ts b/apps/learneth/src/redux/models/remixide.ts index c16fd163e0..fe7491f650 100644 --- a/apps/learneth/src/redux/models/remixide.ts +++ b/apps/learneth/src/redux/models/remixide.ts @@ -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) diff --git a/apps/learneth/src/redux/models/workshop.ts b/apps/learneth/src/redux/models/workshop.ts index 087d1e89e0..635503c657 100644 --- a/apps/learneth/src/redux/models/workshop.ts +++ b/apps/learneth/src/redux/models/workshop.ts @@ -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) } diff --git a/apps/learneth/src/remix-client.ts b/apps/learneth/src/remix-client.ts index 8ee04dce2a..4742970649 100644 --- a/apps/learneth/src/remix-client.ts +++ b/apps/learneth/src/remix-client.ts @@ -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', diff --git a/apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts b/apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts new file mode 100644 index 0000000000..234a3a08cd --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts @@ -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 diff --git a/apps/remix-ide-e2e/src/commands/pinGrid.ts b/apps/remix-ide-e2e/src/commands/pinGrid.ts new file mode 100644 index 0000000000..6c82922960 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/pinGrid.ts @@ -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 diff --git a/apps/remix-ide-e2e/src/commands/selectFiles.ts b/apps/remix-ide-e2e/src/commands/selectFiles.ts index d24ec1e42b..544f36ba3d 100644 --- a/apps/remix-ide-e2e/src/commands/selectFiles.ts +++ b/apps/remix-ide-e2e/src/commands/selectFiles.ts @@ -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 diff --git a/apps/remix-ide-e2e/src/commands/setupMetamask.ts b/apps/remix-ide-e2e/src/commands/setupMetamask.ts index 768b9631bb..585473827c 100644 --- a/apps/remix-ide-e2e/src/commands/setupMetamask.ts +++ b/apps/remix-ide-e2e/src/commands/setupMetamask.ts @@ -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() }) } diff --git a/apps/remix-ide-e2e/src/githttpbackend/setup.sh b/apps/remix-ide-e2e/src/githttpbackend/setup.sh index 5d2644ddb1..496428301f 100755 --- a/apps/remix-ide-e2e/src/githttpbackend/setup.sh +++ b/apps/remix-ide-e2e/src/githttpbackend/setup.sh @@ -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 diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index dbdef20903..c9fb7c27bc 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -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}) diff --git a/apps/remix-ide-e2e/src/tests/circom.test.ts b/apps/remix-ide-e2e/src/tests/circom.test.ts index 2f64844faa..ac14415ea9 100644 --- a/apps/remix-ide-e2e/src/tests/circom.test.ts +++ b/apps/remix-ide-e2e/src/tests/circom.test.ts @@ -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"]') } } diff --git a/apps/remix-ide-e2e/src/tests/dgit_github.test.ts b/apps/remix-ide-e2e/src/tests/dgit_github.test.ts index 6c8f8cfa08..da38485359 100644 --- a/apps/remix-ide-e2e/src/tests/dgit_github.test.ts +++ b/apps/remix-ide-e2e/src/tests/dgit_github.test.ts @@ -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' }) }, diff --git a/apps/remix-ide-e2e/src/tests/dgit_local.test.ts b/apps/remix-ide-e2e/src/tests/dgit_local.test.ts index 86d766a753..fc0ec93f98 100644 --- a/apps/remix-ide-e2e/src/tests/dgit_local.test.ts +++ b/apps/remix-ide-e2e/src/tests/dgit_local.test.ts @@ -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 { @@ -360,10 +452,10 @@ async function getGitLog(path: string): Promise { }) } -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()); diff --git a/apps/remix-ide-e2e/src/tests/erc721.test.ts b/apps/remix-ide-e2e/src/tests/erc721.test.ts index 373477f064..890a5e7fc3 100644 --- a/apps/remix-ide-e2e/src/tests/erc721.test.ts +++ b/apps/remix-ide-e2e/src/tests/erc721.test.ts @@ -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"]') diff --git a/apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts b/apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts index 1d31d6a9b5..214217af38 100644 --- a/apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts @@ -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()) + }) + }) + } } } diff --git a/apps/remix-ide-e2e/src/tests/grid.test.ts b/apps/remix-ide-e2e/src/tests/grid.test.ts new file mode 100644 index 0000000000..5da7379328 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/grid.test.ts @@ -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 + } +} \ No newline at end of file diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts index 20a252a729..446d5e4d13 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts @@ -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; diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts index c96f702339..91ef7d6a4f 100644 --- a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts @@ -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"]') diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index e1335c856d..9d73831389 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -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(() => { diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index 316c239448..8a77f2e60c 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -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 {"') + }) } } diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index d351b05ce1..094b35a080 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -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 diff --git a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts index e0fe581d9d..0661eb31ee 100644 --- a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts @@ -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' }) }, diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 33f14fb169..4a5b1831ee 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -48,6 +48,7 @@ declare module 'nightwatch' { removeFile(path: string, workspace: string): NightwatchBrowser switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult) => 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 diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh b/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh index 964da156b1..40cc0c0fb0 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh @@ -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" diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh b/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh index 98f7af21c8..62b7a1d483 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-beta.sh @@ -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" diff --git a/apps/remix-ide/ci/deploy_from_travis_remix-live.sh b/apps/remix-ide/ci/deploy_from_travis_remix-live.sh index 0aadafc75c..b0b0a8bcce 100755 --- a/apps/remix-ide/ci/deploy_from_travis_remix-live.sh +++ b/apps/remix-ide/ci/deploy_from_travis_remix-live.sh @@ -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" diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 52fed627a6..4c490aaf34 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -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()) { diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 92e0a637a9..9654f8abda 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -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) } diff --git a/apps/remix-ide/src/app/files/fileManager.ts b/apps/remix-ide/src/app/files/fileManager.ts index e232c2a132..2ff2d5a4d3 100644 --- a/apps/remix-ide/src/app/files/fileManager.ts +++ b/apps/remix-ide/src/app/files/fileManager.ts @@ -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) diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 2260443663..2b3946bca1 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -189,7 +189,7 @@ module.exports = class Filepanel extends ViewPlugin { if (err) reject(err) else resolve(data || true) }) - }) + }, false) } renameWorkspace(oldName, workspaceName) { diff --git a/apps/remix-ide/src/app/panels/layout.ts b/apps/remix-ide/src/app/panels/layout.ts index 90896c2041..51149c3859 100644 --- a/apps/remix-ide/src/app/panels/layout.ts +++ b/apps/remix-ide/src/app/panels/layout.ts @@ -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') } } diff --git a/apps/remix-ide/src/app/plugins/git.tsx b/apps/remix-ide/src/app/plugins/git.tsx index 73331d87ed..6ffa0f0835 100644 --- a/apps/remix-ide/src/app/plugins/git.tsx +++ b/apps/remix-ide/src/app/plugins/git.tsx @@ -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 = { diff --git a/apps/remix-ide/src/app/plugins/matomo.ts b/apps/remix-ide/src/app/plugins/matomo.ts index 9495d1ddd7..8aa8f61f70 100644 --- a/apps/remix-ide/src/app/plugins/matomo.ts +++ b/apps/remix-ide/src/app/plugins/matomo.ts @@ -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 { diff --git a/apps/remix-ide/src/app/plugins/remixGuide.tsx b/apps/remix-ide/src/app/plugins/remixGuide.tsx index 2fa94f2466..2a4a79cdc2 100644 --- a/apps/remix-ide/src/app/plugins/remixGuide.tsx +++ b/apps/remix-ide/src/app/plugins/remixGuide.tsx @@ -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 diff --git a/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.css b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.css new file mode 100644 index 0000000000..f6e9f504f1 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.css @@ -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; +} diff --git a/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx new file mode 100644 index 0000000000..6df97e60b6 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx @@ -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 + dispatch: React.Dispatch = () => { } + 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): void { + this.dispatch = dispatch + this.renderComponent() + } + + render() { + return ( +
    + +
    + ) + } + + 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 ( + + { + templates(window._intl).map(template => { + return + {template.items.map(item => { + return +
    +
    +
    + {item.description && {item.description}} +
    +
    + {(item.opts && item.opts.upgradeable && item.opts.upgradeable === 'uupds') && Upgradeable-UUPS} + {(item.opts && item.opts.mintable) && mintable} + {(item.opts && item.opts.burnable) && burnable} + {(item.opts && item.opts.pausable) && pausable} +
    +
    +
    + {(!template.IsArtefact || !item.isArtefact) && + createWorkspace(item)} + className="btn btn-sm mr-2 border border-primary" + > + Create + + } + + addToCurrentWorkspace(item)} + className="btn btn-sm border" + > + Add to current + + +
    +
    +
    + })} +
    + })} +
    + ) + } +} + +const createModalMessage = async ( + defaultName: string, + gitConfigNotSet: boolean, + onChangeTemplateName: (name: string) => void, + onChangeInitGit: (name: string) => void) => { + + return ( + <> + + onChangeTemplateName(e.target.value)} + onInput={(e) => onChangeTemplateName((e.target as any).value)} + /> +
    + onChangeInitGit(e.target.value)} + onInput={(e) => onChangeInitGit((e.target as any).value)} + /> + +
    + {gitConfigNotSet ? ( +
    + +
    + ) : ( + <> + )} + + ) +} + diff --git a/apps/remix-ide/src/app/plugins/templates-selection/templates.ts b/apps/remix-ide/src/app/plugins/templates-selection/templates.ts new file mode 100644 index 0000000000..a42524a285 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/templates-selection/templates.ts @@ -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 + } + ] +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/providers/environment-explorer.tsx b/apps/remix-ide/src/app/providers/environment-explorer.tsx new file mode 100644 index 0000000000..8e5fe87011 --- /dev/null +++ b/apps/remix-ide/src/app/providers/environment-explorer.tsx @@ -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 = () => {} + + constructor() { + super(profile) + this.providersFlat = {} + this.providers = { + 'Injected': [], + 'Remix VMs': [], + 'Externals': [] + } + } + + async onActivation(): Promise { + 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): void { + this.dispatch = dispatch + this.renderComponent() + } + render() { + return ( +
    + +
    + ) + } + + 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 ( + + + {this.providers['Injected'].map(provider => { + return { + 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 + } + }} + > +
    {provider.description}
    +
    + })} +
    + {this.providers['Remix VMs'].map(provider => { + return { + 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 + } + }} + > +
    {provider.description}
    +
    + })}
    + {this.providers['Externals'].map(provider => { + return { + 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 + } + }} + > +
    {provider.description}
    +
    + })}
    +
    + ) + } +} diff --git a/apps/remix-ide/src/app/providers/style/environment-explorer.css b/apps/remix-ide/src/app/providers/style/environment-explorer.css new file mode 100644 index 0000000000..b5770fba42 --- /dev/null +++ b/apps/remix-ide/src/app/providers/style/environment-explorer.css @@ -0,0 +1,5 @@ +.EECellStyle { + min-height: 6rem; + max-width: 12rem; + min-width: 10rem; +} diff --git a/apps/remix-ide/src/app/udapp/run-tab.js b/apps/remix-ide/src/app/udapp/run-tab.js index 837e8d97ba..35306c87d1 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.js +++ b/apps/remix-ide/src/app/udapp/run-tab.js @@ -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) diff --git a/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp b/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp new file mode 100644 index 0000000000..8fc0b06788 Binary files /dev/null and b/apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp differ diff --git a/apps/remix-ide/src/assets/img/Walletconnect-logo.png b/apps/remix-ide/src/assets/img/Walletconnect-logo.png new file mode 100644 index 0000000000..ea0ca5810d Binary files /dev/null and b/apps/remix-ide/src/assets/img/Walletconnect-logo.png differ diff --git a/apps/remix-ide/src/assets/img/arbitrum-arb-logo.png b/apps/remix-ide/src/assets/img/arbitrum-arb-logo.png new file mode 100644 index 0000000000..a7ee4623d6 Binary files /dev/null and b/apps/remix-ide/src/assets/img/arbitrum-arb-logo.png differ diff --git a/apps/remix-ide/src/assets/img/brave.png b/apps/remix-ide/src/assets/img/brave.png new file mode 100644 index 0000000000..a55721b1d2 Binary files /dev/null and b/apps/remix-ide/src/assets/img/brave.png differ diff --git a/apps/remix-ide/src/assets/img/foundry.png b/apps/remix-ide/src/assets/img/foundry.png new file mode 100644 index 0000000000..21863acc13 Binary files /dev/null and b/apps/remix-ide/src/assets/img/foundry.png differ diff --git a/apps/remix-ide/src/assets/img/hardhat.png b/apps/remix-ide/src/assets/img/hardhat.png new file mode 100644 index 0000000000..e29c2cdaea Binary files /dev/null and b/apps/remix-ide/src/assets/img/hardhat.png differ diff --git a/apps/remix-ide/src/assets/img/metamask.png b/apps/remix-ide/src/assets/img/metamask.png new file mode 100644 index 0000000000..96081fe838 Binary files /dev/null and b/apps/remix-ide/src/assets/img/metamask.png differ diff --git a/apps/remix-ide/src/assets/img/optimism-ethereum-op-logo.png b/apps/remix-ide/src/assets/img/optimism-ethereum-op-logo.png new file mode 100644 index 0000000000..cb31c98074 Binary files /dev/null and b/apps/remix-ide/src/assets/img/optimism-ethereum-op-logo.png differ diff --git a/apps/remix-ide/src/assets/img/trust-wallet.png b/apps/remix-ide/src/assets/img/trust-wallet.png new file mode 100644 index 0000000000..87ac7a80de Binary files /dev/null and b/apps/remix-ide/src/assets/img/trust-wallet.png differ diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index bbe6d302f1..00a64273f0 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -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 + provider:{ + sendAsync: (payload: any) => Promise + } +} + 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) { diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index b54b8d371b..fd127fb20c 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -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 } diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 95696ae8c3..92f76e950e 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -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) => { diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index ba321480ed..e4977c43f2 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -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 } } diff --git a/libs/ghaction-helper/package.json b/libs/ghaction-helper/package.json index 2290c89071..c3f69e6aff 100644 --- a/libs/ghaction-helper/package.json +++ b/libs/ghaction-helper/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-analyzer/package.json b/libs/remix-analyzer/package.json index 6550d471ce..0aaf874b51 100644 --- a/libs/remix-analyzer/package.json +++ b/libs/remix-analyzer/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-api/src/lib/plugins/layout-api.ts b/libs/remix-api/src/lib/plugins/layout-api.ts new file mode 100644 index 0000000000..cb53309219 --- /dev/null +++ b/libs/remix-api/src/lib/plugins/layout-api.ts @@ -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 + } +} diff --git a/libs/remix-api/src/lib/plugins/matomo-api.ts b/libs/remix-api/src/lib/plugins/matomo-api.ts new file mode 100644 index 0000000000..9bd368f17c --- /dev/null +++ b/libs/remix-api/src/lib/plugins/matomo-api.ts @@ -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 + } +} diff --git a/libs/remix-api/src/lib/plugins/pinned-panel-api.ts b/libs/remix-api/src/lib/plugins/pinned-panel-api.ts new file mode 100644 index 0000000000..db3aacf199 --- /dev/null +++ b/libs/remix-api/src/lib/plugins/pinned-panel-api.ts @@ -0,0 +1,11 @@ +import { IFilePanel } from '@remixproject/plugin-api' +import { StatusEvents } from '@remixproject/plugin-utils' + +export interface IPinnedPanelApi { + events:{ + + } & StatusEvents + methods: { + currentFocus(): Promise + } +} diff --git a/libs/remix-api/src/lib/plugins/sidePanel-api.ts b/libs/remix-api/src/lib/plugins/sidePanel-api.ts new file mode 100644 index 0000000000..7f7ccdfd19 --- /dev/null +++ b/libs/remix-api/src/lib/plugins/sidePanel-api.ts @@ -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: { + + } +} diff --git a/libs/remix-api/src/lib/remix-api.ts b/libs/remix-api/src/lib/remix-api.ts index c2805a602d..71b3536910 100644 --- a/libs/remix-api/src/lib/remix-api.ts +++ b/libs/remix-api/src/lib/remix-api.ts @@ -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 \ No newline at end of file diff --git a/libs/remix-astwalker/package.json b/libs/remix-astwalker/package.json index a6a82f6cac..6a1cfb3fe3 100644 --- a/libs/remix-astwalker/package.json +++ b/libs/remix-astwalker/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-core-plugin/src/lib/compiler-content-imports.ts b/libs/remix-core-plugin/src/lib/compiler-content-imports.ts index 54f99a2984..df715721d2 100644 --- a/libs/remix-core-plugin/src/lib/compiler-content-imports.ts +++ b/libs/remix-core-plugin/src/lib/compiler-content-imports.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 + } } diff --git a/libs/remix-debug/package.json b/libs/remix-debug/package.json index 72b01e0e9c..5a86998144 100644 --- a/libs/remix-debug/package.json +++ b/libs/remix-debug/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-lib/package.json b/libs/remix-lib/package.json index dffc187d0a..c5d8136421 100644 --- a/libs/remix-lib/package.json +++ b/libs/remix-lib/package.json @@ -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" -} +} \ No newline at end of file diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts index bbb19bceb4..621e1c475a 100644 --- a/libs/remix-lib/src/execution/txRunnerWeb3.ts +++ b/libs/remix-lib/src/execution/txRunnerWeb3.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, diff --git a/libs/remix-simulator/package.json b/libs/remix-simulator/package.json index 8a9a1a0bbc..6b45fba53a 100644 --- a/libs/remix-simulator/package.json +++ b/libs/remix-simulator/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-solidity/package.json b/libs/remix-solidity/package.json index 7a61d805e6..85218ddd77 100644 --- a/libs/remix-solidity/package.json +++ b/libs/remix-solidity/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-tests/package.json b/libs/remix-tests/package.json index f9db3aae99..c0926f75f4 100644 --- a/libs/remix-tests/package.json +++ b/libs/remix-tests/package.json @@ -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" } \ No newline at end of file diff --git a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx index 0c57ebaffb..009cb410b6 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx @@ -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() { diff --git a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx index b4a4b5f66a..2ae17389e6 100644 --- a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx +++ b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx @@ -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) { diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 6c0b67e705..6baeaec1fc 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -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 { diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 686c014383..658eb20fba 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -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']) } }) diff --git a/libs/remix-ui/git/src/components/branchHeader.tsx b/libs/remix-ui/git/src/components/branchHeader.tsx index debb86bd5a..91647687c0 100644 --- a/libs/remix-ui/git/src/components/branchHeader.tsx +++ b/libs/remix-ui/git/src/components/branchHeader.tsx @@ -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 ( +
    +
    +
    + {getName() ? ( + + {getName() ?? ''} + {context.currentBranch && context.currentBranch.remote && context.currentBranch.remote.name ? ` on ${context.currentBranch.remote.name}` : ''} + + ) : null + } + {context.currentBranch && context.currentBranch.name ? + + {context.currentBranch && context.currentBranch.name}{changed?'*':''} + : null} + {(latestCommit && latestCommit.commit && latestCommit.commit.message) ? + + {latestCommit ? + latestCommit.commit && latestCommit.commit.message ? `"${latestCommit.commit.message}"` : '' : null} + + : null} + {isDetached ? + + {isDetached ? + <>You are in a detached state : null} + + : null} + {context.storage.enabled ? + + {context.storage.used} MB used + ({context.storage.percentUsed} %) + + : null} +
    +
    +
    + ) + } + return (<>
    -
    - - {changed ? '*' : ''}{context.currentBranch && context.currentBranch.name} -
    - {latestCommit ? -
    - {latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : ''} -
    : null} - {isDetached ? -
    - You are in a detached state -
    : null} +

    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/buttons/commitmessage.tsx b/libs/remix-ui/git/src/components/buttons/commitmessage.tsx index 17fc807a79..e5726df46c 100644 --- a/libs/remix-ui/git/src/components/buttons/commitmessage.tsx +++ b/libs/remix-ui/git/src/components/buttons/commitmessage.tsx @@ -126,7 +126,7 @@ export const CommitMessage = () => { return ( <> -
    +
    - ); + + if (rest.tooltip) { + + return ( + + + + ); + } else { + return ( + + ); + } }; export default GitUIButton; \ No newline at end of file diff --git a/libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx b/libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx index 64d36df050..628e57f0fb 100644 --- a/libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx +++ b/libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx @@ -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') diff --git a/libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx b/libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx index 22879e9a94..c8273914bc 100644 --- a/libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx +++ b/libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx @@ -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() } diff --git a/libs/remix-ui/git/src/components/github/branchselect.tsx b/libs/remix-ui/git/src/components/github/branchselect.tsx index e272f266f9..1e87e195e9 100644 --- a/libs/remix-ui/git/src/components/github/branchselect.tsx +++ b/libs/remix-ui/git/src/components/github/branchselect.tsx @@ -38,7 +38,7 @@ export const BranchSelect = (props: BranchySelectProps) => { return (<>{branchOptions && branchOptions.length ?
    - + sendToMatomo(gitMatomoEventTypes.COPYGITHUBDEVICECODE)} content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} />


    @@ -107,21 +115,29 @@ export const GetDeviceCode = () => { { (context.gitHubUser && context.gitHubUser.login) ?
    - - - Connected as {context.gitHubUser.login} - + +
    Connected as {context.gitHubUser.login}
    +
    + {context.gitHubUser.avatar_url ? + : null} +
    +
    +
    + {context.gitHubUser.html_url ? <> + + {context.gitHubUser.html_url} : null} + {context.userEmails && context.userEmails.length > 0 ? <> + {context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => { return

    {email.email}
    - })} - - - + })} : null} +
    +
    : null } ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/github/repositoryselect.tsx b/libs/remix-ui/git/src/components/github/repositoryselect.tsx index 6d8aabb91b..72b055ba9a 100644 --- a/libs/remix-ui/git/src/components/github/repositoryselect.tsx +++ b/libs/remix-ui/git/src/components/github/repositoryselect.tsx @@ -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(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 ( - <> + <> { show ? - selectRepo(e)} + theme={selectTheme} + styles={selectStyles} + isClearable={true} + placeholder="Type to search for a repository..." + isLoading={loading} + /> + { selected ? null : } + : null } ); }; diff --git a/libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx b/libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx index 497f5b6e1f..9c3e879c71 100644 --- a/libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx +++ b/libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx @@ -18,7 +18,7 @@ export const SelectAndCloneRepositories = (props: RepositoriesProps) => { const [branch, setBranch] = useState({ name: "" }); const [repo, setRepo] = useState(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 ( <> + - - {repo &&} + {repo && } {repo && branch && branch.name && branch.name !== '0' ? : null} + {repo && (!branch || branch.name === '0') ? + : null} + ) } diff --git a/libs/remix-ui/git/src/components/gitui.tsx b/libs/remix-ui/git/src/components/gitui.tsx index 9a4289b1d5..814ad4cdd8 100644 --- a/libs/remix-ui/git/src/components/gitui.tsx +++ b/libs/remix-ui/git/src/components/gitui.tsx @@ -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) ? - : -
    + <>{(!gitState.canUseApp) ? : +
    @@ -180,68 +186,67 @@ export const GitUI = (props: IGitUi) => { {setup && !needsInit ? : null} {needsInit ? : null} {!setup && !needsInit ? - <> - - - - - <> - - - - -
    - - - <> - - - -
    - - - <> - - - -
    - - - <> - - -
    - - - <> - - - -
    - - - <> - - -
    - - - <> - -
    - - -
    -
    - - - <> - - - - -
    - + <> + + + +
    + + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    + +
    + +
    +
    +
    + + +
    + +
    +
    + +
    : null}
    @@ -250,4 +255,4 @@ export const GitUI = (props: IGitUi) => {
    } ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/branchedetails.tsx b/libs/remix-ui/git/src/components/navigation/branchedetails.tsx index 40a40c5b70..502a1244d4 100644 --- a/libs/remix-ui/git/src/components/navigation/branchedetails.tsx +++ b/libs/remix-ui/git/src/components/navigation/branchedetails.tsx @@ -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 ( <>
    @@ -63,30 +69,31 @@ export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) = activePanel === eventKey ? : } -
    {branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}
    +
    {branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}
    - {context.currentBranch && context.currentBranch.name === branch.name ? - { }}> - - - : - checkout(branch)}> - - - } + {allowCheckout ? + context.currentBranch && context.currentBranch.name === branch.name ? + { }}> + + + : + checkout(branch)}> + + + : null} {!branch.remote && canFetch() && <> - fetchBranch()}> - openRemote()}> + fetchBranch()}> + openRemote()}> } {branch.remote?.url && <> - reloadBranch()}> + reloadBranch()}> } {branch.remote?.url && <> - openRemote()}> + openRemote()}> } diff --git a/libs/remix-ui/git/src/components/navigation/branches.tsx b/libs/remix-ui/git/src/components/navigation/branches.tsx index b9dd9b06f2..21decc6e7a 100644 --- a/libs/remix-ui/git/src/components/navigation/branches.tsx +++ b/libs/remix-ui/git/src/components/navigation/branches.tsx @@ -23,10 +23,10 @@ export const BranchesNavigation = ({ eventKey, activePanel, callback }) => { { activePanel === eventKey ? : } - +
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/clone.tsx b/libs/remix-ui/git/src/components/navigation/clone.tsx index ee9e1579bf..d81c8fc9ca 100644 --- a/libs/remix-ui/git/src/components/navigation/clone.tsx +++ b/libs/remix-ui/git/src/components/navigation/clone.tsx @@ -20,10 +20,10 @@ export const CloneNavigation = ({ eventKey, activePanel, callback }) => { { activePanel === eventKey ? : } - +
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/commands.tsx b/libs/remix-ui/git/src/components/navigation/commands.tsx index 9a3b3f03df..0a035beabe 100644 --- a/libs/remix-ui/git/src/components/navigation/commands.tsx +++ b/libs/remix-ui/git/src/components/navigation/commands.tsx @@ -21,15 +21,14 @@ export const CommandsNavigation = ({ eventKey, activePanel, callback }) => { return ( <>
    - handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> + handleClick()} role={'button'} className="nav d-flex justify-content-start align-items-center w-75"> { activePanel === eventKey ? : } - - - + +
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/commitdetails.tsx b/libs/remix-ui/git/src/components/navigation/commitdetails.tsx index 1cae572a00..2e49875f52 100644 --- a/libs/remix-ui/git/src/components/navigation/commitdetails.tsx +++ b/libs/remix-ui/git/src/components/navigation/commitdetails.tsx @@ -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 ? : } - +
    ); diff --git a/libs/remix-ui/git/src/components/navigation/commits.tsx b/libs/remix-ui/git/src/components/navigation/commits.tsx index a3c59950e3..44c9299eb8 100644 --- a/libs/remix-ui/git/src/components/navigation/commits.tsx +++ b/libs/remix-ui/git/src/components/navigation/commits.tsx @@ -48,9 +48,8 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran } {ahead? : null} {behind? : null} - + - {showButtons ? @@ -60,4 +59,4 @@ export const CommitsNavigation = ({ eventKey, activePanel, callback, title, bran ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/github.tsx b/libs/remix-ui/git/src/components/navigation/github.tsx index 2e45e3317a..5324cfbdbb 100644 --- a/libs/remix-ui/git/src/components/navigation/github.tsx +++ b/libs/remix-ui/git/src/components/navigation/github.tsx @@ -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 ? : } - + - ); -} \ No newline at end of file + ) +} diff --git a/libs/remix-ui/git/src/components/navigation/log.tsx b/libs/remix-ui/git/src/components/navigation/log.tsx index 7b148b8de2..63b56bdfa9 100644 --- a/libs/remix-ui/git/src/components/navigation/log.tsx +++ b/libs/remix-ui/git/src/components/navigation/log.tsx @@ -50,7 +50,7 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => { { activePanel === eventKey ? : } - + {logState.errorCount > 0 && (
    {logState.errorCount} @@ -84,4 +84,4 @@ export const LogNavigation = ({ eventKey, activePanel, callback }) => {
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/remotes.tsx b/libs/remix-ui/git/src/components/navigation/remotes.tsx index 8bb8c95494..b458568080 100644 --- a/libs/remix-ui/git/src/components/navigation/remotes.tsx +++ b/libs/remix-ui/git/src/components/navigation/remotes.tsx @@ -23,10 +23,10 @@ export const RemotesNavigation = ({ eventKey, activePanel, callback }) => { { activePanel === eventKey ? : } - + ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/remotesdetails.tsx b/libs/remix-ui/git/src/components/navigation/remotesdetails.tsx index 5648390c98..4a702b1193 100644 --- a/libs/remix-ui/git/src/components/navigation/remotesdetails.tsx +++ b/libs/remix-ui/git/src/components/navigation/remotesdetails.tsx @@ -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 ( <>
    -
    handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'> +
    handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'> { activePanel === eventKey ? : } -
    +
    {remote.name} {remote.url}
    - {context.defaultRemote && context.defaultRemote?.url === remote.url ? - { }} disabledCondition={true}> + {isDefault() ? + { }} disabledCondition={true}> : - + } - { + { await actions.fetch({ remote }) }}> actions.removeRemote(remote)}> - {remote?.url && openRemote()}>} + {remote?.url && openRemote()}>}
    ); diff --git a/libs/remix-ui/git/src/components/navigation/settings.tsx b/libs/remix-ui/git/src/components/navigation/settings.tsx index 9ea4d2c175..1e80856a15 100644 --- a/libs/remix-ui/git/src/components/navigation/settings.tsx +++ b/libs/remix-ui/git/src/components/navigation/settings.tsx @@ -24,7 +24,7 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => { { activePanel === eventKey ? : } - + @@ -38,4 +38,4 @@ export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx b/libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx index e6eb60a36c..7baf2f50e1 100644 --- a/libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx +++ b/libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx @@ -24,12 +24,13 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) => return ( <> -
    - handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> +
    + handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75' + > { activePanel === eventKey ? : } - + @@ -39,4 +40,4 @@ export const SourceControlNavigation = ({ eventKey, activePanel, callback }) =>
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx b/libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx index 2eec2305d5..40cdad574e 100644 --- a/libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx +++ b/libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx @@ -38,10 +38,10 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
    { activePanel === eventKey ? - + {group.name === 'Changes' ? }> - + : null} : null @@ -49,4 +49,4 @@ export const SourceControlGroupNavigation = (props: SourceControlGroupNavigation
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/branches.tsx b/libs/remix-ui/git/src/components/panels/branches.tsx index 2978e56e1b..f42ad292a4 100644 --- a/libs/remix-ui/git/src/components/panels/branches.tsx +++ b/libs/remix-ui/git/src/components/panels/branches.tsx @@ -5,6 +5,8 @@ import GitUIButton from "../buttons/gituibutton"; import { gitPluginContext } from "../gitui"; import { LocalBranchDetails } from "./branches/localbranchdetails"; import { RemoteBranchDetails } from "./branches/remotebranchedetails"; +import { faSync } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export const Branches = () => { const context = React.useContext(gitPluginContext) @@ -17,23 +19,41 @@ export const Branches = () => { return ( <> -
    +
    {context.branches && context.branches.length ?
    - {context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => { - return ( - - ); - })} +
    + + {context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => { + return ( + + ); + })} +

    + {context.upstream ? + <> +
    + + {context.branches && context.branches.filter((branch, index) => branch.remote && branch.remote.name === context.upstream.name).map((branch, index) => { + return ( + + ); + })} + { + await actions.fetch({ + remote: context.upstream + }) + }}> +
    : null}
    : null} {context.currentBranch && context.currentBranch.name !== '' && (!context.branches || context.branches.length === 0) ? -
    Current branch is `{context.currentBranch.name}` but you have no commits.
    +
    Current branch is {`${context.currentBranch.name}`} but you have no commits.
    : null} - +
    { actions.createBranch(newBranch.value)} - className="btn w-md-25 w-100 btn-primary" + className="btn w-md-25 w-100 btn-primary mb-3" id="createbranch-btn" > create new branch @@ -56,4 +76,4 @@ export const Branches = () => {
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/branches/branchdifferencedetails.tsx b/libs/remix-ui/git/src/components/panels/branches/branchdifferencedetails.tsx index d50ad0d511..b2a266c00a 100644 --- a/libs/remix-ui/git/src/components/panels/branches/branchdifferencedetails.tsx +++ b/libs/remix-ui/git/src/components/panels/branches/branchdifferencedetails.tsx @@ -33,7 +33,7 @@ export const BranchDifferenceDetails = (props: BrancheDifferenceProps) => { } return ( - +
    diff --git a/libs/remix-ui/git/src/components/panels/branches/localbranchdetails.tsx b/libs/remix-ui/git/src/components/panels/branches/localbranchdetails.tsx index 6608bd2ff3..39c3cc15e4 100644 --- a/libs/remix-ui/git/src/components/panels/branches/localbranchdetails.tsx +++ b/libs/remix-ui/git/src/components/panels/branches/localbranchdetails.tsx @@ -5,11 +5,13 @@ import { CommitDetailsNavigation } from "../../navigation/commitdetails"; import { gitActionsContext } from "../../../state/context"; import { gitPluginContext } from "../../gitui"; import { branch } from "@remix-api"; +import { gitMatomoEventTypes } from "../../../types"; import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; import { CommitDetailsItems } from "../commits/commitdetailsitem"; import { CommitDetails } from "../commits/commitdetails"; import { BranchDifferences } from "./branchdifferences"; import GitUIButton from "../../buttons/gituibutton"; +import { sendToMatomo } from "../../../lib/pluginActions"; export interface BrancheDetailsProps { branch: branch; @@ -32,8 +34,9 @@ export const LocalBranchDetails = (props: BrancheDetailsProps) => { } }, [activePanel]) - const checkout = (branch: branch) => { - actions.checkout({ + const checkout = async (branch: branch) => { + await sendToMatomo(gitMatomoEventTypes.CHECKOUT_LOCAL_BRANCH) + await actions.checkout({ ref: branch.name, remote: branch.remote && branch.remote.name || null, refresh: true @@ -63,7 +66,7 @@ export const LocalBranchDetails = (props: BrancheDetailsProps) => { } return ( - + <>
    diff --git a/libs/remix-ui/git/src/components/panels/branches/remotebranchedetails.tsx b/libs/remix-ui/git/src/components/panels/branches/remotebranchedetails.tsx index 9e4f2ed06b..8b4608f821 100644 --- a/libs/remix-ui/git/src/components/panels/branches/remotebranchedetails.tsx +++ b/libs/remix-ui/git/src/components/panels/branches/remotebranchedetails.tsx @@ -5,17 +5,20 @@ import { CommitDetailsNavigation } from "../../navigation/commitdetails"; import { gitActionsContext } from "../../../state/context"; import { gitPluginContext } from "../../gitui"; import { branch } from "@remix-api"; +import { gitMatomoEventTypes } from "../../../types"; import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; import { CommitDetailsItems } from "../commits/commitdetailsitem"; import { CommitDetails } from "../commits/commitdetails"; import GitUIButton from "../../buttons/gituibutton"; +import { sendToMatomo } from "../../../lib/pluginActions"; export interface BrancheDetailsProps { branch: branch; + allowCheckout: boolean } export const RemoteBranchDetails = (props: BrancheDetailsProps) => { - const { branch } = props; + const { branch, allowCheckout } = props; const actions = React.useContext(gitActionsContext) const context = React.useContext(gitPluginContext) const [activePanel, setActivePanel] = useState(""); @@ -46,6 +49,7 @@ export const RemoteBranchDetails = (props: BrancheDetailsProps) => { }, [context.remoteBranchCommits]) const checkout = async (branch: branch) => { + await sendToMatomo(gitMatomoEventTypes.CHECKOUT_REMOTE_BRANCH) await actions.fetch({ remote: branch.remote, ref: branch, @@ -89,7 +93,7 @@ export const RemoteBranchDetails = (props: BrancheDetailsProps) => { } return ( - + <>
    diff --git a/libs/remix-ui/git/src/components/panels/clone.tsx b/libs/remix-ui/git/src/components/panels/clone.tsx index e72cf984af..e7719fe68e 100644 --- a/libs/remix-ui/git/src/components/panels/clone.tsx +++ b/libs/remix-ui/git/src/components/panels/clone.tsx @@ -1,14 +1,19 @@ -import React, { useState } from "react"; -import { Alert, Form, FormControl, InputGroup } from "react-bootstrap"; -import { useLocalStorage } from "../../hooks/useLocalStorage"; -import { gitActionsContext } from "../../state/context"; -import { gitPluginContext } from "../gitui"; -import { SelectAndCloneRepositories } from "../github/selectandclonerepositories"; -import { RemixUiCheckbox } from "@remix-ui/checkbox"; -import GitUIButton from "../buttons/gituibutton"; - -export const Clone = () => { +import React, { useState } from "react" +import { Alert, Form, FormControl, InputGroup } from "react-bootstrap" +import { useLocalStorage } from "../../hooks/useLocalStorage" +import { gitActionsContext } from "../../state/context" +import { gitPluginContext } from "../gitui" +import { SelectAndCloneRepositories } from "../github/selectandclonerepositories" +import { RemixUiCheckbox } from "@remix-ui/checkbox" +import GitUIButton from "../buttons/gituibutton" + +interface CloneProps { + hideLoadFromGitHub?: boolean +} + +export const Clone = (props: CloneProps) => { + const { hideLoadFromGitHub } = props const context = React.useContext(gitPluginContext) const actions = React.useContext(gitActionsContext) const [cloneUrl, setCloneUrl] = useLocalStorage( @@ -64,21 +69,24 @@ export const Clone = () => { return ( <>
    - + {!hideLoadFromGitHub ? <> + + +
    : null} + + onGitHubCloneUrlChange(e.target.value)} aria-describedby="urlprepend" /> - onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-1 mt-2" placeholder="branch" type="text" id="clonebranch" /> + onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-2 mt-2" placeholder="branch" type="text" id="clonebranch" /> { clone() }}>clone
    - -
    - + - - + + --depth @@ -98,4 +106,4 @@ export const Clone = () => {
    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/commands.tsx b/libs/remix-ui/git/src/components/panels/commands.tsx index f1b9cfc952..162603eb26 100644 --- a/libs/remix-ui/git/src/components/panels/commands.tsx +++ b/libs/remix-ui/git/src/components/panels/commands.tsx @@ -6,9 +6,9 @@ import { Merge } from "./commands/merge"; export const Commands = () => { return ( - <> +

    - ) -} \ No newline at end of file +
    ) +} diff --git a/libs/remix-ui/git/src/components/panels/commands/fetch.tsx b/libs/remix-ui/git/src/components/panels/commands/fetch.tsx index d7b8ee0c30..8387cf5b14 100644 --- a/libs/remix-ui/git/src/components/panels/commands/fetch.tsx +++ b/libs/remix-ui/git/src/components/panels/commands/fetch.tsx @@ -15,11 +15,11 @@ export const Fetch = () => {
    actions.fetch({ remote: context.upstream, - })} className="btn btn-primary mr-1 w-50">
    Fetch {context.upstream && context.upstream.name}
    + })} className="btn btn-secondary mr-1 w-50">
    Fetch {context.upstream && context.upstream.name}
    actions.fetch({ remote: context.upstream, ref: context.currentBranch - })} className="btn btn-primary w-50 long-and-truncated">Fetch {context.currentBranch.name} + })} className="btn btn-secondary w-50 long-and-truncated">Fetch {context.currentBranch.name}
    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/commands/pushpull.tsx b/libs/remix-ui/git/src/components/panels/commands/pushpull.tsx index 14f522c25f..3fae193fd9 100644 --- a/libs/remix-ui/git/src/components/panels/commands/pushpull.tsx +++ b/libs/remix-ui/git/src/components/panels/commands/pushpull.tsx @@ -5,7 +5,9 @@ import { selectStyles, selectTheme } from "../../../types/styles"; import Select, { Options, OptionsOrGroups } from 'react-select' import GitUIButton from "../../buttons/gituibutton"; import { remote } from "@remix-api"; +import { gitMatomoEventTypes } from "../../../types"; import { relative } from "path"; +import { sendToMatomo } from "../../../lib/pluginActions"; export const PushPull = () => { const context = React.useContext(gitPluginContext) @@ -22,7 +24,7 @@ export const PushPull = () => { setRemoteBranch(context.currentBranch.name) setLocalBranch(context.currentBranch.name) - const currentUpstreamIsInRemotes = context.upstream && context.remotes.find(r => r.name === context.upstream.name) + const currentUpstreamIsInRemotes = context.upstream && context.remotes.find(r => r.name === context.upstream.name && r.url === context.upstream.url) if (!context.upstream || !currentUpstreamIsInRemotes) { if (context.currentBranch && context.currentBranch.remote && context.currentBranch.remote.name) { actions.setUpstreamRemote(context.currentBranch.remote) @@ -39,11 +41,19 @@ export const PushPull = () => { } }, [context.currentBranch, context.remotes, context.branches]) - const onRemoteBranchChange = (value: string) => { + useEffect(() => { + if (context.defaultRemote && context.remotes.find(r => r.name === context.defaultRemote.name && r.url === context.defaultRemote.url)) { + actions.setUpstreamRemote(context.defaultRemote) + } + },[context.defaultRemote]) + + const onRemoteBranchChange = async (value: string) => { + await sendToMatomo(gitMatomoEventTypes.SETREMOTEBRANCHINCOMMANDS) setRemoteBranch(value) } - const onLocalBranchChange = (value: any) => { + const onLocalBranchChange = async (value: any) => { + await sendToMatomo(gitMatomoEventTypes.SETLOCALBRANCHINCOMMANDS) setLocalBranch(value) } @@ -51,6 +61,7 @@ export const PushPull = () => { const remote: remote = context.remotes.find(r => r.name === value) if (remote) { actions.setUpstreamRemote(remote) + actions.setDefaultRemote(remote) } } @@ -112,15 +123,20 @@ export const PushPull = () => { }) setLocalBranchOptions(localBranches) + if (!context.upstream){ + setRemoteBranchOptions([]) + return + } const remoteBranches = context.branches && context.branches.length > 0 && context.branches - .filter(branch => branch.remote) + .filter(branch => branch.remote && branch.remote.name === context.upstream.name) + .filter(branch => branch.name !== 'HEAD') .map(repo => { return { value: repo.name, label: repo.name } } ) setRemoteBranchOptions(remoteBranches) - }, [context.branches]) + }, [context.branches, context.upstream]) useEffect(() => { @@ -148,7 +164,7 @@ export const PushPull = () => { push()} className="btn btn-primary">Push
    - + { placeholder="Type to search for a branch..." /> - + onForceChange(e)} className="remixui_autocompile custom-control-input" type="checkbox" data-id="compilerContainerAutoCompile" id="forcepush" title="Force Push" /> - +
    + onForceChange(e)} className="remixui_autocompile form-check-input custom-control-input" type="checkbox" data-id="compilerContainerAutoCompile" id="forcepush" title="Force Push" /> +
    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/commits/commitdetails.tsx b/libs/remix-ui/git/src/components/panels/commits/commitdetails.tsx index c706611486..28c6020f75 100644 --- a/libs/remix-ui/git/src/components/panels/commits/commitdetails.tsx +++ b/libs/remix-ui/git/src/components/panels/commits/commitdetails.tsx @@ -6,6 +6,7 @@ import { gitActionsContext } from "../../../state/context"; import { gitPluginContext } from "../../gitui"; import { CommitDetailsItems } from "./commitdetailsitem"; import { branch, remote } from "@remix-ui/git"; +import { removeGitFromUrl } from "../../../utils"; export interface CommitDetailsProps { commit: ReadCommitResult; @@ -27,7 +28,7 @@ export const CommitDetails = (props: CommitDetailsProps) => { }, [activePanel]) const getRemote = (): remote | null => { - return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null + return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : branch.remote ? branch.remote : null } const commitsAhead = (remote: remote) => { @@ -39,19 +40,19 @@ export const CommitDetails = (props: CommitDetailsProps) => { return commitsAhead(getRemote()).findIndex((c) => c.oid === commit.oid) > -1 } - const openFileOnRemote = (file: string, hash: string) => { + const openFileOnRemote = (file: string, hash: string, branch: branch) => { if (!getRemote()) return - window.open(`${getRemote() ? `${getRemote().url}/blob/${hash}/${file}` : ""}`, "_blank") + window.open(`${getRemote() ? `${removeGitFromUrl(getRemote().url)}/blob/${hash}/${file}` : ""}`, "_blank") } return ( - + <> {context.commitChanges && context.commitChanges.filter( (change) => change.hashModified === commit.oid && change.hashOriginal === commit.commit.parent[0] ).map((change, index) => { - return () + return () })} diff --git a/libs/remix-ui/git/src/components/panels/commits/commitdetailsitem.tsx b/libs/remix-ui/git/src/components/panels/commits/commitdetailsitem.tsx index fd111b47e6..cf7d80ed88 100644 --- a/libs/remix-ui/git/src/components/panels/commits/commitdetailsitem.tsx +++ b/libs/remix-ui/git/src/components/panels/commits/commitdetailsitem.tsx @@ -4,15 +4,17 @@ import path from "path"; import { gitActionsContext, pluginActionsContext } from "../../../state/context"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faGlobe } from "@fortawesome/free-solid-svg-icons"; +import GitUIButton from "../../buttons/gituibutton"; export interface CCommitDetailsItemsProps { commitChange: commitChange; isAheadOfRepo: boolean; - openFileOnRemote: (file: string, hash: string) => void; + openFileOnRemote: (file: string, hash: string, branch: branch) => void; + branch: branch } export const CommitDetailsItems = (props: CCommitDetailsItemsProps) => { - const { commitChange, isAheadOfRepo, openFileOnRemote } = props; + const { commitChange, isAheadOfRepo, openFileOnRemote, branch } = props; const actions = React.useContext(gitActionsContext) const pluginActions = React.useContext(pluginActionsContext) @@ -22,7 +24,7 @@ export const CommitDetailsItems = (props: CCommitDetailsItemsProps) => { } const openRemote = () => { - openFileOnRemote(commitChange.path, commitChange.hashModified) + openFileOnRemote(commitChange.path, commitChange.hashModified, branch) } function FunctionStatusIcons() { @@ -43,7 +45,8 @@ export const CommitDetailsItems = (props: CCommitDetailsItemsProps) => {
    {!isAheadOfRepo ? - openRemote()} className="pointer mr-1 align-self-center" /> : <>} + openRemote()}> + : <>}
    diff --git a/libs/remix-ui/git/src/components/panels/commits/commitsummary.tsx b/libs/remix-ui/git/src/components/panels/commits/commitsummary.tsx index 1672b883e0..bff01b4712 100644 --- a/libs/remix-ui/git/src/components/panels/commits/commitsummary.tsx +++ b/libs/remix-ui/git/src/components/panels/commits/commitsummary.tsx @@ -3,18 +3,20 @@ import { default as dateFormat } from "dateformat"; import React from "react"; import { faGlobe } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { remote } from "@remix-ui/git"; +import { branch, remote } from "@remix-ui/git"; import GitUIButton from "../../buttons/gituibutton"; import { gitPluginContext } from "../../gitui"; +import { removeGitFromUrl } from "../../../utils"; export interface CommitSummaryProps { commit: ReadCommitResult; checkout: (oid: string) => void; isAheadOfRepo: boolean + branch: branch } export const CommitSummary = (props: CommitSummaryProps) => { - const { commit, checkout, isAheadOfRepo } = props; + const { commit, checkout, isAheadOfRepo, branch } = props; const context = React.useContext(gitPluginContext) const getDate = (commit: ReadCommitResult) => { @@ -46,12 +48,12 @@ export const CommitSummary = (props: CommitSummaryProps) => { }; const getRemote = (): remote | null => { - return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null + return branch.remote? branch.remote: context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null } const openRemote = () => { if (getRemote()) - window.open(`${getRemote().url}/commit/${commit.oid}`, '_blank'); + window.open(`${removeGitFromUrl(getRemote().url)}/commit/${commit.oid}`, '_blank'); } function removeLineBreaks(str: string): string { return str.replace(/(\r\n|\n|\r)/gm, ''); @@ -64,7 +66,7 @@ export const CommitSummary = (props: CommitSummaryProps) => {
    {commit.commit.author.name || ""} {getDate(commit)} - {getRemote() && getRemote()?.url && !isAheadOfRepo && openRemote()}>} + {getRemote() && getRemote()?.url && !isAheadOfRepo && openRemote()}>} ) } \ No newline at end of file diff --git a/libs/remix-ui/git/src/components/panels/githubcredentials.tsx b/libs/remix-ui/git/src/components/panels/githubcredentials.tsx index 300e6797f0..3635fbe988 100644 --- a/libs/remix-ui/git/src/components/panels/githubcredentials.tsx +++ b/libs/remix-ui/git/src/components/panels/githubcredentials.tsx @@ -5,6 +5,8 @@ import { CustomTooltip } from "@remix-ui/helper"; import { useIntl, FormattedMessage } from "react-intl"; import { CopyToClipboard } from "@remix-ui/clipboard"; +import { gitMatomoEventTypes } from "../../types"; +import { sendToMatomo } from "../../lib/pluginActions"; export const GitHubCredentials = () => { const context = React.useContext(gitPluginContext) @@ -19,7 +21,7 @@ export const GitHubCredentials = () => { useEffect(() => { refresh() - if (context.gitHubUser){ + if (context.gitHubUser) { setScopeWarning(!(context.gitHubScopes && context.gitHubScopes.length > 0)) } else { setScopeWarning(false) @@ -39,6 +41,7 @@ export const GitHubCredentials = () => { } async function saveGithubToken() { + await sendToMatomo(gitMatomoEventTypes.SAVEMANUALGITHUBCREDENTIALS) await pluginactions.saveGitHubCredentials({ username: githubUsername, email: githubEmail, @@ -67,14 +70,20 @@ export const GitHubCredentials = () => { return ( <> -
    - handleChangeTokenState(e.target.value)} /> + +

    + + + handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-3" placeholder="* Git username" type="text" id="githubUsername" /> + + handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-3" placeholder="* Git email" type="text" id="githubEmail" /> + +
    + handleChangeTokenState(e.target.value)} />
    - handleChangeUserNameState(e.target.value)} value={githubUsername} className="form-control mb-1" placeholder="Git username" type="text" id="githubUsername" /> - handleChangeEmailState(e.target.value)} value={githubEmail} className="form-control mb-1" placeholder="Git email" type="text" id="githubEmail" />
    - {scopeWarning? -
    Your GitHub token may not have the correct permissions. Please use the login with GitHub feature.
    :null} + {scopeWarning ? +
    Your GitHub token may not have the correct permissions. Please use the login with GitHub feature.
    : null}
    ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/init.tsx b/libs/remix-ui/git/src/components/panels/init.tsx index e58163460f..37969cfb0b 100644 --- a/libs/remix-ui/git/src/components/panels/init.tsx +++ b/libs/remix-ui/git/src/components/panels/init.tsx @@ -4,6 +4,7 @@ import React, { useContext } from 'react'; import { FormattedMessage } from 'react-intl'; import { gitActionsContext } from '../../state/context'; import GitUIButton from '../buttons/gituibutton'; +import { Clone } from './clone'; export const Init = () => { @@ -15,15 +16,20 @@ export const Init = () => { return ( <> +
    +
    INITIALIZE
    + >
    +
    +
    CLONE
    + ) } \ No newline at end of file diff --git a/libs/remix-ui/git/src/components/panels/remotes.tsx b/libs/remix-ui/git/src/components/panels/remotes.tsx index cf8ea2fc58..a76721e8d1 100644 --- a/libs/remix-ui/git/src/components/panels/remotes.tsx +++ b/libs/remix-ui/git/src/components/panels/remotes.tsx @@ -1,8 +1,10 @@ -import React, { useEffect } from "react"; -import { gitActionsContext } from "../../state/context"; -import { gitPluginContext } from "../gitui"; -import { Remoteselect } from "./remoteselect"; -import { RemotesImport } from "./remotesimport"; +import React, { useEffect } from "react" +import { gitActionsContext } from "../../state/context" +import { gitPluginContext } from "../gitui" +import { Remoteselect } from "./remoteselect" +import { RemotesImport } from "./remotesimport" +import { sendToMatomo } from "../../lib/pluginActions" +import { gitMatomoEventTypes } from "../../types" export const Remotes = () => { const context = React.useContext(gitPluginContext) @@ -18,6 +20,7 @@ export const Remotes = () => { } const addRemote = async () => { + await sendToMatomo(gitMatomoEventTypes.ADDMANUALREMOTE) actions.addRemote({ name: remoteName, url: url @@ -26,28 +29,31 @@ export const Remotes = () => { return ( <> -
    +
    {context.remotes && context.remotes.length ? - <> +
    {context.remotes && context.remotes.map((remote, index) => { return ( - + ); })} - : <>No remotes} +
    :
    + +
    }
    + + +
    + + onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> + onUrlChange(e.target.value)} value={url} className="form-control mb-2" type="text" id="remoteurl" /> - onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> - onUrlChange(e.target.value)} value={url} className="form-control" type="text" id="remoteurl" /> - - -
    - -
    +
    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/remoteselect.tsx b/libs/remix-ui/git/src/components/panels/remoteselect.tsx index 97e2abd64b..15794bf646 100644 --- a/libs/remix-ui/git/src/components/panels/remoteselect.tsx +++ b/libs/remix-ui/git/src/components/panels/remoteselect.tsx @@ -7,28 +7,42 @@ import { RemotesDetailsNavigation } from "../navigation/remotesdetails"; import { Accordion } from "react-bootstrap"; import { remote } from "@remix-api"; import { RemoteBranchDetails } from "./branches/remotebranchedetails"; +import GitUIButton from "../buttons/gituibutton"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSync } from "@fortawesome/free-solid-svg-icons"; export interface RemoteSelectProps { remote: remote + openDefault: boolean } export const Remoteselect = (props: RemoteSelectProps) => { - const { remote } = props; + const { remote, openDefault } = props; const context = React.useContext(gitPluginContext) const actions = React.useContext(gitActionsContext) const [activePanel, setActivePanel] = useState(""); + useEffect(() => { + setActivePanel(openDefault ? "0" : "") + }, [openDefault]) + return ( <> - + <> - {context.branches && context.branches.filter((branch, index) => branch.remote && branch.remote.name === remote.name ).map((branch, index) => { + {context.branches && context.branches.filter((branch, index) => branch.remote && branch.remote.name === remote.name).map((branch, index) => { return ( - + ); - })} + })} + { + await actions.fetch({ + remote + }) + }}> + diff --git a/libs/remix-ui/git/src/components/panels/remotesimport.tsx b/libs/remix-ui/git/src/components/panels/remotesimport.tsx index a456de8fe4..fc1a423de3 100644 --- a/libs/remix-ui/git/src/components/panels/remotesimport.tsx +++ b/libs/remix-ui/git/src/components/panels/remotesimport.tsx @@ -4,9 +4,9 @@ import { gitActionsContext } from "../../state/context"; import { repository } from "@remix-api"; import { gitPluginContext } from "../gitui"; import Select from 'react-select' -import { selectStyles, selectTheme } from "../../types/styles"; -import { TokenWarning } from "./tokenWarning"; -import RepositorySelect from "../github/repositoryselect"; +import { selectStyles, selectTheme } from "../../types/styles" +import { TokenWarning } from "./tokenWarning" +import RepositorySelect from "../github/repositoryselect" export const RemotesImport = () => { const context = React.useContext(gitPluginContext) @@ -64,11 +64,10 @@ export const RemotesImport = () => { return ( <> + - - {repo ? - onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2" type="text" id="remotename" /> + onRemoteNameChange(e.target.value)} value={remoteName} className="form-control mb-2 mt-2" type="text" id="remotename" /> : null} {repo && remoteName ? @@ -76,6 +75,9 @@ export const RemotesImport = () => { await addRemote() }}>add {remoteName}:{repo.full_name} : null} + {repo && !remoteName ? + : null} + ) } diff --git a/libs/remix-ui/git/src/components/panels/setup.tsx b/libs/remix-ui/git/src/components/panels/setup.tsx index 42a68014ae..71b35f1499 100644 --- a/libs/remix-ui/git/src/components/panels/setup.tsx +++ b/libs/remix-ui/git/src/components/panels/setup.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react' import { GetDeviceCode } from '../github/devicecode' import { GitHubCredentials } from './githubcredentials' +import { Clone } from './clone' export const Setup = () => { @@ -19,6 +20,8 @@ export const Setup = () => {
    +
    CLONE
    + ) } else if (screen === 1) { diff --git a/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolgroup.tsx b/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolgroup.tsx index 1b17cb2b31..c6c77f3328 100644 --- a/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolgroup.tsx +++ b/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolgroup.tsx @@ -35,4 +35,4 @@ export const SourceControGroup = (props: SourceControGroupProps) => { : <>} ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolitem.tsx b/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolitem.tsx index 27f87d1c04..0a96f7b728 100644 --- a/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolitem.tsx +++ b/libs/remix-ui/git/src/components/panels/sourcecontrol/sourcecontrolitem.tsx @@ -42,10 +42,10 @@ export const SourceControlItem = (props: SourceControlItemProps) => { return (<> - {status && status.indexOf("modified") === -1 ? <> :
    M
    } - {status && status.indexOf("deleted") === -1 ? <> : D} - {status && status.indexOf("added") === -1 ? <> : A} - {status && status.indexOf("untracked") === -1 ? <> : U} + {status && status.indexOf("modified") === -1 ? <> : M} + {status && status.indexOf("deleted") === -1 ? <> : D} + {status && status.indexOf("added") === -1 ? <> : A} + {status && status.indexOf("untracked") === -1 ? <> : U} ) } @@ -57,10 +57,10 @@ export const SourceControlItem = (props: SourceControlItemProps) => { {path.basename(file.filename)}
    {file.filename}
    -
    +
    ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/sourcontrol.tsx b/libs/remix-ui/git/src/components/panels/sourcontrol.tsx index ad54852073..beaf2638b7 100644 --- a/libs/remix-ui/git/src/components/panels/sourcontrol.tsx +++ b/libs/remix-ui/git/src/components/panels/sourcontrol.tsx @@ -37,7 +37,7 @@ export const SourceControl = () => { <> {show ? <> -
    +
    : <> @@ -46,4 +46,4 @@ export const SourceControl = () => { ); -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/components/panels/tokenWarning.tsx b/libs/remix-ui/git/src/components/panels/tokenWarning.tsx index 4188c07c93..1c818c716b 100644 --- a/libs/remix-ui/git/src/components/panels/tokenWarning.tsx +++ b/libs/remix-ui/git/src/components/panels/tokenWarning.tsx @@ -1,12 +1,15 @@ import { gitPluginContext } from "../gitui" -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react" + export const TokenWarning = () => { const context = React.useContext(gitPluginContext) return (<> {(context.gitHubUser && context.gitHubUser.login) ? null : -
  • - To use add a GitHub token to the settings.
  • + + Generate and add a Git token or login with GitHub. Tokens are added in { + }}>settings. + } ) -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/lib/gitactions.ts b/libs/remix-ui/git/src/lib/gitactions.ts index d2c19af89b..9962d72754 100644 --- a/libs/remix-ui/git/src/lib/gitactions.ts +++ b/libs/remix-ui/git/src/lib/gitactions.ts @@ -1,14 +1,13 @@ import { ReadBlobResult, ReadCommitResult } from "isomorphic-git"; import React from "react"; -import { fileStatus, fileStatusMerge, setRemoteBranchCommits, resetRemoteBranchCommits, setBranches, setCanCommit, setCommitChanges, setCommits, setCurrentBranch, setGitHubUser, setLoading, setRemoteBranches, setRemotes, setRepos, setUpstream, setLocalBranchCommits, setBranchDifferences, setRemoteAsDefault, setScopes, setLog, clearLog, setUserEmails, setCurrenHead } from "../state/gitpayload"; -import { gitActionDispatch, statusMatrixType, gitState, gitLog, fileStatusResult } from '../types'; +import { fileStatus, fileStatusMerge, setRemoteBranchCommits, resetRemoteBranchCommits, setBranches, setCanCommit, setCommitChanges, setCommits, setCurrentBranch, setGitHubUser, setLoading, setRemoteBranches, setRemotes, setRepos, setUpstream, setLocalBranchCommits, setBranchDifferences, setRemoteAsDefault, setScopes, setLog, clearLog, setUserEmails, setCurrenHead, setStoragePayload, resetBranchDifferences } from "../state/gitpayload"; +import { gitActionDispatch, statusMatrixType, gitState, gitLog, fileStatusResult, storage, gitMatomoEventTypes } from '../types'; import { removeSlash } from "../utils"; import { disableCallBacks, enableCallBacks } from "./listeners"; import { ModalTypes } from "@remix-ui/app"; -import { setFileDecorators } from "./pluginActions"; +import { sendToMatomo, setFileDecorators } from "./pluginActions"; import { Plugin } from "@remixproject/engine"; import { addInputType, branch, branchDifference, checkoutInputType, cloneInputType, commitChange, CustomRemixApi, fetchInputType, GitHubUser, pullInputType, pushInputType, remote, rmInputType, userEmails } from "@remix-api"; -import { file } from "jszip"; export const fileStatuses = [ ["new,untracked", 0, 2, 0], // new, untracked @@ -40,7 +39,7 @@ export const setPlugin = (p: Plugin, dispatcher: React.Dispatch { - + await sendToMatomo(gitMatomoEventTypes.INIT) await plugin.call('dgitApi', "init"); await gitlog(); await getBranches(); @@ -149,6 +148,7 @@ export const currentBranch = async () => { } export const createBranch = async (name: string = "") => { + await sendToMatomo(gitMatomoEventTypes.CREATEBRANCH) dispatch(setLoading(true)) if (name) { await plugin.call('dgitApi', 'branch', { ref: name, force: true, checkout: true }); @@ -179,6 +179,7 @@ const settingsWarning = async () => { export const commit = async (message: string = "") => { + await sendToMatomo(gitMatomoEventTypes.COMMIT) try { const credentials = await settingsWarning() if (!credentials) { @@ -206,6 +207,7 @@ export const commit = async (message: string = "") => { } export const addall = async (files: fileStatusResult[]) => { + await sendToMatomo(gitMatomoEventTypes.ADD_ALL) try { const filesToAdd = files .filter(f => !f.statusNames.includes('deleted')) @@ -231,6 +233,7 @@ export const addall = async (files: fileStatusResult[]) => { } export const add = async (filepath: addInputType) => { + await sendToMatomo(gitMatomoEventTypes.ADD) try { if (typeof filepath.filepath === "string") { filepath.filepath = removeSlash(filepath.filepath) @@ -257,6 +260,7 @@ const getLastCommmit = async () => { } export const rm = async (args: rmInputType) => { + await sendToMatomo(gitMatomoEventTypes.RM) await plugin.call('dgitApi', 'rm', { filepath: removeSlash(args.filepath), }); @@ -293,7 +297,7 @@ export const checkoutfile = async (filename: string) => { } export const checkout = async (cmd: checkoutInputType) => { - + sendToMatomo(gitMatomoEventTypes.CHECKOUT) await disableCallBacks(); await plugin.call('fileManager', 'closeAllFiles') try { @@ -307,6 +311,7 @@ export const checkout = async (cmd: checkoutInputType) => { export const clone = async (input: cloneInputType) => { + await sendToMatomo(gitMatomoEventTypes.CLONE) dispatch(setLoading(true)) const urlParts = input.url.split("/"); const lastPart = urlParts[urlParts.length - 1]; @@ -326,6 +331,8 @@ export const clone = async (input: cloneInputType) => { message: `Cloned ${input.url} to ${repoNameWithTimestamp}` }) + plugin.call('notification', 'toast', `Cloned ${input.url} to ${repoNameWithTimestamp}`) + } catch (e: any) { await parseError(e) } @@ -333,6 +340,7 @@ export const clone = async (input: cloneInputType) => { } export const fetch = async (input: fetchInputType) => { + await sendToMatomo(gitMatomoEventTypes.FETCH) dispatch(setLoading(true)) await disableCallBacks() try { @@ -343,13 +351,14 @@ export const fetch = async (input: fetchInputType) => { } } catch (e: any) { console.log(e) - await parseError(e) + if (!input.quiet) { await parseError(e) } } dispatch(setLoading(false)) await enableCallBacks() } export const pull = async (input: pullInputType) => { + await sendToMatomo(gitMatomoEventTypes.PULL) dispatch(setLoading(true)) await disableCallBacks() try { @@ -364,6 +373,7 @@ export const pull = async (input: pullInputType) => { } export const push = async (input: pushInputType) => { + await sendToMatomo(gitMatomoEventTypes.PUSH) dispatch(setLoading(true)) await disableCallBacks() try { @@ -389,6 +399,7 @@ const parseError = async (e: any) => { console.trace(e) // if message conttains 401 Unauthorized, show token warning if (e.message.includes('401')) { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['401']) const result = await plugin.call('notification', 'modal' as any, { title: 'The GitHub token may be missing or invalid', message: 'Please check the GitHub token and try again. Error: 401 Unauthorized', @@ -399,6 +410,7 @@ const parseError = async (e: any) => { } // if message contains 404 Not Found, show repo not found else if (e.message.includes('404')) { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['404']) await plugin.call('notification', 'modal' as any, { title: 'Repository not found', message: 'Please check the URL and try again.', @@ -409,6 +421,7 @@ const parseError = async (e: any) => { } // if message contains 403 Forbidden else if (e.message.includes('403')) { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['403']) await plugin.call('notification', 'modal' as any, { title: 'The GitHub token may be missing or invalid', message: 'Please check the GitHub token and try again. Error: 403 Forbidden', @@ -417,6 +430,7 @@ const parseError = async (e: any) => { type: ModalTypes.confirm }) } else if (e.toString().includes('NotFoundError') && !e.toString().includes('fetch')) { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['BRANCH NOT FOUND ON REMOTE']) await plugin.call('notification', 'modal', { title: 'Remote branch not found', message: 'The branch you are trying to fetch does not exist on the remote. If you have forked this branch from another branch, you may need to fetch the original branch first or publish this branch on the remote.', @@ -424,6 +438,7 @@ const parseError = async (e: any) => { type: ModalTypes.alert }) } else { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['UKNOWN']) await plugin.call('notification', 'alert' as any, { title: 'Error', message: e.message @@ -451,6 +466,7 @@ export const repositories = async () => { } } else { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['TOKEN ERROR']) plugin.call('notification', 'alert', { id: 'github-token-error', title: 'Error getting repositories', @@ -460,6 +476,7 @@ export const repositories = async () => { } } catch (e) { console.log(e) + await sendToMatomo(gitMatomoEventTypes.ERROR, ['TOKEN ERROR']) plugin.call('notification', 'alert', { id: 'github-token-error', title: 'Error getting repositories', @@ -488,6 +505,7 @@ export const remoteBranches = async (owner: string, repo: string) => { page++ } } else { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['TOKEN ERROR']) plugin.call('notification', 'alert', { title: 'Error getting branches', id: 'github-token-error', @@ -497,6 +515,7 @@ export const remoteBranches = async (owner: string, repo: string) => { } } catch (e) { console.log(e) + await sendToMatomo(gitMatomoEventTypes.ERROR, ['TOKEN ERROR']) plugin.call('notification', 'alert', { title: 'Error', id: 'github-error', @@ -623,8 +642,10 @@ export const loadGitHubUserFromToken = async () => { type: 'success', message: `Github user loaded...` }) + await sendToMatomo(gitMatomoEventTypes.LOADGITHUBUSERSUCCESS) return true } else { + await sendToMatomo(gitMatomoEventTypes.ERROR, ['GITHUB USER LOAD ERROR']) sendToGitLog({ type: 'error', message: `Please check your GitHub token in the GitHub settings.` @@ -669,7 +690,7 @@ export const resolveRef = async (ref: string) => { } export const diff = async (commitChange: commitChange) => { - + await sendToMatomo(gitMatomoEventTypes.DIFF) if (!commitChange.hashModified) { const newcontent = await plugin.call( "fileManager", @@ -793,9 +814,14 @@ export const getBranchDifferences = async (branch: branch, remote: remote, state remote = state.remotes[0] } } - if (!remote) return + try { + if (!remote) { + dispatch(resetBranchDifferences()) + return + } + const branchDifference: branchDifference = await plugin.call('dgitApi', 'compareBranches', { branch, remote @@ -809,6 +835,8 @@ export const getBranchDifferences = async (branch: branch, remote: remote, state })) } catch (e) { // do nothing + if (dispatch) + dispatch(resetBranchDifferences()) } } @@ -833,19 +861,27 @@ export const getBranchCommits = async (branch: branch, page: number) => { } export const setDefaultRemote = async (remote: remote) => { + await sendToMatomo(gitMatomoEventTypes.SETDEFAULTREMOTE) dispatch(setRemoteAsDefault(remote)) } export const addRemote = async (remote: remote) => { + await sendToMatomo(gitMatomoEventTypes.ADDREMOTE) try { await plugin.call('dgitApi', 'addremote', remote) await getRemotes() + await fetch({ + remote: remote, + singleBranch: true, + quiet: true, + }) } catch (e) { console.log(e) } } export const removeRemote = async (remote: remote) => { + await sendToMatomo(gitMatomoEventTypes.RMREMOTE) try { await plugin.call('dgitApi', 'delremote', remote) await getRemotes() @@ -860,4 +896,8 @@ export const sendToGitLog = async (message: gitLog) => { export const clearGitLog = async () => { dispatch(clearLog()) -} \ No newline at end of file +} + +export const setStorage = async (storage: storage) => { + dispatch(setStoragePayload(storage)) +} diff --git a/libs/remix-ui/git/src/lib/listeners.ts b/libs/remix-ui/git/src/lib/listeners.ts index f6384873d7..b4a93a6cc8 100644 --- a/libs/remix-ui/git/src/lib/listeners.ts +++ b/libs/remix-ui/git/src/lib/listeners.ts @@ -1,9 +1,9 @@ import React from "react"; import { setCanUseApp, setLoading, setRepoName, setGItHubToken, setLog, setGitHubUser, setUserEmails, setDesktopWorkingDir, setVersion } from "../state/gitpayload"; -import { gitActionDispatch } from "../types"; +import { gitActionDispatch, gitUIPanels, storage } from "../types"; import { Plugin } from "@remixproject/engine"; -import { getBranches, getFileStatusMatrix, loadGitHubUserFromToken, getRemotes, gitlog, setPlugin } from "./gitactions"; +import { getBranches, getFileStatusMatrix, loadGitHubUserFromToken, getRemotes, gitlog, setPlugin, setStorage } from "./gitactions"; import { Profile } from "@remixproject/plugin-utils"; import { CustomRemixApi } from "@remix-api"; import { statusChanged } from "./pluginActions"; @@ -178,6 +178,12 @@ export const setCallBacks = (viewPlugin: Plugin, gitDispatcher: React.Dispatch { loadFiles() }) + loadFileQueue.enqueue(async () => { + getBranches() + }) + loadFileQueue.enqueue(async () => { + gitlog() + }) }) plugin.on('manager', 'pluginActivated', async (p: Profile) => { if (p.name === 'dgitApi') { @@ -194,11 +200,8 @@ export const setCallBacks = (viewPlugin: Plugin, gitDispatcher: React.Dispatch { - const panels = { - 'branches': '2' - } - const panelNumber = panels[panel] - setAtivePanel(panelNumber) + + setAtivePanel(panel) }) callBackEnabled = true; @@ -217,6 +220,7 @@ export const getGitConfig = async () => { export const loadFiles = async (filepaths: string[] = null) => { try { + await calculateLocalStorage() const branch = await plugin.call('dgitApi', "currentbranch") if (branch) { await getFileStatusMatrix(filepaths); @@ -236,3 +240,43 @@ export const enableCallBacks = async () => { callBackEnabled = true; } +const calculateLocalStorage = async () => { + function bytesToMB(bytes) { + return parseFloat((bytes / (1024 * 1024)).toFixed(2)); + } + + function calculatePercentage(used, quota) { + return parseFloat(((used / quota) * 100).toFixed(2)); + } + + let storage: storage = { + used: 0, + total: 0, + available: 0, + percentUsed: 0, + enabled: false + } + + if ('storage' in navigator && 'estimate' in navigator.storage) { + navigator.storage.estimate().then(estimate => { + const usedMB = bytesToMB(estimate.usage); + const quotaMB = bytesToMB(estimate.quota); + const availableMB = bytesToMB(estimate.quota - estimate.usage); + const percentageUsed = calculatePercentage(estimate.usage, estimate.quota); + storage = { + used: usedMB, + total: quotaMB, + available: availableMB, + percentUsed: percentageUsed, + enabled: true + } + setStorage(storage); + + }); + } else { + console.log('Storage API not supported in this browser.'); + setStorage(storage); + } + +} + diff --git a/libs/remix-ui/git/src/lib/pluginActions.ts b/libs/remix-ui/git/src/lib/pluginActions.ts index a13f1cca93..d1c7a8854a 100644 --- a/libs/remix-ui/git/src/lib/pluginActions.ts +++ b/libs/remix-ui/git/src/lib/pluginActions.ts @@ -1,5 +1,6 @@ import { fileStatusResult, gitActionDispatch, gitState } from "../types" +import { gitMatomoEventTypes } from "../types" import { fileDecoration, fileDecorationType } from "@remix-ui/file-decorators" import { removeSlash } from "../utils" import { getFilesByStatus } from "./fileHelpers" @@ -105,3 +106,8 @@ export const openFolderInSameWindow = async (path: string) => { export const openCloneDialog = async () => { plugin.call('filePanel', 'clone') } +export const sendToMatomo = async (event: gitMatomoEventTypes, args?: string[]) => { + const trackArgs = args ? ['trackEvent', 'git', event, ...args] : ['trackEvent', 'git', event]; + plugin && await plugin.call('matomo', 'track', trackArgs); +} + diff --git a/libs/remix-ui/git/src/state/actions.ts b/libs/remix-ui/git/src/state/actions.ts index 4386c67fe4..c596727901 100644 --- a/libs/remix-ui/git/src/state/actions.ts +++ b/libs/remix-ui/git/src/state/actions.ts @@ -1,6 +1,8 @@ import { ReadCommitResult } from "isomorphic-git" import { fileStatusResult, gitLog } from "../types" import { GitHubUser, branch, branchDifference, commitChange, pagedCommits, remote, remoteBranch, repository, userEmails } from '@remix-api' +import { storage } from "../types" + export interface ActionPayloadTypes { FILE_STATUS: fileStatusResult[], FILE_STATUS_MERGE: fileStatusResult[] @@ -34,6 +36,7 @@ export interface ActionPayloadTypes { remote: remote branchDifference: branchDifference } + RESET_BRANCH_DIFFERENCES: null SET_GITHUB_USER: GitHubUser SET_RATE_LIMIT: any SET_GITHUB_ACCESS_TOKEN: string @@ -44,6 +47,7 @@ export interface ActionPayloadTypes { SET_USER_EMAILS: userEmails DESKTOP_SET_WORKING_DIR: string SET_VERSION: string + SET_STORAGE: storage } export interface Action { diff --git a/libs/remix-ui/git/src/state/gitpayload.ts b/libs/remix-ui/git/src/state/gitpayload.ts index da6aef53b8..9b6a0b19f8 100644 --- a/libs/remix-ui/git/src/state/gitpayload.ts +++ b/libs/remix-ui/git/src/state/gitpayload.ts @@ -1,6 +1,8 @@ import { ReadCommitResult } from "isomorphic-git" import { fileStatusResult, gitLog } from "../types" import { repository, pagedCommits, branch, remote, commitChange, branchDifference, GitHubUser, userEmails } from "@remix-api" +import { storage } from "../types" +import { Endpoints } from "@octokit/types" export const fileStatus = (files: fileStatusResult[]) => { return { @@ -192,6 +194,12 @@ export const setBranchDifferences = ({ } } +export const resetBranchDifferences = () => { + return { + type: 'RESET_BRANCH_DIFFERENCES' + } +} + export const setGItHubToken = (token: string) => { return { type: 'SET_GITHUB_ACCESS_TOKEN', @@ -232,3 +240,9 @@ export const setVersion = (version: string) => { payload: version } } +export const setStoragePayload = (storage: storage) => { + return { + type: 'SET_STORAGE', + payload: storage + } +} diff --git a/libs/remix-ui/git/src/state/gitreducer.tsx b/libs/remix-ui/git/src/state/gitreducer.tsx index 21c3e5f9b4..088ea9db9f 100644 --- a/libs/remix-ui/git/src/state/gitreducer.tsx +++ b/libs/remix-ui/git/src/state/gitreducer.tsx @@ -156,6 +156,12 @@ export const gitReducer = (state: gitState = defaultGitState, action: Actions): branchDifferences: { ...state.branchDifferences } } + case 'RESET_BRANCH_DIFFERENCES': + return { + ...state, + branchDifferences: {} + } + case 'SET_GITHUB_USER': return { ...state, @@ -215,6 +221,11 @@ export const gitReducer = (state: gitState = defaultGitState, action: Actions): ...state, version: action.payload } + case 'SET_STORAGE': + return { + ...state, + storage: action.payload + } } -} \ No newline at end of file +} diff --git a/libs/remix-ui/git/src/style/index.css b/libs/remix-ui/git/src/style/index.css index 973889c15c..57df5f68c9 100644 --- a/libs/remix-ui/git/src/style/index.css +++ b/libs/remix-ui/git/src/style/index.css @@ -28,9 +28,17 @@ .gitfile:hover { background-color : var(--custom-select); } - + hr { background-color: var(--custom-select); } +.messageTip { + +} + +.messageTip:hover { + cursor: pointer; + text-decoration: underline; +} diff --git a/libs/remix-ui/git/src/types/index.ts b/libs/remix-ui/git/src/types/index.ts index 2f407ef77a..6c3d35c32e 100644 --- a/libs/remix-ui/git/src/types/index.ts +++ b/libs/remix-ui/git/src/types/index.ts @@ -14,7 +14,7 @@ export type gitState = { fileStatusResult: fileStatusResult[] canUseApp: boolean loading: boolean - storageUsed: any + storage: storage reponame: string staged: fileStatusResult[] untracked: fileStatusResult[] @@ -71,7 +71,13 @@ export const defaultGitState: gitState = { allchangesnotstaged: [], canUseApp: false, loading: false, - storageUsed: {}, + storage: { + used: 0, + total: 0, + available: 0, + percentUsed: 0, + enabled: false + }, reponame: "", repositories: [], remoteBranches: [], @@ -115,6 +121,61 @@ export type sourceControlGroup = { name: string } +export type storage = { + used: number, + total: number + available: number + percentUsed: number + enabled: boolean +} + +export enum gitMatomoEventTypes { + INIT = 'INIT', + COMMIT = 'COMMIT', + PUSH = 'PUSH', + PULL = 'PULL', + ADDREMOTE = 'ADDREMOTE', + RMREMOTE = 'RMREMOTE', + CLONE = 'CLONE', + FETCH = 'FETCH', + ADD = 'ADD', + ADD_ALL = 'ADD_ALL', + RM = 'RM', + CHECKOUT = 'CHECKOUT', + CHECKOUT_LOCAL_BRANCH = 'CHECKOUT_LOCAL_BRANCH', + CHECKOUT_REMOTE_BRANCH = 'CHECKOUT_REMOTE_BRANCH', + DIFF = 'DIFF', + BRANCH = 'BRANCH', + CREATEBRANCH = 'CREATEBRANCH', + GETGITHUBDEVICECODE = 'GET_GITHUB_DEVICECODE', + CONNECTTOGITHUB = 'CONNECT_TO_GITHUB', + DISCONNECTFROMGITHUB = 'DISCONNECT_FROM_GITHUB', + SAVEMANUALGITHUBCREDENTIALS = 'SAVE_MANUAL_GITHUB_CREDENTIALS', + LOADREPOSITORIESFROMGITHUB = 'LOAD_REPOSITORIES_FROM_GITHUB', + COPYGITHUBDEVICECODE = 'COPY_GITHUB_DEVICE_CODE', + CONNECTTOGITHUBSUCCESS = 'CONNECT_TO_GITHUB_SUCCESS', + CONNECTTOGITHUBFAIL = 'CONNECT_TO_GITHUB_FAIL', + OPENPANEL = 'OPEN_PANEL', + ADDMANUALREMOTE = 'ADD_MANUAL_REMOTE', + SETDEFAULTREMOTE = 'SET_DEFAULT_REMOTE', + SETLOCALBRANCHINCOMMANDS = 'SET_LOCAL_BRANCH_IN_COMMANDS', + SETREMOTEBRANCHINCOMMANDS = 'SET_REMOTE_IN_COMMANDS', + REFRESH = 'REFRESH', + ERROR = 'ERROR', + LOADGITHUBUSERSUCCESS = 'LOAD_GITHUB_USER_SUCCESS', +} + +export enum gitUIPanels { + SOURCECONTROL = '0', + COMMANDS = '1', + BRANCHES = '2', + COMMITS = '3', + CLONE = '4', + REMOTES = '5', + GITHUB = '7', + LOG = '6' +} + export interface fileStatusAction { type: string, payload: fileStatusResult[] diff --git a/libs/remix-ui/git/src/utils/index.ts b/libs/remix-ui/git/src/utils/index.ts index 8aab076424..59694cb401 100644 --- a/libs/remix-ui/git/src/utils/index.ts +++ b/libs/remix-ui/git/src/utils/index.ts @@ -1,3 +1,7 @@ export const removeSlash = (s: string) => { return s.replace(/^\/+/, ""); }; + +export const removeGitFromUrl = (url: string) => { + return url.replace(/\.git$/, ""); +} \ No newline at end of file diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css index 46e31866a8..760a85e1d6 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.css @@ -23,12 +23,13 @@ .remixui_grid_cell_pin:focus { outline: none; } + .remixui_grid_cell_pin { - width: 1rem; + width: 1rem; height: 1rem; position: relative; - right: 1rem; - top: -0.5rem; + right: 0.9rem; + top: -0.7rem; background: transparent; } @@ -40,8 +41,8 @@ .remixui_grid_cell_tags_no_pin { position: relative; - right: 0rem; - top: 0.1rem; + right: 0.45rem; + top: 1px } .remixui_grid_cell_tag { diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx index e3d979e0cb..3055d0d4e7 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-cell.tsx @@ -16,13 +16,17 @@ interface RemixUIGridCellProps { pinned?: boolean pinStateCallback?: any logo?: string + logos?: string[] title: string + hideTitle?: boolean tagList?: string[] // max 8, others will be ignored classList?: string styleList?: any children?: ReactNode expandViewEl?: any handleExpand?: any + id: string + searchKeywords?: string[] } export const RemixUIGridCell = (props: RemixUIGridCellProps) => { @@ -32,11 +36,20 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => { const [pinned, setPinned] = useState(props.pinned) useEffect(() => { - if (props.tagList) setAnyEnabled(props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled)) - else setAnyEnabled(filterCon?.keyValueMap['no tag']?.enabled) - if (filterCon.filter != '') setAnyEnabled(anyEnabled && props.title.toLowerCase().includes(filterCon.filter)) - console.log("pin ", pinned) + let enabled = false + // check for tags + if (props.tagList && props.tagList.length != 0) { + enabled = props.tagList.some((key) => filterCon.keyValueMap[key]?.enabled) + } else if (filterCon?.keyValueMap['no tag']?.enabled || !Object.keys(filterCon?.keyValueMap).length) { + enabled = true + } + // check for filter + if (filterCon.filter != '') + enabled = (props.title?.toLowerCase().includes(filterCon.filter?.toLowerCase()) || + props.searchKeywords?.map(keyword => keyword.toLowerCase()).some(searchKeyword => searchKeyword.toLowerCase().includes(filterCon.filter.toLocaleLowerCase()))) + + setAnyEnabled(enabled) }, [filterCon, props.tagList]) /*const listenOnExpand = (key) => { @@ -54,37 +67,46 @@ export const RemixUIGridCell = (props: RemixUIGridCellProps) => { */ return ( -
    { +
    { if (props.expandViewEl) props.handleExpand(!expand) else return }}> - { anyEnabled &&
    + { anyEnabled &&
    -
    -
    -
    +
    +
    + { !props.hideTitle &&
    { props.logo && } - { props.title && } -
    + { props.logos && props.logos.map((logo) => )} + { props.title && + + + + } +
    } { props.children }
    { filterCon.showPin && } { props.tagList &&
    { Object.keys(props.tagList).map((key) => ( - filterCon.keyValueMap[props.tagList[key]].enabled && ( + filterCon.keyValueMap[props.tagList[key]]?.enabled && ( { )) }
    } { !props.tagList && + className={'px-1 remixui_grid_cell_tags'}> }
    { expand &&
    diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx index ca391c7685..a3cb8b15c0 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-section.tsx @@ -1,6 +1,7 @@ import React, {useState, useEffect, useContext, useRef, ReactNode} from 'react' // eslint-disable-line import './remix-ui-grid-section.css' +import FiltersContext from "./filtersContext" declare global { interface Window { @@ -19,7 +20,41 @@ interface RemixUIGridSectionProps { expandedCell?: any } +const hasChildCell = (children: ReactNode): boolean => { + return true + let found = false + + const isElement = (child: ReactNode): child is React.ReactElement => { + return React.isValidElement(child) + } + + const traverse = (child: ReactNode) => { + if (found) return + + if (isElement(child)) { + if (child.props.classList === 'remixui_grid_cell_container') { + found = true + return + } + + if (child.props.children) { + React.Children.forEach(child.props.children, traverse) + } + } + } + + React.Children.forEach(children, traverse) + return found +} + export const RemixUIGridSection = (props: RemixUIGridSectionProps) => { + const [children, setChildren] = useState(props.children) + const filterCon = useContext(FiltersContext) + + useEffect(() => { + setChildren(props.children) + }, [props.children]) + return (
    {
    { props.title &&
    { props.title }
    }
    + { !hasChildCell(children) && No items found } { props.children }
    { props.expandedCell &&
    diff --git a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx index 6c6cc27bbd..b8c8373869 100644 --- a/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx +++ b/libs/remix-ui/grid-view/src/lib/remix-ui-grid-view.tsx @@ -51,7 +51,6 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => { searchInputRef.current.value = '' } else { setState((prevState) => { - console.log("update filter", searchInputRef.current.value) return { ...prevState, searchDisable: searchInputRef.current.value === '', @@ -120,7 +119,7 @@ export const RemixUIGridView = (props: RemixUIGridViewProps) => { ref={searchInputRef} type="text" style={{ minWidth: '100px' }} - className="border form-control border-right-0 mr-4" + className="border form-control mr-4" id="GVFilterInput" placeholder={"Filter the list"} data-id="RemixGVFilterInput" diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx index bcbc5e17b1..24df154cf1 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx @@ -25,7 +25,7 @@ type WorkspaceTemplate = { workspaceTitle: string description: string projectLogo: string - templateName: string + templateName?: string } const workspaceTemplates: WorkspaceTemplate[] = [ @@ -70,7 +70,7 @@ const workspaceTemplates: WorkspaceTemplate[] = [ description: 'Create a new MultiSig wallet using this template.', projectLogo: 'assets/img/gnosissafeLogo.png', templateName: 'gnosisSafeMultisig', - }, + } ] function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) { @@ -129,7 +129,8 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) { { url: metadata.url, branch: metadata.branch, - workspaceName: templateDisplayName + workspaceName: templateDisplayName, + depth: 10 }) } else if (metadata && metadata.type === 'plugin') { await plugin.appManager.activatePlugin('filePanel') @@ -162,9 +163,14 @@ function HomeTabGetStarted({ plugin }: HomeTabGetStartedProps) {