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

pull/5370/head
bunsenstraat 5 months ago
commit 469682896a
  1. 2
      .circleci/config.yml
  2. 2
      .github/workflows/pr-reminder.yml
  3. 2
      .gitignore
  4. 4
      README.md
  5. 15
      apps/remix-ide-e2e/src/commands/addFile.ts
  6. 13
      apps/remix-ide-e2e/src/githttpbackend/package.json
  7. 48
      apps/remix-ide-e2e/src/githttpbackend/server.ts
  8. 7
      apps/remix-ide-e2e/src/githttpbackend/setup.sh
  9. 511
      apps/remix-ide-e2e/src/githttpbackend/yarn.lock
  10. 8
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  11. 312
      apps/remix-ide-e2e/src/tests/dgit_github.test.ts
  12. 521
      apps/remix-ide-e2e/src/tests/dgit_local.test.ts
  13. 4
      apps/remix-ide-e2e/src/tests/editor.test.ts
  14. 10
      apps/remix-ide-e2e/src/tests/etherscan_api.test.ts
  15. 2
      apps/remix-ide-e2e/src/tests/gist.test.ts
  16. 16
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  17. 260
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  18. 17
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  19. 169
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  20. 88
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  21. 120
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  22. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  23. 19
      apps/remix-ide/src/app.js
  24. 5
      apps/remix-ide/src/app/components/status-bar.tsx
  25. 35
      apps/remix-ide/src/app/editor/editor.js
  26. 634
      apps/remix-ide/src/app/files/dgitProvider.ts
  27. 41
      apps/remix-ide/src/app/files/fileManager.ts
  28. 11
      apps/remix-ide/src/app/panels/layout.ts
  29. 17
      apps/remix-ide/src/app/panels/tab-proxy.js
  30. 40
      apps/remix-ide/src/app/plugins/git.tsx
  31. 26
      apps/remix-ide/src/app/plugins/matomo.ts
  32. 5
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  33. 20
      apps/remix-ide/src/app/tabs/locales/en/git.json
  34. 2
      apps/remix-ide/src/app/tabs/locales/en/home.json
  35. 2
      apps/remix-ide/src/app/tabs/locales/es/home.json
  36. 2
      apps/remix-ide/src/app/tabs/locales/fr/home.json
  37. 2
      apps/remix-ide/src/app/tabs/locales/it/home.json
  38. 2
      apps/remix-ide/src/app/tabs/locales/ru/home.json
  39. 2
      apps/remix-ide/src/assets/css/themes/remix-black_undtds.css
  40. 2
      apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css
  41. 7
      apps/remix-ide/src/remixAppManager.js
  42. 2
      apps/remix-ide/src/remixEngine.js
  43. 14
      apps/remixdesktop/src/plugins/isoGitPlugin.ts
  44. 9
      apps/remixdesktop/src/types/index.ts
  45. 24
      apps/vyper/src/app/utils/remix-client.tsx
  46. 4
      apps/vyper/src/main.tsx
  47. 8
      libs/ghaction-helper/package.json
  48. 8
      libs/remix-analyzer/package.json
  49. 1
      libs/remix-api/src/index.ts
  50. 11
      libs/remix-api/src/lib/plugins/config-api.ts
  51. 12
      libs/remix-api/src/lib/plugins/filePanel-api.ts
  52. 10
      libs/remix-api/src/lib/plugins/fileSystem-api.ts
  53. 11
      libs/remix-api/src/lib/plugins/filedecorator-api.ts
  54. 31
      libs/remix-api/src/lib/plugins/notification-api.ts
  55. 13
      libs/remix-api/src/lib/plugins/settings-api.ts
  56. 22
      libs/remix-api/src/lib/remix-api.ts
  57. 6
      libs/remix-astwalker/package.json
  58. 12
      libs/remix-debug/package.json
  59. 4
      libs/remix-lib/package.json
  60. 7
      libs/remix-simulator/package.json
  61. 6
      libs/remix-solidity/package.json
  62. 10
      libs/remix-tests/package.json
  63. 78
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  64. 82
      libs/remix-ui/editor/src/lib/actions/editor.ts
  65. 18
      libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts
  66. 68
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  67. 70
      libs/remix-ui/git/src/components/branchHeader.tsx
  68. 147
      libs/remix-ui/git/src/components/buttons/commitmessage.tsx
  69. 24
      libs/remix-ui/git/src/components/buttons/gituibutton.tsx
  70. 90
      libs/remix-ui/git/src/components/buttons/sourceControlBase.tsx
  71. 98
      libs/remix-ui/git/src/components/buttons/sourcecontrolbuttons.tsx
  72. 12
      libs/remix-ui/git/src/components/disabled.tsx
  73. 51
      libs/remix-ui/git/src/components/github/branchselect.tsx
  74. 127
      libs/remix-ui/git/src/components/github/devicecode.tsx
  75. 87
      libs/remix-ui/git/src/components/github/repositoryselect.tsx
  76. 59
      libs/remix-ui/git/src/components/github/selectandclonerepositories.tsx
  77. 245
      libs/remix-ui/git/src/components/gitui.tsx
  78. 97
      libs/remix-ui/git/src/components/navigation/branchedetails.tsx
  79. 32
      libs/remix-ui/git/src/components/navigation/branches.tsx
  80. 29
      libs/remix-ui/git/src/components/navigation/clone.tsx
  81. 35
      libs/remix-ui/git/src/components/navigation/commands.tsx
  82. 37
      libs/remix-ui/git/src/components/navigation/commitdetails.tsx
  83. 63
      libs/remix-ui/git/src/components/navigation/commits.tsx
  84. 29
      libs/remix-ui/git/src/components/navigation/github.tsx
  85. 20
      libs/remix-ui/git/src/components/navigation/loaderindicator.tsx
  86. 87
      libs/remix-ui/git/src/components/navigation/log.tsx
  87. 49
      libs/remix-ui/git/src/components/navigation/menu/sourcecontrolmenu.tsx
  88. 32
      libs/remix-ui/git/src/components/navigation/remotes.tsx
  89. 68
      libs/remix-ui/git/src/components/navigation/remotesdetails.tsx
  90. 41
      libs/remix-ui/git/src/components/navigation/settings.tsx
  91. 42
      libs/remix-ui/git/src/components/navigation/sourcecontrol.tsx
  92. 52
      libs/remix-ui/git/src/components/navigation/sourcecontrolgroup.tsx
  93. 60
      libs/remix-ui/git/src/components/panels/branches.tsx
  94. 48
      libs/remix-ui/git/src/components/panels/branches/branchdifferencedetails.tsx
  95. 39
      libs/remix-ui/git/src/components/panels/branches/branchdifferences.tsx
  96. 86
      libs/remix-ui/git/src/components/panels/branches/localbranchdetails.tsx
  97. 111
      libs/remix-ui/git/src/components/panels/branches/remotebranchedetails.tsx
  98. 101
      libs/remix-ui/git/src/components/panels/clone.tsx
  99. 14
      libs/remix-ui/git/src/components/panels/commands.tsx
  100. 25
      libs/remix-ui/git/src/components/panels/commands/fetch.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -746,7 +746,7 @@ workflows:
- build-desktop: - build-desktop:
filters: filters:
branches: branches:
only: ['master', /.*desktop.*/] only: [/.*desktop.*/]
- build-remixdesktop-mac: - build-remixdesktop-mac:
requires: requires:
- build-desktop - build-desktop

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

2
.gitignore vendored

@ -66,6 +66,6 @@ apps/remixdesktop/build*/
apps/remix-ide/src/assets/list.json apps/remix-ide/src/assets/list.json
apps/remix-ide/src/assets/esbuild.wasm apps/remix-ide/src/assets/esbuild.wasm
apps/remixdesktop/build* apps/remixdesktop/build*
apps/remixdesktop/reports apps/remixdesktop/reports/
apps/remixdesktop/logs/ apps/remixdesktop/logs/
logs logs

@ -13,7 +13,7 @@
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix) [![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project) ![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/mh9hFCKkEq) [![Discord](https://img.shields.io/badge/join-discord-brightgreen.svg?style=flat&logo=discord)](https://discord.gg/mh9hFCKkEq)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix) [![X Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=x&color=green)](https://x.com/ethereumremix)
</div> </div>
@ -266,5 +266,5 @@ parameters:
- Official documentation: https://remix-ide.readthedocs.io/en/latest/ - Official documentation: https://remix-ide.readthedocs.io/en/latest/
- Curated list of Remix resources: https://github.com/ethereum/awesome-remix - Curated list of Remix resources: https://github.com/ethereum/awesome-remix
- Medium: https://medium.com/remix-ide - Medium: https://medium.com/remix-ide
- Twitter: https://twitter.com/ethereumremix - X: https://x.com/ethereumremix
- Join Discord: https://discord.gg/mh9hFCKkEq - Join Discord: https://discord.gg/mh9hFCKkEq

@ -2,9 +2,11 @@ import { NightwatchBrowser, NightwatchContractContent } from 'nightwatch'
import EventEmitter from 'events' import EventEmitter from 'events'
class AddFile extends EventEmitter { class AddFile extends EventEmitter {
command(this: NightwatchBrowser, name: string, content: NightwatchContractContent): NightwatchBrowser { command(this: NightwatchBrowser, name: string, content: NightwatchContractContent, readMeFile?:string): NightwatchBrowser {
if(!readMeFile)
readMeFile = 'README.txt'
this.api.perform((done) => { this.api.perform((done) => {
addFile(this.api, name, content, () => { addFile(this.api, name, content, readMeFile, () => {
done() done()
this.emit('complete') this.emit('complete')
}) })
@ -13,7 +15,8 @@ class AddFile extends EventEmitter {
} }
} }
function addFile(browser: NightwatchBrowser, name: string, content: NightwatchContractContent, done: VoidFunction) { function addFile(browser: NightwatchBrowser, name: string, content: NightwatchContractContent, readMeFile:string, done: VoidFunction) {
const readmeSelector = `li[data-id="treeViewLitreeViewItem${readMeFile}"]`
browser browser
.isVisible({ .isVisible({
selector: "//*[@data-id='sidePanelSwapitTitle' and contains(.,'File explorer')]", selector: "//*[@data-id='sidePanelSwapitTitle' and contains(.,'File explorer')]",
@ -25,9 +28,9 @@ function addFile(browser: NightwatchBrowser, name: string, content: NightwatchCo
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
} }
}) })
.scrollInto('li[data-id="treeViewLitreeViewItemREADME.txt"]') .scrollInto(readmeSelector)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]') .waitForElementVisible(readmeSelector)
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]').pause(1000) // focus on root directory .click(readmeSelector).pause(1000) // focus on root directory
.isVisible({ .isVisible({
selector: `//*[@data-id="treeViewLitreeViewItem${name}"]`, selector: `//*[@data-id="treeViewLitreeViewItem${name}"]`,
locateStrategy: 'xpath', locateStrategy: 'xpath',

@ -0,0 +1,13 @@
{
"scripts": {
"start:server": "npx ts-node server.ts"
},
"dependencies": {
"body-parser": "^1.20.2",
"child_process": "^1.0.2",
"express": "^4.19.2",
"git-http-backend": "^1.1.2",
"path": "^0.12.7",
"zlib": "^1.0.5"
}
}

@ -0,0 +1,48 @@
import * as http from 'http';
import { spawn } from 'child_process';
import * as path from 'path';
let backend = require('git-http-backend');
import * as zlib from 'zlib';
const directory = process.argv[2];
if (!directory) {
console.error('Please provide a directory as a command line argument.');
process.exit(1);
}
const server = http.createServer((req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
// Handle preflight request
res.writeHead(204);
res.end();
return;
}
const repo = req.url?.split('/')[1];
const dir = path.join(directory, 'git', repo || '');
console.log(dir);
const reqStream = req.headers['content-encoding'] === 'gzip' ? req.pipe(zlib.createGunzip()) : req;
reqStream.pipe(backend(req.url || '', (err, service) => {
if (err) return res.end(err + '\n');
res.setHeader('content-type', service.type);
console.log(service.action, repo, service.fields);
const ps = spawn(service.cmd, [...service.args, dir]);
ps.stdout.pipe(service.createStream()).pipe(ps.stdin);
})).pipe(res);
});
server.listen(6868, () => {
console.log('Server is listening on port 6868');
});

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

@ -0,0 +1,511 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
body-parser@1.20.2, body-parser@^1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
child_process@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a"
integrity sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.2.1"
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
gopd "^1.0.1"
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
destroy@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
es-define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@^4.19.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
finalhandler@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
on-finished "2.4.1"
parseurl "~1.3.3"
statuses "2.0.1"
unpipe "~1.0.0"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
git-http-backend@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/git-http-backend/-/git-http-backend-1.1.2.tgz#fb6c7b261251df8c2bdf9dc30876a96ce323f545"
integrity sha512-Gx7n/kyCEXGFZlCGmbsEsyeyabLs8XWeb+E/6842up7p3PktQS2/8rlNfB6hCagnW0pJ13Tn8E3yhOkKS6ihdg==
dependencies:
git-side-band-message "~0.0.3"
inherits "~2.0.1"
git-side-band-message@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/git-side-band-message/-/git-side-band-message-0.0.3.tgz#b8a5348c2dcbf1949fd295c506014e26c3f26a46"
integrity sha512-4Rq4xm1+zqCkmuHxRbGdA5ActF7F4UfgK8uI0B7ZfSkByZfikRuF7mqHlvqmycvqos7jpXNkgsZK7DThLLHG3w==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
dependencies:
es-define-property "^1.0.0"
has-proto@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
hasown@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
dependencies:
depd "2.0.0"
inherits "2.0.4"
setprototypeof "1.2.0"
statuses "2.0.1"
toidentifier "1.0.1"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
inherits@2.0.4, inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
object-inspect@^1.13.1:
version "1.13.1"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
on-finished@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path@^0.12.7:
version "0.12.7"
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
dependencies:
process "^0.11.1"
util "^0.10.3"
process@^0.11.1:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
dependencies:
forwarded "0.2.0"
ipaddr.js "1.9.1"
qs@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
iconv-lite "0.4.24"
unpipe "1.0.0"
safe-buffer@5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.18.0"
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
define-data-property "^1.1.4"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
side-channel@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
dependencies:
call-bind "^1.0.7"
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
dependencies:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
util@^0.10.3:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
zlib@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"
integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==

@ -3,7 +3,6 @@ import {RemixPlugin} from './Client'
import {Logger} from './logger' import {Logger} from './logger'
import {filePanelProfile} from '@remixproject/plugin-api' import {filePanelProfile} from '@remixproject/plugin-api'
import {filSystemProfile} from '@remixproject/plugin-api' import {filSystemProfile} from '@remixproject/plugin-api'
import {dGitProfile} from '@remixproject/plugin-api'
import {editorProfile} from '@remixproject/plugin-api' import {editorProfile} from '@remixproject/plugin-api'
import {settingsProfile} from '@remixproject/plugin-api' import {settingsProfile} from '@remixproject/plugin-api'
import {networkProfile} from '@remixproject/plugin-api' import {networkProfile} from '@remixproject/plugin-api'
@ -12,8 +11,13 @@ import {compilerProfile} from '@remixproject/plugin-api'
import {contentImportProfile} from '@remixproject/plugin-api' import {contentImportProfile} from '@remixproject/plugin-api'
import {windowProfile} from '@remixproject/plugin-api' import {windowProfile} from '@remixproject/plugin-api'
import {pluginManagerProfile} from '@remixproject/plugin-api' import {pluginManagerProfile} from '@remixproject/plugin-api'
import {Profile} from '@remixproject/plugin-utils' import {LibraryProfile, Profile} from '@remixproject/plugin-utils'
export const dGitProfile: LibraryProfile<any> = {
name: 'dgitApi',
methods: ['status', 'log', 'commit', 'add', 'branches'],
}
import './app.css' import './app.css'
const client = new RemixPlugin() const client = new RemixPlugin()

@ -0,0 +1,312 @@
import { ChildProcess, spawn } from "child_process"
import init from "../helpers/init"
import { Nightwatch, NightwatchBrowser } from "nightwatch"
module.exports = {
'@disabled': true,
before: function (browser, done) {
init(browser, done)
},
after: function (browser: NightwatchBrowser) {
browser.perform((done) => {
done()
})
},
'Update settings for git #group1 #group2': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
.setValue('*[data-id="githubToken"]', process.env.dgit_token)
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
},
'check if the settings are loaded #group1 #group2': function (browser: NightwatchBrowser) {
browser.
click('*[data-id="github-panel"]')
.waitForElementVisible('*[data-id="connected-as-bunsenstraat"]')
.waitForElementVisible('*[data-id="connected-img-bunsenstraat"]')
.waitForElementVisible('*[data-id="connected-link-bunsenstraat"]')
},
'clone a repository #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="clone-panel"]')
.click({
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="fetch-repositories"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "master")]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]',
locateStrategy: 'xpath'
})
},
'check if there is a README.md file #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]')
},
'check the commands panel #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]",
locateStrategy: 'xpath'
})
},
'check the remotes #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-current-branch-master"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-sync-origin"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
},
'check the commits of branch links #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="commit-summary-linking fixed-"]',
locateStrategy: 'xpath'
})
},
'switch to branch links #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-branch-links"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@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"]',
locateStrategy: 'xpath'
})
},
'check the local commits #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commits-panel"]')
.pause(1000)
.waitForElementVisible({
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-change-modified-README.md"]',
locateStrategy: 'xpath'
})
},
'check the commands panel for links #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]",
locateStrategy: 'xpath'
})
},
'add a remote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="fetch-repositories"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]',
locateStrategy: 'xpath'
})
.setValue({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]',
locateStrategy: 'xpath'
}, 'newremote')
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
locateStrategy: 'xpath'
})
},
'check the commands panel for newremote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]",
locateStrategy: 'xpath'
})
.getAttribute({
selector: '//*[@data-id="sourcecontrol-pull"]',
locateStrategy: 'xpath'
}, 'disabled', (result) => {
if (result.value) {
browser.assert.fail('Button is disabled')
} else {
browser.assert.ok(true)
}
})
},
'remove the remove #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]',
locateStrategy: 'xpath'
})
.pause(2000)
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]',
locateStrategy: 'xpath'
})
.pause(1000)
.waitForElementNotPresent({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
locateStrategy: 'xpath'
})
},
'check the commands panel for removed remote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]",
locateStrategy: 'xpath'
})
.waitForElementNotPresent({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]",
locateStrategy: 'xpath'
})
.getAttribute({
selector: '//*[@data-id="sourcecontrol-pull"]',
locateStrategy: 'xpath'
}, 'disabled', (result) => {
if (result.value) {
browser.assert.ok(true)
} else {
browser.assert.fail('Button is not disabled')
}
})
},
}

@ -0,0 +1,521 @@
import { ChildProcess, spawn } from "child_process"
import kill from 'tree-kill'
import init from "../helpers/init"
import { Nightwatch, NightwatchBrowser } from "nightwatch"
let gitserver: ChildProcess
/*
/ uses the git-http-backend package to create a git server ( if needed kill the server: kill -9 $(sudo lsof -t -i:6868) )
/ GROUP 1: file operations PUSH PULL COMMIT SYNC FETCH CLONE ADD
/ GROUP 2: branch operations CREATE & PUBLISH
/ GROUP 3: file operations rename delete
*/
module.exports = {
'@disabled': true,
before: function (browser, done) {
init(browser, done)
},
after: function (browser: NightwatchBrowser) {
browser.perform((done) => {
console.log('kill server', gitserver.pid)
kill(gitserver.pid)
done()
})
},
'run server #group1 #group2 #group3': 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) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
.modalFooterOKClick('github-credentials-error')
.pause(2000)
},
'clone a repo #group1 #group2 #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="clone-panel"]')
.click('*[data-id="clone-panel"]')
.waitForElementVisible('*[data-id="clone-url"]')
.setValue('*[data-id="clone-url"]', 'http://localhost:6868/bare.git')
.waitForElementVisible('*[data-id="clone-btn"]')
.click('*[data-id="clone-btn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]')
},
// GROUP 1
'check file added #group1 #group3': function (browser: NightwatchBrowser) {
browser.
addFile('test.txt', { content: 'hello world' }, 'README.md')
.clickLaunchIcon('dgit')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]')
.pause(1000)
.click('*[data-id="addToGitChangestest.txt"]')
.waitForElementVisible({
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.setValue('*[data-id="commitMessage"]', 'testcommit')
.click('*[data-id="commitButton"]')
},
'look at the commit #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-ahead"]',
locateStrategy: 'xpath'
})
},
'sync the commit #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible('*[data-id="syncButton"]')
.click('*[data-id="syncButton"]')
.pause(2000)
.waitForElementVisible('*[data-id="commitButton"]')
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-"]',
locateStrategy: 'xpath'
})
},
'check the log #group1': async function (browser: NightwatchBrowser) {
const logs = await getGitLog('/tmp/git/bare.git')
console.log(logs)
browser.assert.ok(logs.includes('testcommit'))
},
'change a file #group1': function (browser: NightwatchBrowser) {
browser.
openFile('test.txt').
pause(1000).
setEditorValue('changes', null)
},
'stage changed file #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({
selector: "//*[@data-status='modified-unstaged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]')
.click('*[data-id="addToGitChangestest.txt"]')
.waitForElementVisible({
selector: "//*[@data-status='modified-staged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.setValue('*[data-id="commitMessage"]', 'testcommit2')
.click('*[data-id="commitButton"]')
},
'push the commit #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible('*[data-id="sourcecontrol-push"]')
.click('*[data-id="sourcecontrol-push"]')
.pause(2000)
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit2-"]',
locateStrategy: 'xpath'
}).pause(2000)
},
'check the log for testcommit2 #group1': async function (browser: NightwatchBrowser) {
const logs = await getGitLog('/tmp/git/bare.git')
console.log(logs)
browser.assert.ok(logs.includes('testcommit2'))
},
'clone locally and add a file and push #group1': async function (browser: NightwatchBrowser) {
await cloneOnServer('http://localhost:6868/bare.git', '/tmp/')
await onLocalGitRepoAddFile('/tmp/bare/', 'test2.txt')
await createCommitOnLocalServer('/tmp/bare/', 'testlocal')
await onLocalGitRepoPush('/tmp/bare/', 'master')
},
'run a git fetch #group1': function (browser: NightwatchBrowser) {
browser
.pause(2000)
.click('*[data-id="commands-panel"]')
.waitForElementVisible('*[data-id="sourcecontrol-fetch-branch"]')
.click('*[data-id="sourcecontrol-fetch-branch"]')
.pause(2000)
.click('*[data-id="commits-panel"]')
.click('*[data-id="commits-panel-behind"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testlocal-"]',
locateStrategy: 'xpath'
})
},
'run pull from the header #group1': function (browser: NightwatchBrowser) {
browser.
click('*[data-id="sourcecontrol-button-pull"]')
.waitForElementNotPresent('*[data-id="commits-panel-behind"]')
},
'check if the file is added #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest2.txt"]')
},
// group 3
'rename a file #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]')
.click('*[data-id="treeViewLitreeViewItemtest.txt"]')
.renamePath('test.txt', 'test_rename', 'test_rename.txt')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_rename.txt"]')
.pause(1000)
},
'stage renamed file #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.waitForElementVisible({
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]')
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test_rename.txt']",
locateStrategy: 'xpath'
})
.click('*[data-id="sourcecontrol-add-all"]')
.pause(2000)
.waitForElementVisible({
selector: "//*[@data-status='deleted-staged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: "//*[@data-status='added-staged' and @data-file='/test_rename.txt']",
locateStrategy: 'xpath'
})
},
'undo the rename #group3': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="unDoStagedtest.txt"]')
.pause(1000)
.waitForElementNotPresent({
selector: "//*[@data-file='/test.txt']",
locateStrategy: 'xpath'
})
},
'check if file is returned #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]')
},
// GROUP 2
'create a branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.click('*[data-id="branches-panel"]')
.waitForElementVisible('*[data-id="newbranchname"]')
.setValue('*[data-id="newbranchname"]', 'testbranch')
.click('*[data-id="sourcecontrol-create-branch"]')
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]')
.pause(1000)
},
'check if the branch is in the filePanel #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('[data-id="workspaceGitBranchesDropdown"]')
.expect.element('[data-id="workspaceGit-testbranch"]').text.to.contain('✓ ')
},
'publish the branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.pause(1000)
.click('*[data-id="publishBranchButton"]')
.pause(2000)
.waitForElementNotVisible('*[data-id="publishBranchButton"]')
},
'check if the branch is published #group2': async function (browser: NightwatchBrowser) {
const branches = await getBranches('/tmp/git/bare.git')
browser.assert.ok(branches.includes('testbranch'))
},
'add file to new branch #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.addFile('test.txt', { content: 'hello world' }, 'README.md')
.clickLaunchIcon('dgit')
.pause(2000)
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]')
.pause(1000)
.click('*[data-id="addToGitChangestest.txt"]')
.waitForElementVisible({
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
})
.setValue('*[data-id="commitMessage"]', 'testcommit')
.click('*[data-id="commitButton"]')
.pause(1000)
},
'check if the commit is ahead in the branches list #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="branches-panel"]')
.click('*[data-id="branches-panel"]')
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]')
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-current-branch-testbranch']",
locateStrategy: 'xpath',
suppressNotFoundErrors: true
})
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='commits-panel-ahead']",
locateStrategy: 'xpath',
suppressNotFoundErrors: true
})
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-summary-testcommit-ahead']",
locateStrategy: 'xpath',
})
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-change-added-test.txt']",
locateStrategy: 'xpath',
})
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-summary-testcommit-ahead']",
locateStrategy: 'xpath',
})
.waitForElementVisible({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-change-added-test.txt']",
locateStrategy: 'xpath',
})
},
'switch back to master #group2': function (browser: NightwatchBrowser) {
browser
.click({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']",
locateStrategy: 'xpath',
})
.waitForElementVisible({
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-current-branch-master']",
locateStrategy: 'xpath',
})
},
'check if test file is gone #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]')
}
}
async function getBranches(path: string): Promise<string> {
return new Promise((resolve, reject) => {
const git = spawn('git', ['branch'], { cwd: path })
let branches = ''
git.stdout.on('data', function (data) {
console.log('stdout git branches', data.toString())
branches += data.toString()
})
git.stderr.on('data', function (data) {
console.log('stderr git branches', data.toString())
reject(data.toString())
})
git.on('close', function () {
resolve(branches)
})
})
}
async function getGitLog(path: string): Promise<string> {
return new Promise((resolve, reject) => {
const git = spawn('git', ['log'], { cwd: path })
let logs = ''
git.stdout.on('data', function (data) {
logs += data.toString()
})
git.stderr.on('err', function (data) {
reject(data.toString())
})
git.on('close', function () {
resolve(logs)
})
})
}
async function cloneOnServer(repo: string, path: string) {
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 });
git.stdout.on('data', function (data) {
console.log('stdout data cloning', data.toString());
if (data.toString().includes('done')) {
resolve(git);
}
});
git.stderr.on('data', function (data) {
console.log('stderr data cloning', data.toString());
if (data.toString().includes('into')) {
setTimeout(() => {
resolve(git);
}, 5000)
}
});
git.on('error', (error) => {
reject(`Process error: ${error.message}`);
});
git.on('exit', (code, signal) => {
if (code !== 0) {
reject(`Process exited with code: ${code} and signal: ${signal}`);
}
});
});
}
async function onLocalGitRepoAddFile(path: string, file: string) {
console.log('adding file', file)
return new Promise((resolve, reject) => {
const git = spawn('touch', [file], { cwd: path });
git.stdout.on('data', function (data) {
console.log('stdout data adding file', data.toString());
if (data.toString().includes('done')) {
resolve(git);
}
});
git.stderr.on('data', function (data) {
console.error('stderr adding file', data.toString());
reject(data.toString());
});
git.on('error', (error) => {
reject(`Process error: ${error.message}`);
});
git.on('exit', (code, signal) => {
if (code !== 0) {
reject(`Process exited with code: ${code} and signal: ${signal}`);
} else {
resolve(git);
}
});
});
}
async function onLocalGitRepoPush(path: string, branch: string = 'master') {
console.log('pushing', path)
return new Promise((resolve, reject) => {
const git = spawn('git', ['push', 'origin', branch], { cwd: path, shell: true, detached: true });
git.stdout.on('data', function (data) {
console.log('stdout data pushing', data.toString());
if (data.toString().includes('done')) {
resolve(git);
}
});
git.stderr.on('data', function (data) {
console.error('stderr data pushing', data.toString());
if (data.toString().includes(branch)) {
resolve(git);
}
});
git.on('error', (error) => {
reject(`Process error: ${error.message}`);
});
git.on('exit', (code, signal) => {
if (code !== 0) {
reject(`Process exited with code: ${code} and signal: ${signal}`);
} else {
resolve(git);
}
});
});
}
async function createCommitOnLocalServer(path: string, message: string) {
console.log('committing', message, path)
return new Promise((resolve, reject) => {
const git = spawn('git add . && git', ['commit', '-m', message], { cwd: path, shell: true, detached: true });
git.stdout.on('data', function (data) {
console.log('data stdout committing', data.toString());
if (data.toString().includes(message)) {
setTimeout(() => {
resolve(git);
}, 1000)
}
});
git.stderr.on('data', function (data) {
console.error('data commiting', data.toString());
reject(data.toString());
});
git.on('error', (error) => {
console.error('error', error);
reject(`Process error: ${error.message}`);
});
git.on('exit', (code, signal) => {
if (code !== 0) {
console.error('exit', code, signal);
reject(`Process exited with code: ${code} and signal: ${signal}`);
} else {
resolve(git);
}
});
});
}
async function spawnGitServer(path: string): Promise<ChildProcess> {
console.log(process.cwd())
try {
const server = spawn('yarn && sh setup.sh && npx ts-node server.ts', [`${path}`], { cwd: process.cwd() + '/apps/remix-ide-e2e/src/githttpbackend/', shell: true, detached: true })
console.log('spawned', server.stdout.closed, server.stderr.closed)
return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) {
console.log(data.toString())
if (
data.toString().includes('is listening')
|| data.toString().includes('address already in use')
) {
console.log('resolving')
resolve(server)
}
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) {
console.log(e)
}
}

@ -20,13 +20,13 @@ module.exports = {
.checkElementStyle('.view-lines', 'font-size', '14px') .checkElementStyle('.view-lines', 'font-size', '14px')
.click('*[data-id="tabProxyZoomIn"]') .click('*[data-id="tabProxyZoomIn"]')
.click('*[data-id="tabProxyZoomIn"]') .click('*[data-id="tabProxyZoomIn"]')
.checkElementStyle('.view-lines', 'font-size', '16.8px') .checkElementStyle('.view-lines', 'font-size', '19.6px')
}, },
'Should zoom out editor #group1': function (browser: NightwatchBrowser) { 'Should zoom out editor #group1': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('#editorView') .waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16.8px') .checkElementStyle('.view-lines', 'font-size', '19.6px')
.click('*[data-id="tabProxyZoomOut"]') .click('*[data-id="tabProxyZoomOut"]')
.click('*[data-id="tabProxyZoomOut"]') .click('*[data-id="tabProxyZoomOut"]')
.checkElementStyle('.view-lines', 'font-size', '14px') .checkElementStyle('.view-lines', 'font-size', '14px')

@ -6,7 +6,7 @@ declare global {
interface Window { testplugin: { name: string, url: string }; } interface Window { testplugin: { name: string, url: string }; }
} }
module.exports = { const tests = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null) init(browser, done, null)
@ -58,6 +58,14 @@ module.exports = {
} }
} }
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
};
const verifiedContract = ` const verifiedContract = `
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0

@ -39,7 +39,7 @@ module.exports = {
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() }) // .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemREADME.txt"]`) .waitForElementVisible(`[data-id="treeViewLitreeViewItemREADME.txt"]`)
.openFile(`README.txt`) //.openFile(`README.txt`)
// Remix publish to gist // Remix publish to gist
/* .click('*[data-id="fileExplorerNewFilepublishToGist"]') /* .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.pause(2000) .pause(2000)

@ -10,7 +10,7 @@ declare global {
const localPluginData: Profile & LocationProfile & ExternalProfile = { const localPluginData: Profile & LocationProfile & ExternalProfile = {
name: 'localPlugin', name: 'localPlugin',
displayName: 'Local Plugin', displayName: 'Local Plugin',
canActivate: ['dGitProvider', 'flattener', 'solidityUnitTesting', 'udapp', 'hardhat-provider'], canActivate: ['dgitApi', 'flattener', 'solidityUnitTesting', 'udapp', 'hardhat-provider'],
url: 'http://localhost:9999', url: 'http://localhost:9999',
location: 'sidePanel' location: 'sidePanel'
} }
@ -319,24 +319,24 @@ module.exports = {
// DGIT // DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) { 'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit') await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit')
await clickAndCheckLog(browser, 'dGitProvider:status', [[".prettierrc.json",0,2,0], ["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null) await clickAndCheckLog(browser, 'dgitApi:status', [[".prettierrc.json",0,2,0], ["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
}, },
'Should stage contract #group3': async function (browser: NightwatchBrowser) { 'Should stage contract #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:add', null, null, { await clickAndCheckLog(browser, 'dgitApi:add', null, null, {
filepath: 'contracts/1_Storage.sol' filepath: 'contracts/1_Storage.sol'
}) })
await clickAndCheckLog(browser, 'dGitProvider:status', [[".prettierrc.json",0,2,0],["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null) await clickAndCheckLog(browser, 'dgitApi:status', [[".prettierrc.json",0,2,0],["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
}, },
'Should commit changes #group3': async function (browser: NightwatchBrowser) { 'Should commit changes #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' }) await clickAndCheckLog(browser, 'dgitApi:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' })
await clickAndCheckLog(browser, 'dGitProvider:log', 'commit-message', null, null) await clickAndCheckLog(browser, 'dgitApi:log', 'commit-message', null, null)
}, },
'Should have git log #group3': async function (browser: NightwatchBrowser) { 'Should have git log #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:log', 'commit-message', null, null) await clickAndCheckLog(browser, 'dgitApi:log', 'commit-message', null, null)
}, },
'Should have branches #group3': async function (browser: NightwatchBrowser) { 'Should have branches #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:branches', [{ name: 'main' }], null, null) await clickAndCheckLog(browser, 'dgitApi:branches', [{ name: 'main' }], null, null)
}, },
// resolver // resolver
'Should resolve url #group4': async function (browser: NightwatchBrowser) { 'Should resolve url #group4': async function (browser: NightwatchBrowser) {

@ -72,7 +72,7 @@ module.exports = {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts')) remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) { } catch (err) {
console.error(err) console.error(err)
} }
console.log('working directory', process.cwd()) console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
@ -106,7 +106,7 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20 .setSolidityCompilerVersion('soljson-v0.8.20+commit.a1b79de6.js') // open-zeppelin moved to pragma ^0.8.20
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11']) .testContracts('test_import_node_modules_with_github_import.sol', sources[4]['test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
}, },
'Should setup a hardhat project #group4': function (browser: NightwatchBrowser) { 'Should setup a hardhat project #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => { browser.perform(async (done) => {
await setupHardhatProject() await setupHardhatProject()
@ -127,7 +127,7 @@ module.exports = {
done() done()
}) })
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000) .expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000)
let addressRef let addressRef
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
.openFile('contracts') .openFile('contracts')
@ -157,7 +157,7 @@ module.exports = {
}) })
.expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000) .expect.element('*[data-id="terminalJournal"]').text.to.contain('receiving compilation result from Hardhat').before(60000)
browser.clickLaunchIcon('filePanel') browser.clickLaunchIcon('filePanel')
.openFile('contracts') .openFile('contracts')
.openFile('contracts/Token.sol') .openFile('contracts/Token.sol')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
@ -188,7 +188,7 @@ module.exports = {
browser.perform(async (done) => { browser.perform(async (done) => {
console.log('working directory', homedir() + '/foundry_tmp/hello_foundry') console.log('working directory', homedir() + '/foundry_tmp/hello_foundry')
remixd = await spawnRemixd(join(homedir(), '/foundry_tmp/hello_foundry')) remixd = await spawnRemixd(join(homedir(), '/foundry_tmp/hello_foundry'))
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
.perform(async (done) => { .perform(async (done) => {
@ -242,9 +242,32 @@ module.exports = {
browser.testConstantFunction(contractAaddress, 'number - call', null, '0:\nuint256: 1').perform(() => { browser.testConstantFunction(contractAaddress, 'number - call', null, '0:\nuint256: 1').perform(() => {
done() done()
}) })
})
},
'Should disable git when running remixd #group9': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts/hardhat'))
console.log('working directory', process.cwd())
connectRemixd(browser, done)
})
browser
.clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="disabled"]')
.pause(2000)
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.click({
selector: '[data-path="connectionAlert-modal-footer-ok-react"]',
suppressNotFoundErrors: true,
timeout: 2000
}) })
.pause(2000)
.clickLaunchIcon('dgit')
.waitForElementNotPresent('*[data-id="disabled"]')
}, },
'Should install slither #group6': function (browser: NightwatchBrowser) { 'Should install slither #group6': function (browser: NightwatchBrowser) {
browser.perform(async (done) => { browser.perform(async (done) => {
await installSlither() await installSlither()
@ -258,26 +281,26 @@ module.exports = {
remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts')) remixd = await spawnRemixd(join(process.cwd(), '/apps/remix-ide', '/contracts'))
} catch (err) { } catch (err) {
console.error(err) console.error(err)
} }
console.log('working directory', process.cwd()) console.log('working directory', process.cwd())
connectRemixd(browser, done) connectRemixd(browser, done)
}) })
.openFile('ballot.sol') .openFile('ballot.sol')
.pause(2000) .pause(2000)
.clickLaunchIcon('solidityStaticAnalysis') .clickLaunchIcon('solidityStaticAnalysis')
.useXpath() .useXpath()
.click('//*[@id="staticAnalysisRunBtn"]') .click('//*[@id="staticAnalysisRunBtn"]')
.waitForElementPresent('//*[@id="staticanalysisresult"]', 5000) .waitForElementPresent('//*[@id="staticanalysisresult"]', 5000)
.waitForElementVisible({ .waitForElementVisible({
selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '3')]", selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '3')]",
locateStrategy: 'xpath', locateStrategy: 'xpath',
timeout: 5000 timeout: 5000
}) })
.waitForElementVisible({ .waitForElementVisible({
selector: "//div[@data-id='block']/span[contains(text(), '3 warnings found.')]", selector: "//div[@data-id='block']/span[contains(text(), '3 warnings found.')]",
locateStrategy: 'xpath', locateStrategy: 'xpath',
timeout: 5000 timeout: 5000
}) })
} }
} }
@ -336,11 +359,11 @@ async function installRemixd(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
remixd.stdout.on('data', function (data) { remixd.stdout.on('data', function (data) {
console.log(data.toString()) console.log(data.toString())
if( if (
data.toString().includes('success Saved lockfile') data.toString().includes('success Saved lockfile')
|| data.toString().includes('success Already up-to-date') || data.toString().includes('success Already up-to-date')
) { ) {
resolve() resolve()
} }
}) })
@ -357,12 +380,11 @@ async function spawnRemixd(path: string): Promise<ChildProcess> {
const remixd = spawn('chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js --remix-ide http://127.0.0.1:8080', [`-s ${path}`], { cwd: process.cwd(), shell: true, detached: true }) const remixd = spawn('chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js --remix-ide http://127.0.0.1:8080', [`-s ${path}`], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
remixd.stdout.on('data', function (data) { remixd.stdout.on('data', function (data) {
console.log(data.toString()) if (
if( data.toString().includes('is listening')
data.toString().includes('is listening')
|| data.toString().includes('There is already a client running') || data.toString().includes('There is already a client running')
) { ) {
resolve(remixd) resolve(remixd)
} }
}) })
@ -398,141 +420,141 @@ function connectRemixd(browser: NightwatchBrowser, done: any) {
async function setupHardhatProject(): Promise<void> { async function setupHardhatProject(): Promise<void> {
console.log(process.cwd()) console.log(process.cwd())
try { try {
const server = spawn('git clone https://github.com/NomicFoundation/hardhat-boilerplate && cd hardhat-boilerplate && yarn install && yarn add "@typechain/ethers-v5@^10.1.0" && yarn add "@typechain/hardhat@^6.1.2" && yarn add "typechain@^8.1.0" && echo "END"', [], { cwd: process.cwd() + '/apps/remix-ide', shell: true, detached: true }) const server = spawn('git clone https://github.com/NomicFoundation/hardhat-boilerplate && cd hardhat-boilerplate && yarn install && yarn add "@typechain/ethers-v5@^10.1.0" && yarn add "@typechain/hardhat@^6.1.2" && yarn add "typechain@^8.1.0" && echo "END"', [], { cwd: process.cwd() + '/apps/remix-ide', shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) { server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode); console.log("Child exited with code: " + exitCode);
console.log('end') console.log('end')
resolve() resolve()
})
}) })
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function compileHardhatProject(): Promise<void> { async function compileHardhatProject(): Promise<void> {
console.log(process.cwd()) console.log(process.cwd())
try { try {
const server = spawn('npx hardhat compile', [], { cwd: process.cwd() + '/apps/remix-ide/hardhat-boilerplate', shell: true, detached: true }) const server = spawn('npx hardhat compile', [], { cwd: process.cwd() + '/apps/remix-ide/hardhat-boilerplate', shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) { server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode); console.log("Child exited with code: " + exitCode);
console.log('end') console.log('end')
resolve() resolve()
})
}) })
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function downloadFoundry(): Promise<void> { async function downloadFoundry(): Promise<void> {
console.log('downloadFoundry', process.cwd()) console.log('downloadFoundry', process.cwd())
try { try {
const server = spawn('curl -L https://foundry.paradigm.xyz | bash', [], { cwd: process.cwd(), shell: true, detached: true }) const server = spawn('curl -L https://foundry.paradigm.xyz | bash', [], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) { server.stdout.on('data', function (data) {
console.log(data.toString()) console.log(data.toString())
if ( if (
data.toString().includes("simply run 'foundryup' to install Foundry") data.toString().includes("simply run 'foundryup' to install Foundry")
|| data.toString().includes("foundryup: could not detect shell, manually add") || data.toString().includes("foundryup: could not detect shell, manually add")
) { ) {
console.log('resolving') console.log('resolving')
resolve() resolve()
} }
}) })
server.stderr.on('err', function (data) { server.stderr.on('err', function (data) {
console.log(data.toString()) console.log(data.toString())
reject(data.toString()) reject(data.toString())
})
}) })
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function installFoundry(): Promise<void> { async function installFoundry(): Promise<void> {
console.log('installFoundry', process.cwd()) console.log('installFoundry', process.cwd())
try { try {
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && foundryup', [], { cwd: process.cwd(), shell: true, detached: true }) const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && foundryup', [], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) { server.stdout.on('data', function (data) {
console.log(data.toString()) console.log(data.toString())
if ( if (
data.toString().includes("foundryup: done!") data.toString().includes("foundryup: done!")
) { ) {
console.log('resolving') console.log('resolving')
resolve() resolve()
} }
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
}) })
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function initFoundryProject(): Promise<void> { async function initFoundryProject(): Promise<void> {
console.log('initFoundryProject', homedir()) console.log('initFoundryProject', homedir())
try { try {
spawn('git config --global user.email \"you@example.com\"', [], { cwd: homedir(), shell: true, detached: true }) spawn('git config --global user.email \"you@example.com\"', [], { cwd: homedir(), shell: true, detached: true })
spawn('git config --global user.name \"Your Name\"', [], { cwd: homedir(), shell: true, detached: true }) spawn('git config --global user.name \"Your Name\"', [], { cwd: homedir(), shell: true, detached: true })
spawn('mkdir foundry_tmp', [], { cwd: homedir(), shell: true, detached: true }) spawn('mkdir foundry_tmp', [], { cwd: homedir(), shell: true, detached: true })
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge init hello_foundry', [], { cwd: homedir() + '/foundry_tmp', shell: true, detached: true }) const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge init hello_foundry', [], { cwd: homedir() + '/foundry_tmp', shell: true, detached: true })
server.stdout.pipe(process.stdout) server.stdout.pipe(process.stdout)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) { server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode); console.log("Child exited with code: " + exitCode);
console.log('end') console.log('end')
resolve() resolve()
})
}) })
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function buildFoundryProject(): Promise<void> { async function buildFoundryProject(): Promise<void> {
console.log('buildFoundryProject', homedir()) console.log('buildFoundryProject', homedir())
try { try {
const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge build', [], { cwd: homedir() + '/foundry_tmp/hello_foundry', shell: true, detached: true }) const server = spawn('export PATH="' + homedir() + '/.foundry/bin:$PATH" && forge build', [], { cwd: homedir() + '/foundry_tmp/hello_foundry', shell: true, detached: true })
server.stdout.pipe(process.stdout) server.stdout.pipe(process.stdout)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.on('exit', function (exitCode) { server.on('exit', function (exitCode) {
console.log("Child exited with code: " + exitCode); console.log("Child exited with code: " + exitCode);
console.log('end') console.log('end')
resolve() resolve()
})
}) })
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async function installSlither(): Promise<void> { async function installSlither(): Promise<void> {
console.log('installSlither', process.cwd()) console.log('installSlither', process.cwd())
try { try {
const server = spawn('node', ['./dist/libs/remixd/src/scripts/installSlither.js'], { cwd: process.cwd(), shell: true, detached: true }) const server = spawn('node', ['./dist/libs/remixd/src/scripts/installSlither.js'], { cwd: process.cwd(), shell: true, detached: true })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server.stdout.on('data', function (data) { server.stdout.on('data', function (data) {
console.log(data.toString()) console.log(data.toString())
if ( if (
data.toString().includes("Slither is ready to use") data.toString().includes("Slither is ready to use")
) { ) {
console.log('resolving') console.log('resolving')
resolve() resolve()
} }
})
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
}) })
server.stderr.on('err', function (data) {
console.log(data.toString())
reject(data.toString())
})
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }

@ -25,7 +25,7 @@ const checkAlerts = function (browser: NightwatchBrowser){
}) })
} }
module.exports = { const tests = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) init(browser, done)
@ -50,10 +50,10 @@ module.exports = {
.pause(5000) .pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => { .switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser browser
.waitForElementVisible('*[data-testid="page-container-footer-next"]') .waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix .click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]') .waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') .click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]') // .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]') // .click('*[data-testid="popover-close"]')
@ -162,7 +162,7 @@ module.exports = {
.perform((done) => { .perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => { browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser browser
.waitForElementPresent('[data-testid="page-container-footer-next"]') .waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx .click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix .switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
@ -177,7 +177,7 @@ module.exports = {
.perform((done) => { // call delegate .perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => { browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser browser
.waitForElementPresent('[data-testid="page-container-footer-next"]') .waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx .click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix .switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
@ -232,6 +232,13 @@ module.exports = {
} }
} }
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
};
const localsCheck = { const localsCheck = {
to: { to: {
value: '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB', value: '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB',

@ -2,6 +2,10 @@
import { NightwatchBrowser } from 'nightwatch' import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
const runMasterTests: boolean = (branch ? (isMasterBranch ? true : false) : true)
module.exports = { module.exports = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
@ -209,26 +213,27 @@ module.exports = {
}, },
'Should run a script which log transaction and block using web3.js and ethers #group7': function (browser: NightwatchBrowser) { 'Should run a script which log transaction and block using web3.js and ethers #group7': function (browser: NightwatchBrowser) {
browser if (runMasterTests)
.clickLaunchIcon('udapp') browser
.switchEnvironment('basic-http-provider') .clickLaunchIcon('udapp')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]') .switchEnvironment('basic-http-provider')
.execute(() => { .waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus() .execute(() => {
}, [], () => { }) (document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
.setValue('[data-id="modalDialogCustomPromp"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9') }, [], () => { })
.modalFooterOKClick('basic-http-provider') .setValue('[data-id="modalDialogCustomPromp"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
.clickLaunchIcon('filePanel') .modalFooterOKClick('basic-http-provider')
.openFile('README.txt') .clickLaunchIcon('filePanel')
.addFile('scripts/log_tx_block.js', { content: scriptBlockAndTransaction }) .openFile('README.txt')
.pause(1000) .addFile('scripts/log_tx_block.js', { content: scriptBlockAndTransaction })
.executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')') .pause(1000)
// check if the input of the transaction is being logged (web3 call) .executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x2b0006fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e9e0000000000000000000000000000000000000000000000000000000000004ea373ded44d6900b8b479935bee9c82176261653e334586e0fd282f569357c0777bd9d084474837ac94bf96f2e26590222a2b8e46545657c7cf06ce2833d267bd6f131b5b3fd36cb1ca3e07cf422224df0766d1a677bbdb7ee4cc0d634efa5367a302a94dac422a16b9b8d5c10fe0555924f8189f6b498bef507b1d32e7915bd4df184f51e6d79ae6a1b11d5745ce7d625cecc3bd0dc50af4f999ffb927225f5e5c019b499f5e1fdcbc70c45df61df76013d1b0d45cdf6a267dac1b4620c0db2efd251f6548509c9c69f5bd9d1ee38ac0df0c73be2774f7d2e1fb7ef5129010f29d091e3c48aed0f035fc29804c99927d33ff2a19ff526979355ac50b2542bc5d8f2d41e4f850d5981e0420807469e828b03173b96b757fbaeacda335e11b3ab8b02a48456fab35d41ca26abde751d5fca8ef5e7ba5295278b6e46ce2aab6c10b3d185a6137d3e5c28bb8dd3a797feaf35520fcb949ea074e1869e0011ef01f8162135e44bb797d3d6215ff74ffbee972c97264fc15d11c840e6a7e796dc1a418572f6dbcc842594a558e1a9e3cb7a159284e16fec758bbc303d13edc28fb6d8bb110c3a398e4ded1748da9854eb84679ad0c99bc59bea7956b521db3ed0a9057510cc11365858704989690f0d891af81b213b1f2e91e41e4998a467656eac87e7025ac2840c17f2b106df7d32a0139036bdf5d87344ca37e9ce770e0dbeb5e021d03a7d496a6695eb06d3de9258b43f3883ce155767962b52083504b19d6d609090a2f96e9724902bf1adbf57359ac1dda48a8ffe596b8d95cac1429378769a6ec2ff1c8a9c0bc343b0a6468f36696bfb202cde9f6cd5241b814096d777751b44f0cc2ac9e7ba142227e8d5f2dd8da62573953540da1abce82c59287b2f7a87a111851758c2505d8c1ded6c42a49fc5577451ee56126d2275da490baa645c3bcac0c31dabee7aa35e6cdffb56ac0d952c2583c6f50f906dfb96f5a98c49a5919031cff880bffbe371a50162a7bd0fa0398a5898eaf6ad6db868a7d807846a3592325bb4207d67ad96bac76435368962ba8944d0201c2f620fb29373a6f35c815d101af98111e9b4cc61e8ae77fc63ce375068328ec8d05b49486666fb0f756f99d2fe747c95b2a553965f304a324879393897315d310841f0a200cd156f6ca4ed2', 120000) // check if the input of the transaction is being logged (web3 call)
// check if the logsBloom is being logged (web3 call) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x2b0006fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e9e0000000000000000000000000000000000000000000000000000000000004ea373ded44d6900b8b479935bee9c82176261653e334586e0fd282f569357c0777bd9d084474837ac94bf96f2e26590222a2b8e46545657c7cf06ce2833d267bd6f131b5b3fd36cb1ca3e07cf422224df0766d1a677bbdb7ee4cc0d634efa5367a302a94dac422a16b9b8d5c10fe0555924f8189f6b498bef507b1d32e7915bd4df184f51e6d79ae6a1b11d5745ce7d625cecc3bd0dc50af4f999ffb927225f5e5c019b499f5e1fdcbc70c45df61df76013d1b0d45cdf6a267dac1b4620c0db2efd251f6548509c9c69f5bd9d1ee38ac0df0c73be2774f7d2e1fb7ef5129010f29d091e3c48aed0f035fc29804c99927d33ff2a19ff526979355ac50b2542bc5d8f2d41e4f850d5981e0420807469e828b03173b96b757fbaeacda335e11b3ab8b02a48456fab35d41ca26abde751d5fca8ef5e7ba5295278b6e46ce2aab6c10b3d185a6137d3e5c28bb8dd3a797feaf35520fcb949ea074e1869e0011ef01f8162135e44bb797d3d6215ff74ffbee972c97264fc15d11c840e6a7e796dc1a418572f6dbcc842594a558e1a9e3cb7a159284e16fec758bbc303d13edc28fb6d8bb110c3a398e4ded1748da9854eb84679ad0c99bc59bea7956b521db3ed0a9057510cc11365858704989690f0d891af81b213b1f2e91e41e4998a467656eac87e7025ac2840c17f2b106df7d32a0139036bdf5d87344ca37e9ce770e0dbeb5e021d03a7d496a6695eb06d3de9258b43f3883ce155767962b52083504b19d6d609090a2f96e9724902bf1adbf57359ac1dda48a8ffe596b8d95cac1429378769a6ec2ff1c8a9c0bc343b0a6468f36696bfb202cde9f6cd5241b814096d777751b44f0cc2ac9e7ba142227e8d5f2dd8da62573953540da1abce82c59287b2f7a87a111851758c2505d8c1ded6c42a49fc5577451ee56126d2275da490baa645c3bcac0c31dabee7aa35e6cdffb56ac0d952c2583c6f50f906dfb96f5a98c49a5919031cff880bffbe371a50162a7bd0fa0398a5898eaf6ad6db868a7d807846a3592325bb4207d67ad96bac76435368962ba8944d0201c2f620fb29373a6f35c815d101af98111e9b4cc61e8ae77fc63ce375068328ec8d05b49486666fb0f756f99d2fe747c95b2a553965f304a324879393897315d310841f0a200cd156f6ca4ed2', 120000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x0fbbd94c448fe6949f848380a1d145a974f386624b4b10aa40f9afb212b3ddeb', 120000) // hash of 4757766 // check if the logsBloom is being logged (web3 call)
// check if the logsBloom is being logged (ethers.js call) .waitForElementContainsText('*[data-id="terminalJournal"]', '0x0fbbd94c448fe6949f848380a1d145a974f386624b4b10aa40f9afb212b3ddeb', 120000) // hash of 4757766
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x9db899cb75888a630ba50a1644c243b83d2eb38525eb828a06a5e8bb5663c0b0', 120000) // hash of 4757767 // check if the logsBloom is being logged (ethers.js call)
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x9db899cb75888a630ba50a1644c243b83d2eb38525eb828a06a5e8bb5663c0b0', 120000) // hash of 4757767
}, },
'Should listen on all transactions #group8': function (browser: NightwatchBrowser) { 'Should listen on all transactions #group8': function (browser: NightwatchBrowser) {
@ -291,46 +296,48 @@ module.exports = {
}, },
'Should connect to mainnet fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) { 'Should connect to mainnet fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
browser if (runMasterTests)
.clickLaunchIcon('udapp') browser
.switchEnvironment('vm-mainnet-fork') .clickLaunchIcon('udapp')
.waitForElementPresent({ .switchEnvironment('vm-mainnet-fork')
locateStrategy: 'css selector', .waitForElementPresent({
selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]', locateStrategy: 'css selector',
timeout: 240000 selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]',
}) timeout: 240000
.executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract })
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000) .executeScriptInTerminal(`web3.eth.getCode('0x180587b00c8642e2c7ac3a758712d97e6f7bdcc7')`) // mainnet contract
.click('*[data-id="terminalClearConsole"]') .waitForElementContainsText('*[data-id="terminalJournal"]', '0x608060405260043610601f5760003560e01c80635c60da1b14603157602b565b36602b576029605f565b005b6029605f565b348015603c57600080fd5b5060436097565b6040516001600160a01b03909116815260200160405180910390f35b609560917f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b60d1565b565b600060c97f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b90565b3660008037600080366000845af43d6000803e80801560ef573d6000f35b3d6000fdfea2646970667358221220969dbb4b1d8aec2bb348e26488dc1a33b6bcf0190f567d161312ab7ca9193d8d64736f6c63430008110033', 120000)
.click('*[data-id="terminalClearConsole"]')
}, },
'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) { 'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
browser if (runMasterTests)
.switchEnvironment('vm-custom-fork') browser
.waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]') .switchEnvironment('vm-custom-fork')
.execute(() => { .waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus() .execute(() => {
}, [], () => { }) (document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkNodeUrl"]') as any).focus()
.clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9') }, [], () => { })
.execute(() => { .clearValue('*[data-id="CustomForkNodeUrl"]').pause(1000).setValue('*[data-id="CustomForkNodeUrl"]', 'https://go.getblock.io/ee42d0a88f314707be11dd799b122cb9')
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkBlockNumber"]') as any).focus() .execute(() => {
}, [], () => { }) (document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkBlockNumber"]') as any).focus()
.clearValue('*[data-id="CustomForkBlockNumber"]').setValue('*[data-id="CustomForkBlockNumber"]', 'latest') }, [], () => { })
.execute(() => { .clearValue('*[data-id="CustomForkBlockNumber"]').setValue('*[data-id="CustomForkBlockNumber"]', 'latest')
(document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkEvmType"]') as any).focus() .execute(() => {
}, [], () => { }) (document.querySelector('*[data-id="vm-custom-forkModalDialogContainer-react"] input[data-id="CustomForkEvmType"]') as any).focus()
.click('*[data-id="CustomForkEvmType"] [value="cancun"]') }, [], () => { })
.pause(5000) .click('*[data-id="CustomForkEvmType"] [value="cancun"]')
.modalFooterOKClick('vm-custom-fork') .pause(5000)
.waitForElementPresent({ .modalFooterOKClick('vm-custom-fork')
locateStrategy: 'css selector', .waitForElementPresent({
selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]', locateStrategy: 'css selector',
timeout: 240000 selector: 'select[data-id="runTabSelectAccount"] option[value="0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]',
}) timeout: 240000
.pause(5000) })
.executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract .pause(5000)
.waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000) .executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract
.click('*[data-id="terminalClearConsole"]') .waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000)
.click('*[data-id="terminalClearConsole"]')
}, },
'Should run a free function while being connected to mainnet #group9': function (browser: NightwatchBrowser) { 'Should run a free function while being connected to mainnet #group9': function (browser: NightwatchBrowser) {
@ -354,29 +361,31 @@ module.exports = {
console.log(resolver.addr(node)); console.log(resolver.addr(node));
} }
` `
browser if (runMasterTests) {
// .clickLaunchIcon('udapp') browser
.switchEnvironment('vm-mainnet-fork') // .clickLaunchIcon('udapp')
.clickLaunchIcon('filePanel') .switchEnvironment('vm-mainnet-fork')
.addFile('test_mainnet.sol', { content: script }) .clickLaunchIcon('filePanel')
.addFile('test_mainnet.sol', { content: script })
const path = "//*[@class='view-line' and contains(.,'resolveENS') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "resolveENS"']` const path = "//*[@class='view-line' and contains(.,'resolveENS') and contains(.,'view')]//span//span[contains(.,'(')]"
browser.waitForElementVisible('#editorView') const pathRunFunction = `//li//*[@aria-label='Run the free function "resolveENS"']`
//.waitForElementPresent(pathRunFunction) browser.waitForElementVisible('#editorView')
.pause(10000) // the parser need to parse the code //.waitForElementPresent(pathRunFunction)
.useXpath() .pause(10000) // the parser need to parse the code
.scrollToLine(16) .useXpath()
.click(path) .scrollToLine(16)
.perform(function () { .click(path)
const actions = this.actions({ async: true }); .perform(function () {
return actions const actions = this.actions({ async: true });
.keyDown(this.Keys.SHIFT) return actions
.keyDown(this.Keys.ALT) .keyDown(this.Keys.SHIFT)
.sendKeys('r') .keyDown(this.Keys.ALT)
}) .sendKeys('r')
.useCss() })
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000) .useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 120000)
}
}, },
'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) { 'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) {

@ -27,30 +27,26 @@ module.exports = {
.frameParent() .frameParent()
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.waitForElementVisible({ .waitForElementVisible({
selector: "//*[@data-id='workspacesSelect' and contains(.,'vyper-lang')]", selector: "//*[@data-id='workspacesSelect' and contains(.,'snekmate')]",
locateStrategy: 'xpath', locateStrategy: 'xpath',
timeout: 60000 timeout: 120000
})
.currentWorkspaceIs('snekmate')
.waitForElementVisible({
selector: "//*[@data-id='treeViewLitreeViewItemsrc' and contains(.,'src')]",
locateStrategy: 'xpath',
timeout: 1200000
}) })
.currentWorkspaceIs('vyper-lang')
// .waitForElementVisible({
// selector: "//*[@data-id='treeViewLitreeViewItemsrc' and contains(.,'src')]",
// locateStrategy: 'xpath',
// timeout: 60000
// })
.openFile('examples')
.openFile('examples/auctions')
.openFile('examples/auctions/simple_open_auction.vy')
}, },
// 'Add vyper file to run tests #group1': function (browser: NightwatchBrowser) {
// '@sources': () => sources, // browser.addFile('TestBallot.sol', sources[0]['TestBallot.sol'])
// 'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) { // },
// browser '@sources': () => sources,
// .addFileSnekmate('blind_auction.vy', sources[0]['blindAuction'])
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) { 'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
browser browser
.click('*[data-id="treeViewDivtreeViewItemexamples/auctions/blind_auction.vy"]') .addFileSnekmate('blind_auction.vy', sources[0]['blindAuction'])
.rightClick('*[data-id="treeViewDivtreeViewItemexamples/auctions/blind_auction.vy"]') .click('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.rightClick('*[data-id="treeViewLitreeViewItemblind_auction.vy"]')
.waitForElementPresent('[data-id="contextMenuItemvyper"]') .waitForElementPresent('[data-id="contextMenuItemvyper"]')
.click('[data-id="contextMenuItemvyper"]') .click('[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper') .clickLaunchIcon('vyper')
@ -149,32 +145,32 @@ module.exports = {
}) })
}, },
// 'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) { 'Compile Ownable contract from snekmate #group1': function (browser: NightwatchBrowser) {
// let contractAddress let contractAddress
// browser browser
// .frameParent() .frameParent()
// .clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
// .switchWorkspace('snekmate') .switchWorkspace('snekmate')
// .openFile('src') .openFile('src')
// .openFile('src/snekmate') .openFile('src/snekmate')
// .openFile('src/snekmate/auth') .openFile('src/snekmate/auth')
// .openFile('src/snekmate/auth/Ownable.vy') .openFile('src/snekmate/auth/Ownable.vy')
// .rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]') .rightClick('*[data-id="treeViewLitreeViewItemsrc/snekmate/auth/Ownable.vy"]')
// .waitForElementVisible('*[data-id="contextMenuItemvyper"]') .waitForElementVisible('*[data-id="contextMenuItemvyper"]')
// .click('*[data-id="contextMenuItemvyper"]') .click('*[data-id="contextMenuItemvyper"]')
// .clickLaunchIcon('vyper') .clickLaunchIcon('vyper')
// // @ts-ignore // @ts-ignore
// .frame(0) .frame(0)
// .click('[data-id="compile"]') .click('[data-id="compile"]')
// .waitForElementVisible({ .waitForElementVisible({
// selector:'[data-id="compilation-details"]', selector:'[data-id="compilation-details"]',
// timeout: 60000 timeout: 60000
// }) })
// .click('[data-id="compilation-details"]') .click('[data-id="compilation-details"]')
// .frameParent() .frameParent()
// .waitForElementVisible('[data-id="copy-abi"]') .waitForElementVisible('[data-id="copy-abi"]')
// .end() .end()
// } }
} }
const testContract = ` const testContract = `
@ -388,6 +384,6 @@ def auctionEnd():
# Transfer funds to beneficiary # Transfer funds to beneficiary
send(self.beneficiary, self.highestBid) send(self.beneficiary, self.highestBid)
` } `}
} }
] ]

@ -40,6 +40,7 @@ module.exports = {
.click('[data-id="settingsTabSaveGistToken"]') .click('[data-id="settingsTabSaveGistToken"]')
}, },
'Should create and initialize a GIT repository #group1': function (browser: NightwatchBrowser) { 'Should create and initialize a GIT repository #group1': function (browser: NightwatchBrowser) {
browser browser
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
@ -59,6 +60,20 @@ module.exports = {
.waitForElementVisible('[data-id="workspaceGitPanel"]') .waitForElementVisible('[data-id="workspaceGitPanel"]')
.waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main') .waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main')
}, },
'check git for the commit #group1': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commits-current-branch-main"]//*[@data-id="commit-summary-Initial commit: remix template blank-"]',
locateStrategy: 'xpath'
})
.click('*[data-id="branches-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="branches-panel-content"]//*[@data-id="branches-current-branch-main"]',
locateStrategy: 'xpath'
})
},
// CLONE REPOSITORY E2E START // CLONE REPOSITORY E2E START
@ -139,7 +154,7 @@ module.exports = {
.click('[data-id="fileSystem-modal-footer-ok-react"]') .click('[data-id="fileSystem-modal-footer-ok-react"]')
.pause(5000) .pause(5000)
.waitForElementVisible('[data-id="cloneGitRepositoryModalDialogModalBody-react"]') .waitForElementVisible('[data-id="cloneGitRepositoryModalDialogModalBody-react"]')
.waitForElementContainsText('[data-id="cloneGitRepositoryModalDialogModalBody-react"]', 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in Settings plugin') .waitForElementContainsText('[data-id="cloneGitRepositoryModalDialogModalBody-react"]', 'An error occurred: Please check that you have the correct URL for the repo. If the repo is private, you need to add your github credentials (with the valid token permissions) in the Git plugin')
.click('[data-id="cloneGitRepository-modal-footer-ok-react"]') .click('[data-id="cloneGitRepository-modal-footer-ok-react"]')
}, },
@ -314,7 +329,6 @@ module.exports = {
'When switching branches the submodules should disappear #group4': function (browser: NightwatchBrowser) { 'When switching branches the submodules should disappear #group4': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]') .waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.pause()
.click('[data-id="workspaceGitBranchesDropdown"]') .click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]') .waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/empty') .waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/empty')
@ -340,26 +354,26 @@ module.exports = {
timeout: 240000 timeout: 240000
}) })
.pause(2000) .pause(2000)
// check recursive submodule // check recursive submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]') .click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]') .click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]') .click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2/submodule2.ts"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2/submodule2.ts"]')
// check test-branch-submodule-2 submodule // check test-branch-submodule-2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]') .click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]') .click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2/submodule2.ts"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2/submodule2.ts"]')
// check libdeep submodule // check libdeep submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep"]') .click('[data-id="treeViewDivtreeViewItemlibdeep"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep"]') .click('[data-id="treeViewDivtreeViewItemlibdeep"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]') .click('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2/submodule2.ts"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2/submodule2.ts"]')
// check libdeep2 submodule // check libdeep2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2"]') .click('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]')
@ -375,6 +389,83 @@ module.exports = {
'Should create a git workspace (uniswapV4Template) #group4': function (browser: NightwatchBrowser) { 'Should create a git workspace (uniswapV4Template) #group4': function (browser: NightwatchBrowser) {
browser browser
.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=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
.openFile('src/Counter.sol')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract Counter is BaseHook {`) !== -1,
'Incorrect content')
}).pause()
},
'Should create Remix default workspace with files #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.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() })
.waitForElementVisible('*[data-id="treeViewDivDraggableItemtests/MyToken_test.sol"]')
},
'Update settings for git #group5': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
.click('*[data-id="initgit-btn"]')
.setValue('*[data-id="gitubUsername"]', 'git')
.setValue('*[data-id="githubEmail"]', 'git@example.com')
.click('*[data-id="saveGitHubCredentials"]')
.modalFooterOKClick('github-credentials-error')
},
'check source controle panel #group5': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/tests/MyToken_test.sol']",
locateStrategy: 'xpath'
})
},
'switch workspace and check git #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
},
'check source controle panel again #group5': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.clickLaunchIcon('dgit')
.waitForElementVisible({
selector: '*[data-id="initgit-btn"]',
suppressNotFoundErrors: true
})
.click({
selector:'*[data-id="initgit-btn"]',
suppressNotFoundErrors: true
})
.pause(2000)
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/tests/storage.test.js']",
locateStrategy: 'xpath'
})
},
'Should create a git workspace (uniswapV4Template) #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]') .click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]') .click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
@ -393,6 +484,23 @@ module.exports = {
'Incorrect content') 'Incorrect content')
}) })
}, },
'check source controle panel for uniswap #group5': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.clickLaunchIcon('dgit')
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin" and contains(.,"v4-template")]',
locateStrategy: 'xpath'
})
},
// GIT WORKSPACE E2E ENDS // GIT WORKSPACE E2E ENDS

@ -11,7 +11,7 @@ declare module 'nightwatch' {
scrollInto(target: string): NightwatchBrowser scrollInto(target: string): NightwatchBrowser
testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser
setEditorValue(value: string, callback?: () => void): NightwatchBrowser setEditorValue(value: string, callback?: () => void): NightwatchBrowser
addFile(name: string, content: NightwatchContractContent): NightwatchBrowser addFile(name: string, content: NightwatchContractContent, readMeFile?: string): NightwatchBrowser
verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser
selectAccount(account?: string): NightwatchBrowser selectAccount(account?: string): NightwatchBrowser
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser

@ -64,6 +64,10 @@ import { HardhatHandle } from './app/files/hardhat-handle'
import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin' import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin'
import { SolCoder } from './app/plugins/solcoderAI' import { SolCoder } from './app/plugins/solcoderAI'
import { GitPlugin } from './app/plugins/git'
import { Matomo } from './app/plugins/matomo'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -234,6 +238,12 @@ class AppComponent {
//---- templates //---- templates
const templates = new TemplatesPlugin() const templates = new TemplatesPlugin()
//---- git
const git = new GitPlugin()
//---- matomo
const matomo = new Matomo()
//---------------- Solidity UML Generator ------------------------- //---------------- Solidity UML Generator -------------------------
const solidityumlgen = new SolidityUmlGen(appManager) const solidityumlgen = new SolidityUmlGen(appManager)
@ -367,7 +377,9 @@ class AppComponent {
solidityScript, solidityScript,
templates, templates,
solcoder, solcoder,
pluginStateLogger git,
pluginStateLogger,
matomo
]) ])
//---- fs plugin //---- fs plugin
@ -497,7 +509,8 @@ class AppComponent {
'network', 'network',
'web3Provider', 'web3Provider',
'offsetToLineColumnConverter', 'offsetToLineColumnConverter',
'pluginStateLogger' 'pluginStateLogger',
'matomo'
]) ])
await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs']) await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['statusBar']) await this.appManager.activatePlugin(['statusBar'])
@ -520,7 +533,7 @@ class AppComponent {
]) ])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgit'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()) { if (isElectron()) {

@ -7,6 +7,7 @@ import { PluginProfile, StatusBarInterface } from '../../types'
import { RemixUIStatusBar } from '@remix-ui/statusbar' import { RemixUIStatusBar } from '@remix-ui/statusbar'
import { FilePanelType } from '@remix-ui/workspace' import { FilePanelType } from '@remix-ui/workspace'
import { VerticalIcons } from './vertical-icons' import { VerticalIcons } from './vertical-icons'
import { CustomRemixApi } from '@remix-api'
const statusBarProfile: PluginProfile = { const statusBarProfile: PluginProfile = {
name: 'statusBar', name: 'statusBar',
@ -16,7 +17,7 @@ const statusBarProfile: PluginProfile = {
version: packageJson.version, version: packageJson.version,
} }
export class StatusBar extends Plugin implements StatusBarInterface { export class StatusBar extends Plugin<any, CustomRemixApi> implements StatusBarInterface {
htmlElement: HTMLDivElement htmlElement: HTMLDivElement
events: EventEmitter events: EventEmitter
filePanelPlugin: FilePanelType filePanelPlugin: FilePanelType
@ -75,7 +76,7 @@ export class StatusBar extends Plugin implements StatusBarInterface {
const workspaceName = localStorage.getItem('currentWorkspace') const workspaceName = localStorage.getItem('currentWorkspace')
workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'error' workspaceName && workspaceName.length > 0 ? this.currentWorkspaceName = workspaceName : this.currentWorkspaceName = 'error'
}) })
this.on('settings', 'copilotChoiceChanged', (isAiActive) => { this.on('settings', 'copilotChoiceChanged', (isAiActive: boolean) => {
this.isAiActive = isAiActive this.isAiActive = isAiActive
}) })
this.renderComponent() this.renderComponent()

@ -5,6 +5,7 @@ import { EditorUI } from '@remix-ui/editor' // eslint-disable-line
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper' import { PluginViewWrapper } from '@remix-ui/helper'
import { commitChange } from '@remix-ui/git'
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
@ -13,7 +14,7 @@ const profile = {
name: 'editor', name: 'editor',
description: 'service - editor', description: 'service - editor',
version: packageJson.version, version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText', 'getPositionAt'], methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers', 'getText', 'getPositionAt', 'openReadOnly'],
} }
class Editor extends Plugin { class Editor extends Plugin {
@ -63,7 +64,8 @@ class Editor extends Plugin {
onBreakPointAdded: (file, line) => this.triggerEvent('breakpointAdded', [file, line]), onBreakPointAdded: (file, line) => this.triggerEvent('breakpointAdded', [file, line]),
onBreakPointCleared: (file, line) => this.triggerEvent('breakpointCleared', [file, line]), onBreakPointCleared: (file, line) => this.triggerEvent('breakpointCleared', [file, line]),
onDidChangeContent: (file) => this._onChange(file), onDidChangeContent: (file) => this._onChange(file),
onEditorMounted: () => this.triggerEvent('editorMounted', []) onEditorMounted: () => this.triggerEvent('editorMounted', []),
onDiffEditorMounted: () => this.triggerEvent('diffEditorMounted', [])
} }
// to be implemented by the react component // to be implemented by the react component
@ -81,8 +83,10 @@ class Editor extends Plugin {
editorAPI={state.api} editorAPI={state.api}
themeType={state.currentThemeType} themeType={state.currentThemeType}
currentFile={state.currentFile} currentFile={state.currentFile}
currentDiffFile={state.currentDiffFile}
events={state.events} events={state.events}
plugin={state.plugin} plugin={state.plugin}
isDiff={state.isDiff}
/> />
} }
@ -111,6 +115,8 @@ class Editor extends Plugin {
api: this.api, api: this.api,
currentThemeType: this.currentThemeType, currentThemeType: this.currentThemeType,
currentFile: this.currentFile, currentFile: this.currentFile,
currentDiffFile: this.currentDiffFile,
isDiff: this.isDiff,
events: this.events, events: this.events,
plugin: this plugin: this
}) })
@ -183,9 +189,10 @@ class Editor extends Plugin {
} }
_switchSession (path) { _switchSession (path) {
if (path === this.currentFile) return if (path !== this.currentFile) {
this.triggerEvent('sessionSwitched', []) this.triggerEvent('sessionSwitched', [])
this.currentFile = path this.currentFile = path
}
this.renderComponent() this.renderComponent()
} }
@ -241,10 +248,10 @@ class Editor extends Plugin {
* @param {string} content Content of the file to open * @param {string} content Content of the file to open
* @param {string} mode Mode for this file [Default is `text`] * @param {string} mode Mode for this file [Default is `text`]
*/ */
async _createSession (path, content, mode) { async _createSession (path, content, mode, readOnly) {
if (!this.activated) return if (!this.activated) return
this.emit('addModel', content, mode, path, this.readOnlySessions[path]) this.emit('addModel', content, mode, path, readOnly || this.readOnlySessions[path])
return { return {
path, path,
language: mode, language: mode,
@ -314,6 +321,7 @@ class Editor extends Plugin {
- URL prepended with "browser" - URL prepended with "browser"
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL - URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/ */
this.isDiff = false
if (!this.sessions[path]) { if (!this.sessions[path]) {
this.readOnlySessions[path] = false this.readOnlySessions[path] = false
const session = await this._createSession(path, content, this._getMode(path)) const session = await this._createSession(path, content, this._getMode(path))
@ -335,9 +343,22 @@ class Editor extends Plugin {
const session = await this._createSession(path, content, this._getMode(path)) const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session this.sessions[path] = session
} }
this.isDiff = false
this._switchSession(path) this._switchSession(path)
} }
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)
await this._createSession(hashedPathOriginal, change.original, this._getMode(change.path), change.readonly)
this.sessions[hashedPathModified] = session
this.currentDiffFile = hashedPathOriginal
this.isDiff = true
this._switchSession(hashedPathModified)
}
/** /**
* Content of the current session * Content of the current session
* @return {String} content of the file referenced by @arg path * @return {String} content of the file referenced by @arg path

File diff suppressed because it is too large Load Diff

@ -8,6 +8,7 @@ import { Registry } from '@remix-project/remix-lib'
import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper' import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js' import helper from '../../lib/helper.js'
import { RemixAppManager } from '../../remixAppManager' import { RemixAppManager } from '../../remixAppManager'
import { commitChange } from '@remix-ui/git'
/* /*
attach to files event (removed renamed) attach to files event (removed renamed)
@ -24,7 +25,8 @@ const profile = {
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite', methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodules', 'copyFolderToJson' 'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule', 'copyFolderToJson', 'diff',
'hasGitSubmodules'
], ],
kind: 'file-system' kind: 'file-system'
} }
@ -702,6 +704,37 @@ class FileManager extends Plugin {
this.emit('noFileSelected') this.emit('noFileSelected')
} }
async diff(change: commitChange) {
await this.saveCurrentFile()
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
if (!change.readonly){
let file = this.normalize(change.path)
const resolved = this.getPathFromUrl(file)
file = resolved.file
this._deps.config.set('currentFile', file)
this.openedFiles[file] = file
}
await this.editor.openDiff(change)
this.emit('openDiff', change)
}
async closeDiff(change: commitChange) {
if (!change.readonly){
const file = this.normalize(change.path)
delete this.openedFiles[file]
if (!Object.keys(this.openedFiles).length) {
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
}
}
this.emit('closeDiff', change)
}
async openFile(file?: string) { async openFile(file?: string) {
if (!file) { if (!file) {
this.emit('noFileSelected') this.emit('noFileSelected')
@ -710,16 +743,17 @@ class FileManager extends Plugin {
const resolved = this.getPathFromUrl(file) const resolved = this.getPathFromUrl(file)
file = resolved.file file = resolved.file
await this.saveCurrentFile() await this.saveCurrentFile()
if (this.currentFile() === file) return // we always open the file in the editor, even if it's the same as the current one if the editor is in diff mode
if (this.currentFile() === file && !this.editor.isDiff) return
const provider = resolved.provider const provider = resolved.provider
this._deps.config.set('currentFile', file) this._deps.config.set('currentFile', file)
this.openedFiles[file] = file this.openedFiles[file] = file
let content = '' let content = ''
try { try {
content = await provider.get(file) content = await provider.get(file)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
throw error throw error
@ -736,6 +770,7 @@ class FileManager extends Plugin {
} else { } else {
await this.editor.open(file, content) await this.editor.open(file, content)
} }
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file) this.emit('currentFileChanged', file)
return true return true
} }

@ -43,6 +43,11 @@ export class Layout extends Plugin {
this.panels.main.active = false this.panels.main.active = false
this.event.emit('change', null) this.event.emit('change', null)
}) })
this.on('fileManager', 'openDiff', () => {
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
})
this.on('tabs', 'openFile', () => { this.on('tabs', 'openFile', () => {
this.panels.editor.active = true this.panels.editor.active = true
this.panels.main.active = false this.panels.main.active = false
@ -59,6 +64,12 @@ export class Layout extends Plugin {
this.panels.main.active = false this.panels.main.active = false
this.event.emit('change', null) 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)
})
this.on('manager', 'activate', (profile: Profile) => { this.on('manager', 'activate', (profile: Profile) => {
switch (profile.name) { switch (profile.name) {
case 'filePanel': case 'filePanel':

@ -146,6 +146,23 @@ export class TabProxy extends Plugin {
} }
}) })
this.on('fileManager', 'openDiff', (commit) => {
const hash = commit.hashModified? commit.hashModified.substring(0,6): 'Working Tree'
const name = `${commit.path} (${hash})`
this.addTab(name, name, async () => {
await this.fileManager.diff(commit)
this.event.emit('openDiff', commit)
this.emit('openDiff', commit)
},
async () => {
this.removeTab(name)
await this.fileManager.closeDiff(commit)
this.event.emit('closeDiff', commit)
this.emit('closeDiff', commit)
})
this.tabsApi.activateTab(name)
})
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon, description }) => { this.on('manager', 'pluginActivated', ({ name, location, displayName, icon, description }) => {
if (location === 'mainPanel') { if (location === 'mainPanel') {
this.addTab( this.addTab(

@ -0,0 +1,40 @@
'use strict'
import { ViewPlugin } from '@remixproject/engine-web';
import React from 'react' // eslint-disable-line
import { gitState, GitUI } from '@remix-ui/git';
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'dgit',
desciption: 'Git plugin for Remix',
methods: ['pull', 'track', 'diff', 'clone', 'open'],
events: [''],
version: packageJson.version,
maintainedBy: 'Remix',
permission: true,
description: 'Use this plugin to interact with your git repositories',
location: 'sidePanel',
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjAwNyA4LjIyMTY4QzIxLjAxMDUgNy41Mjc5MiAyMC44MjA3IDYuODQ2ODkgMjAuNDU5MSA2LjI1NDg1QzIwLjA5NzQgNS42NjI4MSAxOS41NzggNS4xODMxNSAxOC45NTkyIDQuODY5NTdDMTguMzQwMyA0LjU1NiAxNy42NDYzIDQuNDIwOTEgMTYuOTU1MSA0LjQ3OTQxQzE2LjI2MzcgNC41Mzc5MyAxNS42MDI1IDQuNzg3NzMgMTUuMDQ1IDUuMjAwODVDMTQuNDg3NyA1LjYxMzk3IDE0LjA1NjMgNi4xNzQwOSAxMy43OTkzIDYuODE4NUMxMy41NDI0IDcuNDYyOSAxMy40Njk3IDguMTY2MTMgMTMuNTg5OCA4Ljg0OTQ0QzEzLjcwOTkgOS41MzI3NCAxNC4wMTc3IDEwLjE2OTIgMTQuNDc4OSAxMC42ODc0QzE0Ljk0MDIgMTEuMjA1NiAxNS41MzY3IDExLjU4NTIgMTYuMjAxNSAxMS43ODM2QzE1Ljk1NiAxMi4yODI0IDE1LjU3NjMgMTIuNzAzIDE1LjEwNDkgMTIuOTk3OUMxNC42MzM2IDEzLjI5MjkgMTQuMDg5NCAxMy40NTA1IDEzLjUzMzQgMTMuNDUzMkgxMC41NDRDOS40MzcyNiAxMy40NTcxIDguMzcxNjMgMTMuODcyNyA3LjU1NDUxIDE0LjYxOTFWNy4zOTgwOUM4LjQ2MTg0IDcuMjEyODggOS4yNjgwOCA2LjY5NzM3IDkuODE2OTIgNS45NTE1MUMxMC4zNjU4IDUuMjA1NjUgMTAuNjE4MSA0LjI4MjU2IDEwLjUyNSAzLjM2MTIxQzEwLjQzMTkgMi40Mzk4NyAxMC4wMDAxIDEuNTg1OSA5LjMxMzE2IDAuOTY0ODczQzguNjI2MjQgMC4zNDM4NDUgNy43MzMxOSAwIDYuODA3MTYgMEM1Ljg4MTEyIDAgNC45ODgwNyAwLjM0Mzg0NSA0LjMwMTE0IDAuOTY0ODczQzMuNjE0MjIgMS41ODU5IDMuMTgyMzYgMi40Mzk4NyAzLjA4OTI4IDMuMzYxMjFDMi45OTYyIDQuMjgyNTYgMy4yNDg1NSA1LjIwNTY1IDMuNzk3MzkgNS45NTE1MUM0LjM0NjIzIDYuNjk3MzcgNS4xNTI0NyA3LjIxMjg4IDYuMDU5OCA3LjM5ODA5VjE2LjUxNTlDNS4xNTQxOCAxNi42ODkxIDQuMzQzMjMgMTcuMTg3NyAzLjc3OTkzIDE3LjkxNzZDMy4yMTY2MyAxOC42NDc2IDIuOTM5OTIgMTkuNTU4NSAzLjAwMTk3IDIwLjQ3ODVDMy4wNjQwMyAyMS4zOTg0IDMuNDYwNTcgMjIuMjYzOSA0LjExNjggMjIuOTExNUM0Ljc3MzAzIDIzLjU1OTIgNS42NDM2IDIzLjk0NDQgNi41NjQyNyAyMy45OTQ0QzcuNDg0OTYgMjQuMDQ0NSA4LjM5MjExIDIzLjc1NTggOS4xMTQ2NCAyMy4xODNDOS44MzcxOCAyMi42MTAyIDEwLjMyNTEgMjEuNzkyOCAxMC40ODY1IDIwLjg4NUMxMC42NDc4IDE5Ljk3NzEgMTAuNDcxNCAxOS4wNDE3IDkuOTkwNDggMTguMjU1QzkuNTA5NTcgMTcuNDY4MyA4Ljc1NzQxIDE2Ljg4NDggNy44NzU4OCAxNi42MTQ1QzguMTIxNzYgMTYuMTE2MiA4LjUwMTY3IDE1LjY5NjMgOC45NzI5NiAxNS40MDE5QzkuNDQ0MjYgMTUuMTA3NCA5Ljk4ODI3IDE0Ljk1MDMgMTAuNTQ0IDE0Ljk0NzlIMTMuNTMzNEMxNC40NjYxIDE0Ljk0MzYgMTUuMzc0MiAxNC42NDg2IDE2LjEzMTMgMTQuMTAzOUMxNi44ODg0IDEzLjU1OTIgMTcuNDU2OCAxMi43OTIgMTcuNzU3NSAxMS45MDkxQzE4LjY1MzQgMTEuNzkxNCAxOS40NzYzIDExLjM1MjggMjAuMDczOCAxMC42NzQ4QzIwLjY3MTMgOS45OTY4IDIxLjAwMjggOS4xMjUzMyAyMS4wMDcgOC4yMjE2OFpNNC41NjUwOCAzLjczNzUyQzQuNTY1MDggMy4yOTQwOCA0LjY5NjU3IDIuODYwNiA0Ljk0MjkzIDIuNDkxOUM1LjE4OTMgMi4xMjMxOSA1LjUzOTQ3IDEuODM1ODEgNS45NDkxNSAxLjY2NjExQzYuMzU4ODQgMS40OTY0MiA2LjgwOTY1IDEuNDUyMDIgNy4yNDQ1NiAxLjUzODU0QzcuNjc5NDggMS42MjUwNCA4LjA3ODk4IDEuODM4NTcgOC4zOTI1NCAyLjE1MjE0QzguNzA2MTEgMi40NjU3IDguOTE5NjQgMi44NjUyIDkuMDA2MTUgMy4zMDAxMkM5LjA5MjY2IDMuNzM1MDQgOS4wNDgyNyA0LjE4NTg1IDguODc4NTcgNC41OTU1M0M4LjcwODg3IDUuMDA1MjEgOC40MjE0OSA1LjM1NTM5IDguMDUyNzggNS42MDE3NUM3LjY4NDA4IDUuODQ4MTEgNy4yNTA2IDUuOTc5NiA2LjgwNzE2IDUuOTc5NkM2LjIxMjUyIDUuOTc5NiA1LjY0MjI0IDUuNzQzMzkgNS4yMjE3NyA1LjMyMjkxQzQuODAxMjkgNC45MDI0NSA0LjU2NTA4IDQuMzMyMTYgNC41NjUwOCAzLjczNzUyWk05LjA0OTIzIDIwLjE3OTRDOS4wNDkyMyAyMC42MjI5IDguOTE3NzQgMjEuMDU2MyA4LjY3MTM4IDIxLjQyNUM4LjQyNTAxIDIxLjc5MzcgOC4wNzQ4NSAyMi4wODExIDcuNjY1MTYgMjIuMjUwOEM3LjI1NTQ3IDIyLjQyMDUgNi44MDQ2NiAyMi40NjQ5IDYuMzY5NzUgMjIuMzc4NEM1LjkzNDgzIDIyLjI5MiA1LjUzNTMzIDIyLjA3ODQgNS4yMjE3NyAyMS43NjQ4QzQuOTA4MjEgMjEuNDUxMiA0LjY5NDY3IDIxLjA1MTcgNC42MDgxNiAyMC42MTY5QzQuNTIxNjUgMjAuMTgxOSA0LjU2NjA1IDE5LjczMTEgNC43MzU3NSAxOS4zMjE0QzQuOTA1NDUgMTguOTExNyA1LjE5MjgyIDE4LjU2MTUgNS41NjE1MyAxOC4zMTUyQzUuOTMwMjMgMTguMDY4OSA2LjM2MzcxIDE3LjkzNzMgNi44MDcxNiAxNy45MzczQzcuNDAxNzkgMTcuOTM3MyA3Ljk3MjA3IDE4LjE3MzYgOC4zOTI1NCAxOC41OTRDOC44MTMwMiAxOS4wMTQ1IDkuMDQ5MjMgMTkuNTg0OCA5LjA0OTIzIDIwLjE3OTRaTTE3LjI3MDIgMTAuNDYzOEMxNi44MjY3IDEwLjQ2MzggMTYuMzkzMyAxMC4zMzIyIDE2LjAyNDYgMTAuMDg1OUMxNS42NTU5IDkuODM5NTQgMTUuMzY4NSA5LjQ4OTM3IDE1LjE5ODggOS4wNzk2OUMxNS4wMjkxIDguNjcgMTQuOTg0NyA4LjIxOTIgMTUuMDcxMiA3Ljc4NDI3QzE1LjE1NzYgNy4zNDkzNSAxNS4zNzEyIDYuOTQ5ODUgMTUuNjg0OCA2LjYzNjI5QzE1Ljk5ODQgNi4zMjI3MyAxNi4zOTc5IDYuMTA5MTkgMTYuODMyNyA2LjAyMjY4QzE3LjI2NzcgNS45MzYxNyAxNy43MTg1IDUuOTgwNTggMTguMTI4MSA2LjE1MDI3QzE4LjUzNzkgNi4zMTk5NyAxOC44ODgxIDYuNjA3MzQgMTkuMTM0NCA2Ljk3NjA1QzE5LjM4MDcgNy4zNDQ3NiAxOS41MTIzIDcuNzc4MjMgMTkuNTEyMyA4LjIyMTY4QzE5LjUxMjMgOC44MTYzMiAxOS4yNzYgOS4zODY2IDE4Ljg1NTYgOS44MDcwNkMxOC40MzUxIDEwLjIyNzUgMTcuODY0OCAxMC40NjM4IDE3LjI3MDIgMTAuNDYzOFoiIGZpbGw9IiM0MjQyNDIiLz4KPC9zdmc+Cg=="
}
export class GitPlugin extends ViewPlugin {
constructor() {
super(profile)
}
onDeactivation(): void {
this.call('fileDecorator', 'clearFileDecorators')
this.call('manager', 'activatePlugin', 'dgitApi')
}
async open(panel:string) {
this.emit('openPanel', panel)
}
render() {
return <div id='gitTab'><GitUI plugin={this} /></div>
}
}

@ -0,0 +1,26 @@
'use strict'
import { Plugin } from '@remixproject/engine'
const _paq = window._paq = window._paq || []
const profile = {
name: 'matomo',
description: 'send analytics to Matomo',
methods: ['track'],
events: [''],
version: '1.0.0'
}
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner']
export class Matomo extends Plugin {
constructor() {
super(profile)
}
async track(data: string[]) {
if (!allowedPlugins.includes(this.currentRequest.from)) return
_paq.push(data)
}
}

@ -78,7 +78,7 @@
"filePanel.checkoutGitBranch": "Checkout Git Branch", "filePanel.checkoutGitBranch": "Checkout Git Branch",
"filePanel.findOrCreateABranch": "Find or create a branch.", "filePanel.findOrCreateABranch": "Find or create a branch.",
"filePanel.initGitRepositoryLabel": "Initialize workspace as a new git repository", "filePanel.initGitRepositoryLabel": "Initialize workspace as a new git repository",
"filePanel.initGitRepositoryWarning": "To use Git features, add username and email to the Github section of the Settings panel.", "filePanel.initGitRepositoryWarning": "To use Git features, add username and email to the Github section of the Git plugin.",
"filePanel.workspaceName": "Workspace name", "filePanel.workspaceName": "Workspace name",
"filePanel.customizeTemplate": "Customize template", "filePanel.customizeTemplate": "Customize template",
"filePanel.features": "Features", "filePanel.features": "Features",
@ -141,5 +141,6 @@
"filePanel.movingFolderFailed": "Moving Folder Failed", "filePanel.movingFolderFailed": "Moving Folder Failed",
"filePanel.movingFolderFailedMsg": "Unexpected error while moving folder: {src}", "filePanel.movingFolderFailedMsg": "Unexpected error while moving folder: {src}",
"filePanel.workspaceActions": "Workspace actions", "filePanel.workspaceActions": "Workspace actions",
"filePanel.saveCodeSample": "This code-sample workspace will not be persisted. Click here to save it." "filePanel.saveCodeSample": "This code-sample workspace will not be persisted. Click here to save it.",
"filePanel.updateSubmodules": "Update all submodules of repository. Click to pull dependencies."
} }

@ -0,0 +1,20 @@
{
"git.push": "push",
"git.pull": "pull",
"git.commit": "commit",
"git.sync": "sync",
"git.syncchanges": "sync changes",
"git.publish": "publish",
"git.ignore": "ignore",
"git.createBranch": "create branch",
"git.deleteBranch": "delete branch",
"git.mergeBranch": "merge branch",
"git.rebaseBranch": "rebase branch",
"git.checkout": "checkout",
"git.fetch": "fetch",
"git.refresh": "refresh",
"git.unstageall": "unstage all",
"git.stageall": "stage all",
"git.noremote": "this repo has no remotes",
"git.init": "Initialize repository"
}

@ -45,7 +45,7 @@
"home.remixAdvanced": "Deploying with Libraries", "home.remixAdvanced": "Deploying with Libraries",
"home.remixAdvancedDesc": "Learn to deploy with libraries in Remix", "home.remixAdvancedDesc": "Learn to deploy with libraries in Remix",
"home.remixYoutubePlaylist": "Remix Youtube Playlist", "home.remixYoutubePlaylist": "Remix Youtube Playlist",
"home.remixTwitterProfile": "Remix Twitter Profile", "home.remixTwitterProfile": "Remix X Profile",
"home.remixLinkedinProfile": "Remix Linkedin Profile", "home.remixLinkedinProfile": "Remix Linkedin Profile",
"home.remixMediumPosts": "Remix Medium Posts", "home.remixMediumPosts": "Remix Medium Posts",
"home.joinUsOnDiscord": "Join us on Discord", "home.joinUsOnDiscord": "Join us on Discord",

@ -44,7 +44,7 @@
"home.remixAdvanced": "Publicando con Librearías", "home.remixAdvanced": "Publicando con Librearías",
"home.remixAdvancedDesc": "Aprende a publicar con librearías en Remix", "home.remixAdvancedDesc": "Aprende a publicar con librearías en Remix",
"home.remixYoutubePlaylist": "Lista de reproducción en YouTube de Remix", "home.remixYoutubePlaylist": "Lista de reproducción en YouTube de Remix",
"home.remixTwitterProfile": "Perfil en Twitter de Remix", "home.remixTwitterProfile": "Perfil en X de Remix",
"home.remixLinkedinProfile": "Perfil en Linkedin de Remix", "home.remixLinkedinProfile": "Perfil en Linkedin de Remix",
"home.remixMediumPosts": "Publicaciones en Medium de Remix", "home.remixMediumPosts": "Publicaciones en Medium de Remix",
"home.joinUsOnDiscord": "Únete a nosotros en Discord", "home.joinUsOnDiscord": "Únete a nosotros en Discord",

@ -42,7 +42,7 @@
"home.remixAdvanced": "Deploying with Libraries", "home.remixAdvanced": "Deploying with Libraries",
"home.remixAdvancedDesc": "Learn to deploy with libraries in Remix", "home.remixAdvancedDesc": "Learn to deploy with libraries in Remix",
"home.remixYoutubePlaylist": "Remix Youtube Playlist", "home.remixYoutubePlaylist": "Remix Youtube Playlist",
"home.remixTwitterProfile": "Remix Twitter Profile", "home.remixTwitterProfile": "Remix X Profile",
"home.remixLinkedinProfile": "Remix Linkedin Profile", "home.remixLinkedinProfile": "Remix Linkedin Profile",
"home.remixMediumPosts": "Remix Medium Posts", "home.remixMediumPosts": "Remix Medium Posts",
"home.remixGitterChannel": "Join us on Discord", "home.remixGitterChannel": "Join us on Discord",

@ -44,7 +44,7 @@
"home.remixAdvanced": "Distribuire con le Librerie", "home.remixAdvanced": "Distribuire con le Librerie",
"home.remixAdvancedDesc": "Impara a fare deployment con le librerie in Remix", "home.remixAdvancedDesc": "Impara a fare deployment con le librerie in Remix",
"home.remixYoutubePlaylist": "Playlist Youtube di Remix", "home.remixYoutubePlaylist": "Playlist Youtube di Remix",
"home.remixTwitterProfile": "Profilo Twitter di Remix", "home.remixTwitterProfile": "Profilo X di Remix",
"home.remixLinkedinProfile": "Profilo LinkedIn di Remix", "home.remixLinkedinProfile": "Profilo LinkedIn di Remix",
"home.remixMediumPosts": "Post in Medium di Remix", "home.remixMediumPosts": "Post in Medium di Remix",
"home.joinUsOnDiscord": "Unisciti a noi su Discord", "home.joinUsOnDiscord": "Unisciti a noi su Discord",

@ -44,7 +44,7 @@
"home.remixAdvanced": "Развертывание с Библиотеками", "home.remixAdvanced": "Развертывание с Библиотеками",
"home.remixAdvancedDesc": "Научитесь развертывать с библиотеками в Remix", "home.remixAdvancedDesc": "Научитесь развертывать с библиотеками в Remix",
"home.remixYoutubePlaylist": "Плейлист Remix на Youtube", "home.remixYoutubePlaylist": "Плейлист Remix на Youtube",
"home.remixTwitterProfile": "Remix Twitter Профиль", "home.remixTwitterProfile": "Remix X Профиль",
"home.remixLinkedinProfile": "Remix Linkedin профиль", "home.remixLinkedinProfile": "Remix Linkedin профиль",
"home.remixMediumPosts": "Remix Medium публикации", "home.remixMediumPosts": "Remix Medium публикации",
"home.joinUsOnDiscord": "Присоединяйтесь к нам в Discord", "home.joinUsOnDiscord": "Присоединяйтесь к нам в Discord",

@ -241,7 +241,7 @@ textarea {
font-size: inherit; font-size: inherit;
line-height: inherit; line-height: inherit;
background-color: #2b2b2b; background-color: #2b2b2b;
color: #d5d5d5d5d5d5; color: #d5d5d5;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 2px rgba(79, 86, 89, 0.25); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 2px rgba(79, 86, 89, 0.25);
} }
button, button,

@ -5663,7 +5663,7 @@ button.bg-success:hover {
background-color: #27926b !important; background-color: #27926b !important;
} }
.bg-info { .bg-info {
background-color: #274458 !important; background-color: #35576e !important;
} }
a.bg-info:focus, a.bg-info:focus,
a.bg-info:hover, a.bg-info:hover,

@ -36,7 +36,7 @@ let requiredModules = [ // services + layout views + system views
'pluginManager', 'pluginManager',
'tabs', 'tabs',
'udapp', 'udapp',
'dGitProvider', 'dgitApi',
'solidity', 'solidity',
'solidity-logic', 'solidity-logic',
'gistHandler', 'gistHandler',
@ -78,9 +78,11 @@ let requiredModules = [ // services + layout views + system views
'doc-gen', 'doc-gen',
'remix-templates', 'remix-templates',
'solhint', 'solhint',
'dgit',
'pinnedPanel', 'pinnedPanel',
'pluginStateLogger', 'pluginStateLogger',
'remixGuide' 'remixGuide',
'matomo'
] ]
@ -249,6 +251,7 @@ export class RemixAppManager extends PluginManager {
const res = await fetch(this.pluginsDirectory) const res = await fetch(this.pluginsDirectory)
plugins = await res.json() plugins = await res.json()
plugins = plugins.filter((plugin) => { plugins = plugins.filter((plugin) => {
if (plugin.name === 'dgit') return false
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) { if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return plugin.targets.includes('remix') return plugin.targets.includes('remix')
} }

@ -10,7 +10,7 @@ export class RemixEngine extends Engine {
setPluginOption ({ name, kind }) { setPluginOption ({ name, kind }) {
if (kind === 'provider') return { queueTimeout: 60000 * 2 } if (kind === 'provider') return { queueTimeout: 60000 * 2 }
if (name === 'LearnEth') return { queueTimeout: 60000 } if (name === 'LearnEth') return { queueTimeout: 60000 }
if (name === 'dGitProvider') return { queueTimeout: 60000 * 30 } if (name === 'dgitApi') return { queueTimeout: 60000 * 4 }
if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed
if (name === 'hardhat') return { queueTimeout: 60000 * 4 } if (name === 'hardhat') return { queueTimeout: 60000 * 4 }
if (name === 'truffle') return { queueTimeout: 60000 * 4 } if (name === 'truffle') return { queueTimeout: 60000 * 4 }

@ -6,6 +6,7 @@ import git from 'isomorphic-git'
import { dialog } from "electron"; import { dialog } from "electron";
import http from 'isomorphic-git/http/web' import http from 'isomorphic-git/http/web'
import { gitProxy } from "../tools/git"; import { gitProxy } from "../tools/git";
import { remote } from "../types";
const profile: Profile = { const profile: Profile = {
name: 'isogit', name: 'isogit',
@ -329,9 +330,9 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
async clone(cmd: any) { async clone(cmd: any) {
if (this.gitIsInstalled) { if (this.gitIsInstalled) {
try{ try {
await gitProxy.clone(cmd.url, cmd.dir) await gitProxy.clone(cmd.url, cmd.dir)
}catch(e){ } catch (e) {
throw e throw e
} }
} else { } else {
@ -378,8 +379,9 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
if (!this.workingDir || this.workingDir === '') { if (!this.workingDir || this.workingDir === '') {
return [] return []
} }
let remotes = [] let remotes: remote[] = []
remotes = await git.listRemotes({ ...await this.getGitConfig() }) remotes = (await git.listRemotes({ ...await this.getGitConfig() })).map((remote) => { return { name: remote.remote, url: remote.url } }
)
return remotes return remotes
} }
@ -410,10 +412,10 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
for (const remote of remotes) { for (const remote of remotes) {
cmd = { cmd = {
...cmd, ...cmd,
remote: remote.remote remote: remote.name
} }
const remotebranches = (await git.listBranches(cmd)).map((branch) => { return { remote: remote.remote, name: branch } }) const remotebranches = (await git.listBranches(cmd)).map((branch) => { return { remote: remote.name, name: branch } })
branches = [...branches, ...remotebranches] branches = [...branches, ...remotebranches]
} }

@ -0,0 +1,9 @@
export type branch = {
name: string
remote: remote
}
export type remote = {
name: string
url: string
}

@ -5,9 +5,11 @@ import { PluginClient } from '@remixproject/plugin'
import { Contract, compileContract } from './compiler' import { Contract, compileContract } from './compiler'
import { ExampleContract } from '../components/VyperResult' import { ExampleContract } from '../components/VyperResult'
import EventEmitter from 'events' import EventEmitter from 'events'
import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from '@remix-api'
export type VyperComplierAddress = 'https://vyper2.remixproject.org/' | 'http://localhost:8000/' export type VyperComplierAddress = 'https://vyper2.remixproject.org/' | 'http://localhost:8000/'
export class RemixClient extends PluginClient { export class RemixClient extends PluginClient<any, CustomRemixApi> {
private client = createClient<Api, Readonly<RemixApi>>(this) private client = createClient<Api, Readonly<RemixApi>>(this)
compilerUrl: VyperComplierAddress = 'https://vyper2.remixproject.org/' compilerUrl: VyperComplierAddress = 'https://vyper2.remixproject.org/'
compilerOutput: any compilerOutput: any
@ -81,15 +83,23 @@ export class RemixClient extends PluginClient {
try { try {
// @ts-ignore // @ts-ignore
this.call('notification', 'toast', 'cloning Vyper-lang repository...') this.call('notification', 'toast', 'cloning Snekmate Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider')
await this.call( await this.call(
'dGitProvider', 'dgitApi',
'clone', 'clone',
{ url: 'https://github.com/vyperlang/vyper', token: null, branch: 'v0.3.10' }, {url: 'https://github.com/pcaversaccio/snekmate', token: null, branch: 'main', singleBranch: false, workspaceName: 'snekmate'},
// @ts-ignore )
(count === undefined || count === 0) ? 'vyper-lang' : `vyper-lang_${count}`
await this.call(
'dgitApi',
'checkout',
{
ref:'v0.0.5',
force: true,
refresh: true,
}
) )
this.call( this.call(
// @ts-ignore // @ts-ignore
'notification', 'notification',

@ -6,8 +6,8 @@ import App from './app/app'
const container = document.getElementById('root'); const container = document.getElementById('root');
if (container) { if (container) {
createRoot(container).render(<React.StrictMode> createRoot(container).render(
<App /> <App />
</React.StrictMode>); );
} }

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

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

@ -0,0 +1 @@
export * from './lib/remix-api'

@ -0,0 +1,11 @@
import { StatusEvents } from "@remixproject/plugin-utils"
export interface IConfigApi {
events: {
configChanged: () => void
} & StatusEvents,
methods: {
getAppParameter(key: string): Promise<any>,
setAppParameter(key: string, value: any): Promise<void>
}
}

@ -0,0 +1,12 @@
import { IFilePanel } from '@remixproject/plugin-api'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface IFilePanelApi {
events: IFilePanel['events'] & {
workspaceInitializationCompleted: () => void;
switchToWorkspace: (workspace: string) => Promise<void>;
} & StatusEvents
methods: IFilePanel['methods'] & {
}
}

@ -0,0 +1,10 @@
import { commitChange } from "@remix-ui/git";
import { IFileSystem } from "@remixproject/plugin-api"
// Extended interface with 'diff' method
export interface IExtendedFileSystem extends IFileSystem {
methods: IFileSystem['methods'] & {
diff(change: commitChange): Promise<void>
isGitRepo(): Promise<boolean>
};
}

@ -0,0 +1,11 @@
import { fileDecoration } from '@remix-ui/file-decorators'
import { StatusEvents } from '@remixproject/plugin-utils'
export interface IFileDecoratorApi {
events: {
} & StatusEvents
methods: {
clearFileDecorators(path?: string): void
setFileDecorators(decorators: fileDecoration[]): void
}
}

@ -0,0 +1,31 @@
import { ModalTypes } from "@remix-ui/app"
import { StatusEvents } from "@remixproject/plugin-utils"
export interface INotificationApi {
events: {
} & StatusEvents,
methods: {
toast(key: string): Promise<void>,
alert({
title,
message,
id
}:{
title: string,
message: string,
id: string
}): Promise<void>,
modal({
title,
message,
okLabel,
type
}:{
title: string,
message: string,
okLabel: string,
type: ModalTypes
}): Promise<void>,
}
}

@ -0,0 +1,13 @@
import { StatusEvents } from '@remixproject/plugin-utils'
export interface ISettings {
events: {
configChanged: () => void,
copilotChoiceUpdated: (isChecked: boolean) => void,
copilotChoiceChanged: (isChecked: boolean) => void,
} & StatusEvents
methods: {
getGithubAccessToken(): string
get(key: string): Promise<any>
}
}

@ -0,0 +1,22 @@
import { IGitApi } from "@remix-ui/git"
import { IRemixApi } from "@remixproject/plugin-api"
import { StatusEvents } from "@remixproject/plugin-utils"
import { IConfigApi } from "./plugins/config-api"
import { IFileDecoratorApi } from "./plugins/filedecorator-api"
import { IExtendedFileSystem } from "./plugins/fileSystem-api"
import { INotificationApi } from "./plugins/notification-api"
import { ISettings } from "./plugins/settings-api"
import { IFilePanelApi } from "./plugins/filePanel-api"
import { Plugin } from "@remixproject/engine"
export interface ICustomRemixApi extends IRemixApi {
dgitApi: IGitApi
config: IConfigApi
notification: INotificationApi
settings: ISettings
fileDecorator: IFileDecoratorApi
fileManager: IExtendedFileSystem
filePanel: IFilePanelApi
}
export declare type CustomRemixApi = Readonly<ICustomRemixApi>

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

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

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

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

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

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

@ -178,46 +178,48 @@ const RemixApp = (props: IRemixAppUi) => {
<OriginWarning></OriginWarning> <OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady} okFn={() => setShowEnterDialog(true)}></MatomoDialog> <MatomoDialog hide={!appReady} okFn={() => setShowEnterDialog(true)}></MatomoDialog>
{showEnterDialog && <EnterDialog handleUserChoice={(type) => handleUserChosenType(type)}></EnterDialog>} {showEnterDialog && <EnterDialog handleUserChoice={(type) => handleUserChosenType(type)}></EnterDialog>}
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE"> <div className='d-flex flex-column'>
<div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light"> <div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
{props.app.menuicons.render()} <div id="icon-panel" data-id="remixIdeIconPanel" className="custom_icon_panel iconpanel bg-light">
</div> {props.app.menuicons.render()}
<div </div>
ref={sidePanelRef} <div
id="side-panel" ref={sidePanelRef}
data-id="remixIdeSidePanel" id="side-panel"
className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`} data-id="remixIdeSidePanel"
> className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}
{props.app.sidePanel.render()} >
</div> {props.app.sidePanel.render()}
<DragBar </div>
resetTrigger={resetLeftTrigger}
maximiseTrigger={maximiseLeftTrigger}
minWidth={285}
refObject={sidePanelRef}
hidden={hideSidePanel}
setHideStatus={setHideSidePanel}
layoutPosition='left'
></DragBar>
<div id="main-panel" data-id="remixIdeMainPanel" className="mainpanel d-flex">
<RemixUIMainPanel layout={props.app.layout}></RemixUIMainPanel>
</div>
<div id="pinned-panel" ref={pinnedPanelRef} data-id="remixIdePinnedPanel" className={`flex-row-reverse pinnedpanel border-right border-left ${hidePinnedPanel ? 'd-none' : 'd-flex'}`}>
{props.app.pinnedPanel.render()}
</div>
{
!hidePinnedPanel &&
<DragBar <DragBar
resetTrigger={resetRightTrigger} resetTrigger={resetLeftTrigger}
maximiseTrigger={maximiseRightTrigger} maximiseTrigger={maximiseLeftTrigger}
minWidth={331} minWidth={285}
refObject={pinnedPanelRef} refObject={sidePanelRef}
hidden={hidePinnedPanel} hidden={hideSidePanel}
setHideStatus={setHidePinnedPanel} setHideStatus={setHideSidePanel}
layoutPosition='right' layoutPosition='left'
></DragBar> ></DragBar>
} <div id="main-panel" data-id="remixIdeMainPanel" className="mainpanel d-flex">
<div>{props.app.hiddenPanel.render()}</div> <RemixUIMainPanel layout={props.app.layout}></RemixUIMainPanel>
</div>
<div id="pinned-panel" ref={pinnedPanelRef} data-id="remixIdePinnedPanel" className={`flex-row-reverse pinnedpanel border-right border-left ${hidePinnedPanel ? 'd-none' : 'd-flex'}`}>
{props.app.pinnedPanel.render()}
</div>
{
!hidePinnedPanel &&
<DragBar
resetTrigger={resetRightTrigger}
maximiseTrigger={maximiseRightTrigger}
minWidth={331}
refObject={pinnedPanelRef}
hidden={hidePinnedPanel}
setHideStatus={setHidePinnedPanel}
layoutPosition='right'
></DragBar>
}
<div>{props.app.hiddenPanel.render()}</div>
</div>
<div className="statusBar fixed-bottom"> <div className="statusBar fixed-bottom">
{props.app.statusBar.render()} {props.app.statusBar.render()}
</div> </div>

@ -1,20 +1,21 @@
import { monacoTypes } from '@remix-ui/editor'; import { monacoTypes } from '@remix-ui/editor';
import { commitChange } from '@remix-ui/git';
export interface Action { export interface Action {
type: string; type: string;
payload: Record<string, any> payload: Record<string, any>
monaco: any, monaco: any,
editor: any editors: any[]
} }
export const initialState = {} export const initialState = {}
export const reducerActions = (models = initialState, action: Action) => { export const reducerActions = (models = initialState, action: Action) => {
const monaco = action.monaco const monaco = action.monaco
const editor = action.editor const editors = action.editors as any[]
switch (action.type) { switch (action.type) {
case 'ADD_MODEL': { case 'ADD_MODEL': {
if (!editor) return models if (!editors) return models
const uri = action.payload.uri const uri = action.payload.uri
const value = action.payload.value const value = action.payload.value
const language = action.payload.language const language = action.payload.language
@ -22,6 +23,7 @@ export const reducerActions = (models = initialState, action: Action) => {
if (models[uri]) return models // already existing if (models[uri]) return models // already existing
models[uri] = { language, uri, readOnly } models[uri] = { language, uri, readOnly }
let model let model
try { try {
model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri)) model = monaco.editor.createModel(value, language, monaco.Uri.parse(uri))
} catch (e) { } catch (e) {
@ -38,8 +40,12 @@ export const reducerActions = (models = initialState, action: Action) => {
delete models[uri] delete models[uri]
return models return models
} }
case 'ADD_DIFF': {
if (!editors) return models
return models
}
case 'SET_VALUE': { case 'SET_VALUE': {
if (!editor) return models if (!editors) return models
const uri = action.payload.uri const uri = action.payload.uri
const value = action.payload.value const value = action.payload.value
const model = models[uri]?.model const model = models[uri]?.model
@ -49,15 +55,19 @@ export const reducerActions = (models = initialState, action: Action) => {
return models return models
} }
case 'REVEAL_LINE': { case 'REVEAL_LINE': {
if (!editor) return models if (!editors) return models
const line = action.payload.line const line = action.payload.line
const column = action.payload.column const column = action.payload.column
editor.revealLine(line)
editor.setPosition({ column, lineNumber: line }) editors.map((editor) => {
editor.revealLine(line)
editor.setPosition({ column, lineNumber: line })
})
return models return models
} }
case 'REVEAL_RANGE': { case 'REVEAL_RANGE': {
if (!editor) return models if (!editors) return models
const range: monacoTypes.IRange = { const range: monacoTypes.IRange = {
startLineNumber: action.payload.startLineNumber + 1, startLineNumber: action.payload.startLineNumber + 1,
startColumn: action.payload.startColumn, startColumn: action.payload.startColumn,
@ -66,48 +76,60 @@ export const reducerActions = (models = initialState, action: Action) => {
} }
// reset to start of line // reset to start of line
if (action.payload.startColumn < 100) { if (action.payload.startColumn < 100) {
editor.revealRange({ editors.map(editor => editor.revealRange({
startLineNumber: range.startLineNumber, startLineNumber: range.startLineNumber,
startColumn: 1, startColumn: 1,
endLineNumber: range.endLineNumber, endLineNumber: range.endLineNumber,
endColumn: 1 endColumn: 1
}) }))
} else { } else {
editor.revealRangeInCenter(range) editors.map(editor => editor.revealRangeInCenter(range))
} }
return models return models
} }
case 'FOCUS': { case 'FOCUS': {
if (!editor) return models if (!editors) return models
editor.focus() editors.map(editor => editor.focus())
return models return models
} }
case 'SET_FONTSIZE': { case 'SET_FONTSIZE': {
if (!editor) return models if (!editors) return models
const size = action.payload.size const size = action.payload.size
if (size === 1) { editors.map((editor) => {
editor.trigger('keyboard', 'editor.action.fontZoomIn', {}); if (size === 1) {
} else { editor.trigger('keyboard', 'editor.action.fontZoomIn', {});
editor.trigger('keyboard', 'editor.action.fontZoomOut', {}); } else {
} editor.trigger('keyboard', 'editor.action.fontZoomOut', {});
}
})
return models return models
} }
case 'SET_WORDWRAP': { case 'SET_WORDWRAP': {
if (!editor) return models if (!editors) return models
const wrap = action.payload.wrap const wrap = action.payload.wrap
editor.updateOptions({ wordWrap: wrap ? 'on' : 'off' }) editors.map(editor =>
editor.updateOptions({ wordWrap: wrap ? 'on' : 'off' }))
return models return models
} }
} }
} }
export const reducerListener = (plugin, dispatch, monaco, editor, events) => { export const reducerListener = (plugin, dispatch, monaco, editors: any[], events) => {
plugin.on('editor', 'addModel', (value, language, uri, readOnly) => { plugin.on('editor', 'addModel', (value, language, uri, readOnly) => {
dispatch({ dispatch({
type: 'ADD_MODEL', type: 'ADD_MODEL',
payload: { uri, value, language, readOnly, events }, payload: { uri, value, language, readOnly, events },
monaco, monaco,
editor editors
})
})
plugin.on('editor', 'addDiff', (value: commitChange) => {
dispatch({
type: 'ADD_DIFF',
payload: { value },
monaco,
editors
}) })
}) })
@ -116,7 +138,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'DISPOSE_MODEL', type: 'DISPOSE_MODEL',
payload: { uri }, payload: { uri },
monaco, monaco,
editor editors
}) })
}) })
@ -125,7 +147,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'SET_VALUE', type: 'SET_VALUE',
payload: { uri, value }, payload: { uri, value },
monaco, monaco,
editor editors
}) })
}) })
@ -134,7 +156,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'REVEAL_LINE', type: 'REVEAL_LINE',
payload: { line, column }, payload: { line, column },
monaco, monaco,
editor editors
}) })
}) })
@ -148,7 +170,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
endColumn endColumn
}, },
monaco, monaco,
editor editors
}) })
}) })
@ -157,7 +179,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'FOCUS', type: 'FOCUS',
payload: {}, payload: {},
monaco, monaco,
editor editors
}) })
}) })
@ -166,7 +188,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'SET_FONTSIZE', type: 'SET_FONTSIZE',
payload: { size }, payload: { size },
monaco, monaco,
editor editors
}) })
}) })
@ -175,7 +197,7 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
type: 'SET_WORDWRAP', type: 'SET_WORDWRAP',
payload: { wrap }, payload: { wrap },
monaco, monaco,
editor editors
}) })
}) })
} }

@ -4,6 +4,7 @@ import { CompletionTimer } from './completionTimer';
import axios, { AxiosResponse } from 'axios' import axios, { AxiosResponse } from 'axios'
import { slice } from 'lodash'; import { slice } from 'lodash';
import { activateService } from '@remixproject/plugin-utils';
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
const controller = new AbortController(); const controller = new AbortController();
@ -14,6 +15,9 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
props: EditorUIProps props: EditorUIProps
monaco: any monaco: any
completionEnabled: boolean completionEnabled: boolean
task: string
currentCompletion
constructor(props: any, monaco: any) { constructor(props: any, monaco: any) {
this.props = props this.props = props
this.monaco = monaco this.monaco = monaco
@ -28,6 +32,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
const lineRange = model.getFullModelRange().setStartPosition(lineNumber, 1).setEndPosition(lineNumber + 1, 1); const lineRange = model.getFullModelRange().setStartPosition(lineNumber, 1).setEndPosition(lineNumber + 1, 1);
return model.getValueInRange(lineRange); return model.getValueInRange(lineRange);
} }
// get text before the position of the completion // get text before the position of the completion
const word = model.getValueInRange({ const word = model.getValueInRange({
startLineNumber: 1, startLineNumber: 1,
@ -65,6 +70,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
// use the code generation model, only take max 1000 word as context // use the code generation model, only take max 1000 word as context
this.props.plugin.call('terminal', 'log', { type: 'aitypewriterwarning', value: 'Solcoder - generating code for following comment: ' + ask.replace('///', '') }) this.props.plugin.call('terminal', 'log', { type: 'aitypewriterwarning', value: 'Solcoder - generating code for following comment: ' + ask.replace('///', '') })
this.task = 'code_generation'
const data = await this.props.plugin.call('solcoder', 'code_generation', word) const data = await this.props.plugin.call('solcoder', 'code_generation', word)
const parsedData = data[0].trimStart() //JSON.parse(data).trimStart() const parsedData = data[0].trimStart() //JSON.parse(data).trimStart()
@ -103,15 +109,15 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
if (word.replace(/ +$/, '').endsWith('\n')){ if (word.replace(/ +$/, '').endsWith('\n')){
// Code insertion // Code insertion
try { try {
this.task = 'code_insertion'
const output = await this.props.plugin.call('solcoder', 'code_insertion', word, word_after) const output = await this.props.plugin.call('solcoder', 'code_insertion', word, word_after)
const generatedText = output[0] // no need to clean it. should already be const generatedText = output[0] // no need to clean it. should already be
const item: monacoTypes.languages.InlineCompletion = { const item: monacoTypes.languages.InlineCompletion = {
insertText: generatedText insertText: generatedText
}; };
this.completionEnabled = false this.completionEnabled = false
const handleCompletionTimer = new CompletionTimer(5000, () => { this.completionEnabled = true }); const handleCompletionTimer = new CompletionTimer(1000, () => { this.completionEnabled = true });
handleCompletionTimer.start() handleCompletionTimer.start()
return { return {
@ -127,6 +133,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
let result let result
try { try {
// Code completion // Code completion
this.task = 'code_completion'
const output = await this.props.plugin.call('solcoder', 'code_completion', word) const output = await this.props.plugin.call('solcoder', 'code_completion', word)
const generatedText = output[0] const generatedText = output[0]
let clean = generatedText let clean = generatedText
@ -168,14 +175,15 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
} }
handleItemDidShow?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, updatedInsertText: string): void { handleItemDidShow?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, updatedInsertText: string): void {
this.currentCompletion = { 'item':item, 'task':this.task }
_paq.push(['trackEvent', 'ai', 'solcoder', this.task + '_did_show'])
} }
handlePartialAccept?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, acceptedCharacters: number): void { handlePartialAccept?(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>, item: monacoTypes.languages.InlineCompletion, acceptedCharacters: number): void {
_paq.push(['trackEvent', 'ai', 'solcoder', this.task + '_partial_accept'])
} }
freeInlineCompletions(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>): void { freeInlineCompletions(completions: monacoTypes.languages.InlineCompletions<monacoTypes.languages.InlineCompletion>): void {
} }
groupId?: string; groupId?: string;
yieldsToGroupIds?: string[]; yieldsToGroupIds?: string[];
toString?(): string { toString?(): string {

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl'
import { isArray } from 'lodash' import { isArray } from 'lodash'
import Editor, { loader, Monaco } from '@monaco-editor/react' import Editor, { DiffEditor, loader, Monaco } from '@monaco-editor/react'
import { AlertModal } from '@remix-ui/app' import { AlertModal } from '@remix-ui/app'
import { ConsoleLogs, QueryParams } from '@remix-project/remix-lib' import { ConsoleLogs, QueryParams } from '@remix-project/remix-lib'
import { reducerActions, reducerListener, initialState } from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
@ -137,6 +137,8 @@ export interface EditorUIProps {
activated: boolean activated: boolean
themeType: string themeType: string
currentFile: string currentFile: string
currentDiffFile: string
isDiff: boolean
events: { events: {
onBreakPointAdded: (file: string, line: number) => void onBreakPointAdded: (file: string, line: number) => void
onBreakPointCleared: (file: string, line: number) => void onBreakPointCleared: (file: string, line: number) => void
@ -149,6 +151,8 @@ export interface EditorUIProps {
export const EditorUI = (props: EditorUIProps) => { export const EditorUI = (props: EditorUIProps) => {
const intl = useIntl() const intl = useIntl()
const [, setCurrentBreakpoints] = useState({}) const [, setCurrentBreakpoints] = useState({})
const [isDiff, setIsDiff] = useState(false)
const [isSplit, setIsSplit] = useState(true)
const defaultEditorValue = ` const defaultEditorValue = `
\t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____ \t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____
\t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____| \t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____|
@ -166,13 +170,15 @@ export const EditorUI = (props: EditorUIProps) => {
\t\t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.importantLinks.text1' })}: https://remix-project.org/\n \t\t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.importantLinks.text1' })}: https://remix-project.org/\n
\t\t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.importantLinks.text2' })}: https://remix-ide.readthedocs.io/en/latest/\n \t\t\t\t\t\t\t\t${intl.formatMessage({ id: 'editor.importantLinks.text2' })}: https://remix-ide.readthedocs.io/en/latest/\n
\t\t\t\t\t\t\t\tGithub: https://github.com/ethereum/remix-project\n \t\t\t\t\t\t\t\tGithub: https://github.com/ethereum/remix-project\n
\t\t\t\t\t\t\t\tGitter: https://gitter.im/ethereum/remix\n \t\t\t\t\t\t\t\tDiscord: https://discord.gg/mh9hFCKkEq\n
\t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n \t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n \t\t\t\t\t\t\t\tX: https://x.com/ethereumremix\n
` `
const pasteCodeRef = useRef(false) const pasteCodeRef = useRef(false)
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null) const monacoRef = useRef<Monaco>(null)
const diffEditorRef = useRef<any>(null)
const currentFunction = useRef('') const currentFunction = useRef('')
const currentFileRef = useRef('') const currentFileRef = useRef('')
const currentUrlRef = useRef('') const currentUrlRef = useRef('')
@ -330,11 +336,19 @@ export const EditorUI = (props: EditorUIProps) => {
}) })
useEffect(() => { useEffect(() => {
if (!editorRef.current || !props.currentFile) return if (!(editorRef.current || diffEditorRef.current ) || !props.currentFile) return
currentFileRef.current = props.currentFile currentFileRef.current = props.currentFile
props.plugin.call('fileManager', 'getUrlFromPath', currentFileRef.current).then((url) => (currentUrlRef.current = url.file)) props.plugin.call('fileManager', 'getUrlFromPath', currentFileRef.current).then((url) => (currentUrlRef.current = url.file))
const file = editorModelsState[props.currentFile] const file = editorModelsState[props.currentFile]
props.isDiff && diffEditorRef && diffEditorRef.current && diffEditorRef.current.setModel({
original: editorModelsState[props.currentDiffFile].model,
modified: file.model
})
props.isDiff && diffEditorRef.current.getModifiedEditor().updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly })
editorRef.current.setModel(file.model) editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ editorRef.current.updateOptions({
readOnly: editorModelsState[props.currentFile].readOnly, readOnly: editorModelsState[props.currentFile].readOnly,
@ -352,7 +366,9 @@ export const EditorUI = (props: EditorUIProps) => {
} else if (file.language === 'toml') { } else if (file.language === 'toml') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-toml') monacoRef.current.editor.setModelLanguage(file.model, 'remix-toml')
} }
}, [props.currentFile]) }, [props.currentFile, props.isDiff])
const inlineCompletionProvider = new RemixInLineCompletionProvider(props, monacoRef.current)
const convertToMonacoDecoration = (decoration: lineText | sourceAnnotation | sourceMarker, typeOfDecoration: string) => { const convertToMonacoDecoration = (decoration: lineText | sourceAnnotation | sourceMarker, typeOfDecoration: string) => {
if (typeOfDecoration === 'sourceAnnotationsPerFile') { if (typeOfDecoration === 'sourceAnnotationsPerFile') {
@ -549,6 +565,7 @@ export const EditorUI = (props: EditorUIProps) => {
props.editorAPI.getValue = (uri: string) => { props.editorAPI.getValue = (uri: string) => {
if (!editorRef.current) return if (!editorRef.current) return
const model = editorModelsState[uri]?.model const model = editorModelsState[uri]?.model
if (model) { if (model) {
return model.getValue() return model.getValue()
@ -618,10 +635,21 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
function setReducerListener() {
if (diffEditorRef.current && diffEditorRef.current.getModifiedEditor() && editorRef.current){
reducerListener(props.plugin, dispatch, monacoRef.current, [diffEditorRef.current.getModifiedEditor(), editorRef.current], props.events)
}
}
function handleDiffEditorDidMount(editor: any) {
diffEditorRef.current = editor
setReducerListener()
}
function handleEditorDidMount(editor) { function handleEditorDidMount(editor) {
editorRef.current = editor editorRef.current = editor
defineAndSetTheme(monacoRef.current) defineAndSetTheme(monacoRef.current)
reducerListener(props.plugin, dispatch, monacoRef.current, editorRef.current, props.events) setReducerListener()
props.events.onEditorMounted() props.events.onEditorMounted()
editor.onMouseUp((e) => { editor.onMouseUp((e) => {
// see https://microsoft.github.io/monaco-editor/typedoc/enums/editor.MouseTargetType.html // see https://microsoft.github.io/monaco-editor/typedoc/enums/editor.MouseTargetType.html
@ -674,6 +702,17 @@ export const EditorUI = (props: EditorUIProps) => {
} }
}) })
editor.onDidChangeModelContent((e) => {
if (inlineCompletionProvider.currentCompletion) {
const changes = e.changes;
// Check if the change matches the current completion
if (changes.some(change => change.text === inlineCompletionProvider.currentCompletion.item.insertText)) {
_paq.push(['trackEvent', 'ai', 'solcoder', inlineCompletionProvider.currentCompletion.task + '_accepted'])
inlineCompletionProvider.currentCompletion = null;
}
}
});
// add context menu items // add context menu items
const zoominAction = { const zoominAction = {
id: 'zoomIn', id: 'zoomIn',
@ -977,7 +1016,7 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco)) monacoRef.current.languages.registerReferenceProvider('remix-solidity', new RemixReferenceProvider(props, monaco))
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco)) monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco)) monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
monacoRef.current.languages.registerInlineCompletionsProvider('remix-solidity', new RemixInLineCompletionProvider(props, monaco)) monacoRef.current.languages.registerInlineCompletionsProvider('remix-solidity', inlineCompletionProvider)
monaco.languages.registerCodeActionProvider('remix-solidity', new RemixCodeActionProvider(props, monaco)) monaco.languages.registerCodeActionProvider('remix-solidity', new RemixCodeActionProvider(props, monaco))
loadTypes(monacoRef.current) loadTypes(monacoRef.current)
@ -985,8 +1024,22 @@ export const EditorUI = (props: EditorUIProps) => {
return ( return (
<div className="w-100 h-100 d-flex flex-column-reverse"> <div className="w-100 h-100 d-flex flex-column-reverse">
<DiffEditor
originalLanguage={'remix-solidity'}
modifiedLanguage={'remix-solidity'}
original={''}
modified={''}
onMount={handleDiffEditorDidMount}
options={{ readOnly: false, renderSideBySide: isSplit }}
width='100%'
height={props.isDiff ? '100%' : '0%'}
className={props.isDiff ? "d-block" : "d-none"}
/>
<Editor <Editor
width="100%" width="100%"
height={props.isDiff ? '0%' : '100%'}
path={props.currentFile} path={props.currentFile}
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'} language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount} onMount={handleEditorDidMount}
@ -999,6 +1052,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
}} }}
defaultValue={defaultEditorValue} defaultValue={defaultEditorValue}
className={props.isDiff ? "d-none" : "d-block"}
/> />
{editorModelsState[props.currentFile]?.readOnly && ( {editorModelsState[props.currentFile]?.readOnly && (
<span className="pl-4 h6 mb-0 w-100 alert-info position-absolute bottom-0 end-0"> <span className="pl-4 h6 mb-0 w-100 alert-info position-absolute bottom-0 end-0">

@ -0,0 +1,70 @@
import React, { useEffect, useState } from 'react'
import { gitActionsContext, pluginActionsContext } from '../state/context'
import { ReadCommitResult } from "isomorphic-git"
import { gitPluginContext } from './gitui'
export const BranchHeader = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const pluginActions = React.useContext(pluginActionsContext)
const [changed, setChanged] = useState(false)
const [isDetached, setIsDetached] = useState(false)
const [latestCommit, setLatestCommit] = useState<ReadCommitResult>(null)
useEffect(() => {
if (context.currentBranch) {
actions.getBranchDifferences(context.currentBranch, null, context)
}
if (!context.currentBranch || (context.currentBranch && context.currentBranch.name === '')) {
if (context.currentHead === '') {
setIsDetached(false)
} else {
setIsDetached(true)
}
} else {
setIsDetached(false)
}
setLatestCommit(null)
if (context.currentHead !== '') {
if (context.commits && context.commits.length > 0) {
const commit = context.commits.find(commit => commit.oid === context.currentHead)
if (commit) {
setLatestCommit(commit)
}
}
}
}, [context.currentBranch, context.commits, context.branches, context.remotes, context.currentHead])
useEffect(() => {
if (context.fileStatusResult) {
const total = context.allchangesnotstaged.length
const badges = total + context.staged.length
setChanged((context.deleted.length > 0 || context.staged.length > 0 || context.untracked.length > 0 || context.modified.length > 0))
}
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted])
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.`,
title: 'Warning'
})
}
return (<>
<div className='text-sm w-100'>
<div className='text-secondary long-and-truncated'>
<i className="fa fa-code-branch mr-1 pl-2"></i>
{changed ? '*' : ''}{context.currentBranch && context.currentBranch.name}
</div>
{latestCommit ?
<div className='text-secondary long-and-truncated'>
{latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : ''}
</div> : null}
{isDetached ?
<div className='text-warning long-and-truncated'>
You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i>
</div> : null}
</div>
<hr></hr>
</>)
}

@ -0,0 +1,147 @@
import React, { useEffect } from "react"
import { useState } from "react"
import { gitActionsContext } from "../../state/context"
import { gitPluginContext } from "../gitui"
import { faArrowDown, faArrowUp, faCheck, faCloudArrowUp, faSync } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { syncStateContext } from "./sourceControlBase";
enum buttonStateValues {
Commit,
Sync = 1,
PublishBranch = 2
}
export const CommitMessage = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const syncState = React.useContext(syncStateContext)
const [buttonState, setButtonState] = useState<buttonStateValues>(buttonStateValues.Commit)
const [message, setMessage] = useState({ value: '' })
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMessage({ value: e.currentTarget.value })
}
const commit = async () => {
if (context.staged.length === 0 && context.allchangesnotstaged.length == 0) return
if (context.staged.length === 0)
await actions.addall(context.allchangesnotstaged)
await actions.commit(message.value)
setMessage({ value: '' })
}
const getRemote = () => {
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const sync = async () => {
await actions.pull({
remote: getRemote(),
ref: context.currentBranch
})
await actions.push({
remote: getRemote(),
ref: context.currentBranch
})
await actions.pull({
remote: getRemote(),
ref: context.currentBranch
})
}
const commitNotAllowed = () => {
return context.canCommit === false || message.value === "" || (context.staged.length === 0 && context.allchangesnotstaged.length == 0)
}
const commitMessagePlaceholder = () => {
if (context.currentBranch === undefined || context.currentBranch.name === "")
return `message`
return `message ( commit on ${context.currentBranch.name} )`
}
const syncEnabled = () => {
return syncState.commitsAhead.length > 0 || syncState.commitsBehind.length > 0
}
const upDownArrows = () => {
return (
<>
{syncState.commitsBehind && syncState.commitsBehind.length ? <>{syncState.commitsBehind.length}<FontAwesomeIcon icon={faArrowDown} className="ml-1" /></> : null}
{syncState.commitsAhead && syncState.commitsAhead.length ? <>{syncState.commitsAhead.length}<FontAwesomeIcon icon={faArrowUp} className="ml-1" /></> : null}
</>
)
}
const publishEnabled = () => {
const remoteEquivalentBranch = context.branches.find((b) => b.name === context.currentBranch.name && b.remote)
return remoteEquivalentBranch === undefined && getRemote() !== null
}
const publishBranch = async () => {
if (context.currentBranch === undefined || context.currentBranch.name === "")
return
await actions.push({
remote: getRemote(),
ref: context.currentBranch
})
await actions.fetch({
remote: getRemote(),
ref: context.currentBranch,
singleBranch: false,
relative: true
})
}
const messageEnabled = () => {
return context.canCommit && (context.allchangesnotstaged.length > 0 || context.staged.length > 0)
}
const setButtonStateValues = () => {
if (!commitNotAllowed() || context.allchangesnotstaged.length > 0 || context.staged.length > 0) {
if (context.allchangesnotstaged.length == 0 && context.staged.length == 0 && message.value === "" && publishEnabled()) {
setButtonState(buttonStateValues.PublishBranch)
return
}
setButtonState(buttonStateValues.Commit)
return
}
if (syncEnabled()) {
setButtonState(buttonStateValues.Sync)
return
}
if (publishEnabled()) {
setButtonState(buttonStateValues.PublishBranch)
return
}
setButtonState(buttonStateValues.Commit)
}
useEffect(() => {
setButtonStateValues()
}, [context.canCommit, context.staged, context.allchangesnotstaged, context.currentBranch, syncState.commitsAhead, syncState.commitsBehind, message.value])
return (
<>
<div className="form-group">
<input placeholder={commitMessagePlaceholder()} data-id='commitMessage' disabled={!messageEnabled()} className="form-control" type="text" onChange={handleChange} value={message.value} />
</div>
<button data-id='commitButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Commit ? '' : 'd-none'}`} disabled={commitNotAllowed()} onClick={async () => await commit()} >
<FontAwesomeIcon icon={faCheck} className="mr-1" />
Commit
</button>
<button data-id='syncButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Sync ? '' : 'd-none'}`} disabled={!syncEnabled()} onClick={async () => await sync()} >
<FontAwesomeIcon icon={faSync} className="mr-1" aria-hidden="true" />
Sync Changes {upDownArrows()}
</button>
<button data-id='publishBranchButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.PublishBranch ? '' : 'd-none'}`} onClick={async () => await publishBranch()} >
<FontAwesomeIcon icon={faCloudArrowUp} className="mr-1" aria-hidden="true" />
Publish Branch
</button>
<hr></hr>
</>
);
}

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

@ -0,0 +1,90 @@
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { CustomTooltip } from "@remix-ui/helper"
import { ReadCommitResult } from "isomorphic-git"
import React, { createContext, useEffect, useState } from "react"
import { FormattedMessage } from "react-intl"
import { gitActionsContext } from "../../state/context"
import { branch, remote } from "../../types"
import { gitPluginContext } from "../gitui"
import GitUIButton from "./gituibutton"
interface SourceControlButtonsProps {
remote?: remote,
branch?: branch,
children: React.ReactNode
}
export const syncStateContext = createContext<{
commitsAhead: ReadCommitResult[],
commitsBehind: ReadCommitResult[]
branch: branch,
remote: remote
}>
({ commitsAhead: [], commitsBehind: [], branch: undefined, remote: undefined })
export const SourceControlBase = (props: SourceControlButtonsProps) => {
const [branch, setBranch] = useState(props.branch)
const [remote, setRemote] = useState(props.remote)
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [commitsAhead, setCommitsAhead] = useState<ReadCommitResult[]>([])
const [commitsBehind, setCommitsBehind] = useState<ReadCommitResult[]>([])
useEffect(() => {
setDefaultRemote()
if (remote && branch && context.branchDifferences && context.branchDifferences[`${remote.name}/${branch.name}`]) {
setCommitsAhead(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits)
setCommitsBehind(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits)
} else {
setCommitsAhead([])
setCommitsBehind([])
}
}, [context.branchDifferences, context.currentBranch, branch, remote])
const setDefaultRemote = () => {
if (context.remotes.length > 0) {
// find remote called origin
const origin = context.remotes.find(remote => remote.name === 'origin')
if (origin) {
setRemote(origin)
} else {
setRemote(context.remotes[0])
}
return origin
}
return null
}
useEffect(() => {
if (!props.branch) {
setBranch(context.currentBranch)
}
if (!props.remote) {
setRemote(context.defaultRemote)
} else {
setDefaultRemote()
}
}, [])
useEffect(() => {
if (!props.branch) {
setBranch(context.currentBranch)
}
if (!props.remote) {
setRemote(context.defaultRemote)
} else {
setDefaultRemote()
}
}, [context.defaultRemote, context.currentBranch])
return (<>
<syncStateContext.Provider value={{ commitsAhead, commitsBehind, branch, remote }}>
{props.children}
</syncStateContext.Provider>
</>)
}

@ -0,0 +1,98 @@
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { CustomTooltip } from "@remix-ui/helper"
import React, { useEffect, useState } from "react"
import { FormattedMessage } from "react-intl"
import { gitActionsContext } from "../../state/context"
import { branch, remote } from "../../types"
import { gitPluginContext } from "../gitui"
import GitUIButton from "./gituibutton"
import { syncStateContext } from "./sourceControlBase"
export const SourceControlButtons = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const syncState = React.useContext(syncStateContext)
const [branch, setBranch] = useState<branch>(syncState.branch)
const [remote, setRemote] = useState<remote>(syncState.remote)
const getRemote = () => {
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const getRemoteName = () => {
return getRemote() ? getRemote().name : ''
}
const pull = async () => {
await actions.pull({
remote: getRemote(),
ref: branch ? branch : context.currentBranch
})
}
const push = async () => {
await actions.push({
remote: getRemote(),
ref: branch ? branch : context.currentBranch
})
await actions.fetch({
remote: getRemote(),
ref: branch ? branch : context.currentBranch,
relative: false,
depth: 1,
singleBranch: true
})
}
const sync = async () => {
await pull()
await push()
}
const refresh = async() => {
await actions.getFileStatusMatrix(null)
await actions.gitlog()
}
const buttonsDisabled = () => {
return (!context.upstream) || context.remotes.length === 0
}
const getTooltipText = (id: string) => {
if (buttonsDisabled()) return <FormattedMessage id="git.noremote" />
return <><FormattedMessage id={id} /> {getRemoteName()}</>
}
return (
<span className='d-flex justify-content-end align-items-center'>
<CustomTooltip tooltipText={getTooltipText('git.pull')}>
<GitUIButton data-id='sourcecontrol-button-pull' disabledCondition={buttonsDisabled()} onClick={pull} className='btn btn-sm pl-0 pr-2'>
<div className="d-flex align-items-baseline">
{syncState.commitsBehind.length ? <div className="badge badge-pill pl-0">
{syncState.commitsBehind.length}
</div> : null}
<FontAwesomeIcon icon={faArrowDown} className="" />
</div>
</GitUIButton>
</CustomTooltip>
<CustomTooltip tooltipText={getTooltipText('git.push')}>
<GitUIButton data-id='sourcecontrol-button-push' disabledCondition={buttonsDisabled()} onClick={push} className='btn btn-sm pl-0 pr-2'>
<div className="d-flex align-items-baseline">
{syncState.commitsAhead.length ? <div className="badge badge-pill pl-0">
{syncState.commitsAhead.length}
</div> : null}
<FontAwesomeIcon icon={faArrowUp} className="" />
</div>
</GitUIButton>
</CustomTooltip>
<CustomTooltip tooltipText={getTooltipText('git.sync')}>
<GitUIButton data-id='sourcecontrol-button-sync' disabledCondition={buttonsDisabled()} onClick={sync} className='btn btn-sm pl-0 pr-2'><FontAwesomeIcon icon={faArrowsUpDown} className="" /></GitUIButton>
</CustomTooltip>
<CustomTooltip tooltipText={<FormattedMessage id="git.refresh" />}>
<GitUIButton onClick={refresh} className='btn btn-sm'><FontAwesomeIcon icon={faArrowRotateRight} className="" /></GitUIButton>
</CustomTooltip>
</span>
)
}

@ -0,0 +1,12 @@
import React, { useEffect, useState } from 'react'
export const Disabled = () => {
return (
<div data-id='disabled' className='text-sm w-100 alert alert-warning'>
Git is currently disabled.<br></br>
If you are using RemixD you can use git on the terminal.<br></br>
</div>
)
}

@ -0,0 +1,51 @@
import React, { useState, useCallback, useEffect } from 'react';
import Select from 'react-select';
import { gitActionsContext } from '../../state/context';
import { selectStyles, selectTheme } from '../../types/styles';
import { gitPluginContext } from '../gitui';
interface BranchySelectProps {
select: (branch: { name: string }) => void;
}
export const BranchSelect = (props: BranchySelectProps) => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [branchOptions, setBranchOptions] = useState<any>([]);
useEffect(() => {
if (context.remoteBranches && context.remoteBranches.length > 0) {
const options = context.remoteBranches
&& context.remoteBranches.length > 0
&& context.remoteBranches.map(branch => {
return { value: branch.name, label: branch.name }
})
setBranchOptions(options)
} else {
setBranchOptions(null)
}
}, [context.remoteBranches])
const selectRemoteBranch = async (e: any) => {
if (!e || !e.value) {
props.select(null)
return
}
const value = e && e.value
props.select({ name: value.toString() })
}
return (<>{branchOptions && branchOptions.length ?
<Select
options={branchOptions}
className="mt-1"
id="branch-select"
onChange={(e: any) => selectRemoteBranch(e)}
theme={selectTheme}
styles={selectStyles}
isClearable={true}
placeholder="Type to search for a branch..."
/> : null}
</>)
}

@ -0,0 +1,127 @@
import React, { useEffect } from "react";
import { gitActionsContext, pluginActionsContext } from "../../state/context";
import { gitPluginContext } from "../gitui";
import axios from "axios";
import { CopyToClipboard } from "@remix-ui/clipboard";
import { Card } from "react-bootstrap";
export const GetDeviceCode = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const pluginActions = React.useContext(pluginActionsContext)
const [gitHubResponse, setGitHubResponse] = React.useState<any>(null)
const [authorized, setAuthorized] = React.useState<boolean>(false)
const getDeviceCodeFromGitHub = async () => {
setAuthorized(false)
// Send a POST request
const response = await axios({
method: 'post',
url: 'https://github.remixproject.org/login/device/code',
data: {
client_id: '2795b4e41e7197d6ea11',
scope: 'repo gist user:email read:user'
},
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
});
// convert response to json
const githubrespone = await response.data;
setGitHubResponse(githubrespone)
}
const connectApp = async () => {
// poll https://github.com/login/oauth/access_token
const accestokenresponse = await axios({
method: 'post',
url: 'https://github.remixproject.org/login/oauth/access_token',
data: {
client_id: '2795b4e41e7197d6ea11',
device_code: gitHubResponse.device_code,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
},
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
});
// convert response to json
const response = await accestokenresponse.data;
if (response.access_token) {
setAuthorized(true)
await pluginActions.saveToken(response.access_token)
await actions.loadGitHubUserFromToken()
}
}
const disconnect = async () => {
setAuthorized(false)
setGitHubResponse(null)
await pluginActions.saveToken(null)
await actions.loadGitHubUserFromToken()
}
return (
<>
{(context.gitHubUser && context.gitHubUser.login) ? null :
<button className='btn btn-primary mt-1 w-100' onClick={async () => {
getDeviceCodeFromGitHub();
}}><i className="fab fa-github mr-1"></i>Login in with github</button>
}
{gitHubResponse && !authorized &&
<div className="pt-2">
Step 1: Copy this code:
<div className="input-group text-secondary mb-0 h6">
<input disabled type="text" className="form-control" value={gitHubResponse.user_code} />
<div className="input-group-append">
<CopyToClipboard content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} />
</div>
</div>
<br></br>
Step 2: Authorize the app here
<br></br><a target="_blank" href={gitHubResponse.verification_uri}>{gitHubResponse.verification_uri}</a>
<br /><br></br>
Step 3: When you are done, click on the button below:
<button className='btn btn-primary mt-1 w-100' onClick={async () => {
connectApp()
}}>Connect</button>
</div>
}
{
(context.gitHubUser && context.gitHubUser.login) ?
<div className="pt-2">
<button className='btn btn-primary mt-1 w-100' onClick={async () => {
disconnect()
}}>Disconnect</button>
</div> : null
}
{
(context.gitHubUser && context.gitHubUser.login) ?
<div className="pt-2">
<Card>
<Card.Body>
<Card.Title data-id={`connected-as-${context.gitHubUser.login}`}>Connected as {context.gitHubUser.login}</Card.Title>
<Card.Text>
<img data-id={`connected-img-${context.gitHubUser.login}`} src={context.gitHubUser.avatar_url} className="w-100" />
<a data-id={`connected-link-${context.gitHubUser.login}`} href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a>
{context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => {
return <span key={email.email}><br></br>{email.email}</span>
})}
</Card.Text>
</Card.Body>
</Card>
</div> : null
}
</>)
}

@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import { gitActionsContext } from '../../state/context';
import { repository } from '../../types';
import { selectStyles, selectTheme } from '../../types/styles';
import { gitPluginContext } from '../gitui';
import { TokenWarning } from '../panels/tokenWarning';
interface RepositorySelectProps {
select: (repo: repository) => void;
}
const RepositorySelect = (props: RepositorySelectProps) => {
const [repoOtions, setRepoOptions] = useState<any>([]);
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [loading, setLoading] = useState(false)
const [show, setShow] = useState(false)
useEffect(() => {
if (context.repositories && context.repositories.length > 0) {
// map context.repositories to options
const options = context.repositories && context.repositories.length > 0 && context.repositories.map(repo => {
return { value: repo.id, label: repo.full_name }
})
setRepoOptions(options)
setShow(options.length > 0)
} else {
setRepoOptions(null)
setShow(false)
}
setLoading(false)
}, [context.repositories])
const selectRepo = async (e: any) => {
if (!e || !e.value) {
props.select(null)
return
}
const value = e && e.value
const repo = context.repositories.find(repo => {
return repo.id.toString() === value.toString()
})
if (repo) {
props.select(repo)
await actions.remoteBranches(repo.owner.login, repo.name)
}
}
const fetchRepositories = async () => {
try {
setShow(true)
setLoading(true)
setRepoOptions([])
await actions.repositories()
} catch (e) {
// do nothing
}
};
return (
<><Button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1">
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub
</Button>
{
show ?
<Select
options={repoOtions}
className="mt-1"
id="repository-select"
onChange={(e: any) => selectRepo(e)}
theme={selectTheme}
styles={selectStyles}
isClearable={true}
placeholder="Type to search for a repository..."
isLoading={loading}
/> : null
}</>
);
};
export default RepositorySelect;

@ -0,0 +1,59 @@
import React, { useEffect, useState } from "react";
import { gitActionsContext } from "../../state/context";
import { repository } from "../../types";
import { gitPluginContext } from "../gitui";
import RepositorySelect from "./repositoryselect";
import { BranchSelect } from "./branchselect";
import { TokenWarning } from "../panels/tokenWarning";
interface RepositoriesProps {
cloneDepth?: number
cloneAllBranches?: boolean
}
export const SelectAndCloneRepositories = (props: RepositoriesProps) => {
const { cloneDepth, cloneAllBranches } = props
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [branch, setBranch] = useState({ name: "" });
const [repo, setRepo] = useState<repository>(null);
const selectRemoteBranch = async (branch:{ name: string }) => {
setBranch(branch)
}
const selectRepo = async (repo: repository) => {
setBranch(null)
setRepo(repo)
}
const clone = async () => {
try {
await actions.clone({
url: repo.html_url,
branch: branch.name,
depth: cloneDepth,
singleBranch: !cloneAllBranches
})
//actions.clone(repo.html_url, branch.name, cloneDepth, !cloneAllBranches)
} catch (e) {
// do nothing
}
};
return (
<>
<TokenWarning />
<RepositorySelect select={selectRepo} />
{repo &&<BranchSelect select={selectRemoteBranch} />}
{repo && branch && branch.name && branch.name !== '0' ?
<button data-id={`clonebtn-${repo.full_name}-${branch.name}`} className='btn btn-primary mt-1 w-100' onClick={async () => {
await clone()
}}>clone {repo.full_name}:{branch.name}</button> : null}
</>
)
}

@ -0,0 +1,245 @@
import React, { useEffect, useReducer, useState } 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, 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 { Accordion } from "react-bootstrap";
import { CommitMessage } from './buttons/commitmessage'
import { Commits } from './panels/commits'
import { Branches } from './panels/branches'
import { SourceControlNavigation } from './navigation/sourcecontrol'
import { BranchesNavigation } from './navigation/branches'
import { CommitsNavigation } from './navigation/commits'
import '../style/index.css'
import { CloneNavigation } from './navigation/clone'
import { Clone } from './panels/clone'
import { Commands } from './panels/commands'
import { CommandsNavigation } from './navigation/commands'
import { RemotesNavigation } from './navigation/remotes'
import { Remotes } from './panels/remotes'
import { ViewPlugin } from '@remixproject/engine-web'
import { GitHubNavigation } from './navigation/github'
import { loaderReducer } from '../state/loaderReducer'
import { GetDeviceCode } from './github/devicecode'
import { LogNavigation } from './navigation/log'
import LogViewer from './panels/log'
import { SourceControlBase } from './buttons/sourceControlBase'
import { BranchHeader } from './branchHeader'
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 { Disabled } from './disabled'
export const gitPluginContext = React.createContext<gitState>(defaultGitState)
export const loaderContext = React.createContext<loaderState>(defaultLoaderState)
interface IGitUi {
plugin: Plugin<any, CustomRemixApi>
}
export const GitUI = (props: IGitUi) => {
const plugin = props.plugin
const [gitState, gitDispatch] = useReducer(gitReducer, defaultGitState)
const [loaderState, loaderDispatch] = useReducer(loaderReducer, defaultLoaderState)
const [activePanel, setActivePanel] = useState<string>("0")
const [setup, setSetup] = useState<boolean>(false)
const [needsInit, setNeedsInit] = useState<boolean>(true)
const [appLoaded, setAppLoaded] = useState<boolean>(false)
useEffect(() => {
plugin.emit('statusChanged', {
key: 'loading',
type: 'info',
title: 'Loading Git Plugin'
})
setTimeout(() => {
setAppLoaded(true)
}, 2000)
}, [])
useEffect(() => {
if (!appLoaded) return
setCallBacks(plugin, gitDispatch, loaderDispatch, setActivePanel)
setPlugin(plugin, gitDispatch, loaderDispatch)
loaderDispatch({ type: 'plugin', payload: true })
}, [appLoaded])
useEffect(() => {
if (!appLoaded) return
async function checkconfig() {
const username = await plugin.call('settings', 'get', 'settings/github-user-name')
const email = await plugin.call('settings', 'get', 'settings/github-email')
const token = await plugin.call('settings', 'get', 'settings/gist-access-token')
setSetup(!(username && email))
}
checkconfig()
}, [gitState.gitHubAccessToken, gitState.gitHubUser, gitState.userEmails, gitState.commits, gitState.branches])
useEffect(() => {
if (!appLoaded) return
async function setDecorators(gitState: gitState) {
await plugin.call('fileDecorator', 'clearFileDecorators')
await setModifiedDecorator(gitState.modified)
await setUntrackedDecorator(gitState.untracked)
}
setTimeout(() => {
setDecorators(gitState)
})
}, [gitState.fileStatusResult])
useEffect(() => {
if (!appLoaded) return
async function updatestate() {
if (gitState.currentBranch && gitState.currentBranch.remote && gitState.currentBranch.remote.url) {
remoteCommits(gitState.currentBranch.remote.url, gitState.currentBranch.name, 1)
}
}
setTimeout(() => {
updatestate()
})
let needsInit = false
if (!(gitState.currentBranch && gitState.currentBranch.name !== '') && gitState.currentHead === '') {
needsInit = true
}
setNeedsInit(needsInit)
}, [gitState.gitHubUser, gitState.currentBranch, gitState.remotes, gitState.gitHubAccessToken, gitState.currentHead])
const gitActionsProviderValue = {
commit,
addall,
add,
checkoutfile,
rm,
checkout,
createBranch,
clone,
repositories,
remoteBranches,
getCommitChanges,
getBranchCommits,
getBranchDifferences,
diff,
resolveRef,
setUpstreamRemote,
loadGitHubUserFromToken,
getBranches,
getRemotes,
fetch,
pull,
push,
setDefaultRemote,
addRemote,
removeRemote,
sendToGitLog,
clearGitLog,
getFileStatusMatrix,
gitlog,
init
}
const pluginActionsProviderValue = {
statusChanged,
loadFiles,
openFile,
openDiff,
saveToken,
saveGitHubCredentials,
getGitHubCredentialsFromLocalStorage,
showAlert
}
return (
<>{(!gitState.canUseApp) ? <Disabled></Disabled> :
<div className="m-1">
<gitPluginContext.Provider value={gitState}>
<loaderContext.Provider value={loaderState}>
<gitActionsContext.Provider value={gitActionsProviderValue}>
<pluginActionsContext.Provider value={pluginActionsProviderValue}>
<BranchHeader />
{setup && !needsInit ? <Setup></Setup> : null}
{needsInit ? <Init></Init> : null}
{!setup && !needsInit ?
<Accordion activeKey={activePanel} defaultActiveKey="0">
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="0">
<>
<SourceControlBase><CommitMessage /></SourceControlBase>
<SourceControl />
</>
</Accordion.Collapse>
<hr></hr>
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="1">
<>
<Commands></Commands>
</>
</Accordion.Collapse>
<hr></hr>
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} />
<Accordion.Collapse className='bg-light' eventKey="3">
<>
<Commits />
</>
</Accordion.Collapse>
<hr></hr>
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="2">
<>
<Branches /></>
</Accordion.Collapse>
<hr></hr>
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="5">
<>
<Remotes></Remotes>
</>
</Accordion.Collapse>
<hr></hr>
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="4">
<>
<Clone /></>
</Accordion.Collapse>
<hr></hr>
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="7">
<>
<GetDeviceCode></GetDeviceCode>
<hr></hr>
<GitHubCredentials></GitHubCredentials>
</>
</Accordion.Collapse>
<hr></hr>
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className='bg-light' eventKey="6">
<>
<LogViewer />
</>
</Accordion.Collapse>
</Accordion>
: null}
</pluginActionsContext.Provider>
</gitActionsContext.Provider>
</loaderContext.Provider>
</gitPluginContext.Provider>
</div>}
</>
)
}

@ -0,0 +1,97 @@
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight, faArrowsUpDown, faGlobe, faCheckCircle, faToggleOff, faToggleOn, faSync } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect } from "react";
import { gitActionsContext } from "../../state/context";
import { branch } from "../../types";
import GitUIButton from "../buttons/gituibutton";
import { gitPluginContext } from "../gitui";
interface BrancheDetailsNavigationProps {
eventKey: string;
activePanel: string;
callback: (eventKey: string) => void;
branch: branch;
checkout: (branch: branch) => void;
}
export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) => {
const { eventKey, activePanel, callback, branch, checkout } = props;
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
const getRemote = () => {
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const openRemote = () => {
const remote = branch.remote || getRemote()
window.open(`${remote.url}/tree/${branch.name}`, '_blank');
}
const reloadBranch = () => {
actions.getBranchCommits(branch, 1)
}
const canFetch = () => {
if (getRemote())
return context.branches.find((b) => b.name === branch.name && b.remote && b.remote.url === getRemote().url) ? true : false
}
const fetchBranch = async () => {
await actions.fetch({
remote: null,
ref: branch,
singleBranch: true,
relative: true
})
}
return (
<>
<div className="d-flex flex-row w-100 mb-2 mt-2">
<div data-id={`branches-${context.currentBranch.name === branch.name ? 'current-' : ''}branch-${branch.name}`} onClick={() => handleClick()} role={'button'} className='pointer d-flex flex-row w-100 commit-navigation'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<i className="fa fa-code-branch ml-1"></i>
<div className={`ml-1 ${context.currentBranch.name === branch.name ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div>
</div>
{context.currentBranch && context.currentBranch.name === branch.name ?
<GitUIButton data-id={`branches-toggle-current-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => { }}>
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon>
</GitUIButton>
:
<GitUIButton data-id={`branches-toggle-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}>
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon>
</GitUIButton>
}
{!branch.remote && canFetch() && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>
</>}
{branch.remote?.url && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}>
<FontAwesomeIcon icon={faSync} ></FontAwesomeIcon>
</GitUIButton>
</>}
{branch.remote?.url && <>
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}>
<FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon>
</GitUIButton>
</>}
</div>
</>
);
}

@ -0,0 +1,32 @@
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { } from "react";
import { gitActionsContext, pluginActionsContext } from "../../state/context";
import LoaderIndicator from "./loaderindicator";
export const BranchesNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
const context = React.useContext(gitActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}>
<span data-id='branches-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">BRANCHES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}

@ -0,0 +1,29 @@
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect } from "react";
import LoaderIndicator from "./loaderindicator";
export const CloneNavigation = ({ eventKey, activePanel, callback }) => {
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between pb-1 pt-1 ' + (activePanel === eventKey? 'bg-light': '')}>
<span data-id='clone-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">CLONE</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}

@ -0,0 +1,35 @@
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faCircleCheck, faArrowsUpDown, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect } from "react";
import { pluginActionsContext } from "../../state/context";
import GitUIButton from "../buttons/gituibutton";
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons";
import LoaderIndicator from "./loaderindicator";
export const CommandsNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='commands-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">COMMANDS</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}

@ -0,0 +1,37 @@
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect } from "react";
import { CommitSummary } from "../panels/commits/commitsummary";
import { ReadCommitResult } from "isomorphic-git"
interface CommitDetailsNavigationProps {
commit: ReadCommitResult,
checkout: (oid: string) => void
eventKey: string
activePanel: string
callback: (eventKey: string) => void
isAheadOfRepo: boolean
}
export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) => {
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo } = props;
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div onClick={() => handleClick()} role={'button'} className={`pointer mb-2 mt-2 w-100 d-flex flex-row commit-navigation ${isAheadOfRepo ? 'text-success' : ''}`}>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<CommitSummary isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary>
</div>
</>
);
}

@ -0,0 +1,63 @@
import { faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faCloudArrowUp, faCloudArrowDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CustomTooltip } from "@remix-ui/helper";
import React, { useEffect } from "react";
import { FormattedMessage } from "react-intl";
import { pluginActionsContext } from "../../state/context";
import { branch, remote } from "../../types";
import { SourceControlBase } from "../buttons/sourceControlBase";
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons";
import { gitPluginContext } from "../gitui";
import LoaderIndicator from "./loaderindicator";
export interface CommitsNavigationProps {
title: string,
eventKey: string,
activePanel: string,
callback: (eventKey: string) => void
branch?: branch,
remote?: remote
showButtons?: boolean
ahead?: boolean,
behind?: boolean,
}
export const CommitsNavigation = ({ eventKey, activePanel, callback, title, branch, remote, showButtons, ahead, behind }: CommitsNavigationProps) => {
const pluginactions = React.useContext(pluginActionsContext)
const [pullEnabled, setPullEnabled] = React.useState(true)
const [pushEnabled, setPushEnabled] = React.useState(true)
const [syncEnabled, setSyncEnabled] = React.useState(false)
const [fetchEnabled, setFetchEnabled] = React.useState(true)
const context = React.useContext(gitPluginContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={`d-flex justify-content-between ${activePanel === eventKey ? 'bg-light' : ''} ${ahead || behind? 'text-success':''}`}>
<span data-id={`commits-panel${ahead?'-ahead':''}${behind?'-behind':''}`} onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-100'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
{ahead? <FontAwesomeIcon className='ml-1' icon={faCloudArrowUp}></FontAwesomeIcon> : null}
{behind? <FontAwesomeIcon className='ml-1' icon={faCloudArrowDown}></FontAwesomeIcon> : null}
<label className={`pl-1 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label>
<LoaderIndicator></LoaderIndicator>
</span>
{showButtons ?
<SourceControlBase branch={branch} remote={remote}>
<SourceControlButtons />
</SourceControlBase> : null}
</div>
</>
);
}

@ -0,0 +1,29 @@
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)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}>
<span data-id='github-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">GITHUB SETUP</label>
</span>
</div>
</>
);
}

@ -0,0 +1,20 @@
import React, { useContext } from 'react'
import { gitPluginContext } from '../gitui'
interface LoaderIndicatorProps {
type?: string;
isLoadingCondition?: boolean; // Optional additional disabling condition
}
// This component extends a button, disabling it when loading is true
const LoaderIndicator = ({ type, isLoadingCondition }: LoaderIndicatorProps) => {
const { loading } = React.useContext(gitPluginContext)
const isLoading = loading || isLoadingCondition
if (!isLoading) return null
return (
<i style={{ fontSize: 'x-small' }} className="ml-1 fas fa-spinner fa-spin fa-4x"></i>
);
};
export default LoaderIndicator;

@ -0,0 +1,87 @@
import { faBan, faCaretDown, faCaretRight, faCircleCheck, faCircleInfo, faInfo, faTrash, faTriangleExclamation, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useContext, useEffect, useState } from "react";
import { gitActionsContext, pluginActionsContext } from "../../state/context";
import { gitPluginContext } from "../gitui";
export const LogNavigation = ({ eventKey, activePanel, callback }) => {
const context = useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [logState, setLogState] = useState({
errorCount: 0,
warningCount: 0,
infoCount: 0,
successCount: 0
});
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
useEffect(() => {
if (!context.log) return
// count different types of logs
const errorCount = context.log.filter(log => log.type === 'error').length
const warningCount = context.log.filter(log => log.type === 'warning').length
const infoCount = context.log.filter(log => log.type === 'info').length
const successCount = context.log.filter(log => log.type === 'success').length
// update the state
setLogState({
errorCount,
warningCount,
infoCount,
successCount
})
}, [context.log])
const clearLogs = () => {
actions.clearGitLog()
}
return (
<>
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label mr-2">LOG</label>
{logState.errorCount > 0 && (
<div className="text-danger mr-1">
{logState.errorCount}
<FontAwesomeIcon className="ml-1" icon={faTriangleExclamation} />
</div>
)}
{logState.warningCount > 0 && (
<div className="text-warning mr-1">
{logState.warningCount}
<FontAwesomeIcon className="ml-1" icon={faWarning} />
</div>
)}
{logState.infoCount > 0 && (
<div className="text-info mr-1">
{logState.infoCount}
<FontAwesomeIcon className="ml-1" icon={faCircleInfo} />
</div>
)}
{logState.successCount > 0 && (
<div className="text-success">
{logState.successCount}
<FontAwesomeIcon className="ml-1" icon={faCircleCheck} />
</div>
)}
</span>
{context.log && context.log.length > 0 && (
<FontAwesomeIcon onClick={clearLogs} className='btn btn-sm' icon={faBan}></FontAwesomeIcon>)}
</div>
</>
);
}

@ -0,0 +1,49 @@
import { count } from "console"
import { CustomIconsToggle, CustomMenu, CustomTooltip } from "@remix-ui/helper"
import React, { useState } from "react"
import { Dropdown } from "react-bootstrap"
import { FormattedMessage } from "react-intl"
export const SourceControlMenu = () => {
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false)
return (
<Dropdown id="workspacesMenuDropdown" data-id="sourceControlMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}>
<Dropdown.Toggle
onClick={() => {
hideIconsMenu(!showIconsMenu)
}}
as={CustomIconsToggle}
icon={'fas fa-bars'}
></Dropdown.Toggle>
<Dropdown.Menu as={CustomMenu} data-id="wsdropdownMenu" className='custom-dropdown-items remixui_menuwidth' rootCloseEvent="click">
<Dropdown.Item key={0}>
<CustomTooltip
placement="right-start"
tooltipId="cloneWorkspaceTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id='filePanel.workspace.clone' defaultMessage='Clone Git Repository' />}
>
<div
data-id='cloneGitRepository'
onClick={() => {
hideIconsMenu(!showIconsMenu)
}}
key={`cloneGitRepository-fe-ws`}
>
<span
id='cloneGitRepository'
data-id='cloneGitRepository'
onClick={() => {
hideIconsMenu(!showIconsMenu)
}}
className='fab fa-github pl-2'
>
</span>
<span className="pl-3"><FormattedMessage id='filePanel.clone' defaultMessage='Clone' /></span>
</div>
</CustomTooltip>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
)
}

@ -0,0 +1,32 @@
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { } from "react";
import { gitActionsContext, pluginActionsContext } from "../../state/context";
import LoaderIndicator from "./loaderindicator";
export const RemotesNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
const context = React.useContext(gitActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}>
<span data-id='remotes-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">REMOTES</label>
<LoaderIndicator></LoaderIndicator>
</span>
</div>
</>
);
}

@ -0,0 +1,68 @@
import { faCaretDown, faCaretRight, faArrowRightArrowLeft, faGlobe, faToggleOff, faToggleOn, faTrash, faCheck, faSync } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CustomTooltip } from "@remix-ui/helper";
import React, { useContext, useEffect } from "react";
import { gitActionsContext } from "../../state/context";
import { branch, remote } from "../../types";
import GitUIButton from "../buttons/gituibutton";
import { gitPluginContext } from "../gitui";
interface RemotesDetailsNavigationProps {
eventKey: string;
activePanel: string;
callback: (eventKey: string) => void;
remote: remote;
}
export const RemotesDetailsNavigation = (props: RemotesDetailsNavigationProps) => {
const { eventKey, activePanel, callback, remote } = props;
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
const openRemote = () => {
window.open(`${remote.url}`, '_blank');
}
const setAsDefault = () => {
actions.setDefaultRemote(remote)
}
return (
<>
<div className="d-flex flex-row w-100 mb-2 mt-2">
<div data-id={`remote-detail-${remote.name}${context.defaultRemote && context.defaultRemote?.url === remote.url ? '-default' : ''}`} onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<CustomTooltip tooltipText={remote.url} placement="top">
<div className={`long-and-truncated ml-1 ${context.defaultRemote && context.defaultRemote?.url === remote.url ? 'text-success' : ''}`}>
{remote.name} <FontAwesomeIcon className='' icon={faArrowRightArrowLeft}></FontAwesomeIcon> {remote.url}
</div>
</CustomTooltip>
</div>
{context.defaultRemote && context.defaultRemote?.url === remote.url ?
<GitUIButton className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton>
:
<GitUIButton className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton>
}
<GitUIButton data-id={`remote-sync-${remote.name}`} className="btn btn-sm" onClick={async () => {
await actions.fetch({
remote
})
}}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton>
<GitUIButton data-id={`remote-rm-${remote.name}`} className="btn btn-sm" onClick={() => actions.removeRemote(remote)}><FontAwesomeIcon className='text-danger' icon={faTrash} ></FontAwesomeIcon></GitUIButton>
{remote?.url && <GitUIButton className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>}
</div>
</>
);
}

@ -0,0 +1,41 @@
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CustomTooltip } from "@remix-ui/helper";
import React, { useContext, useEffect } from "react";
import { FormattedMessage } from "react-intl";
import { pluginActionsContext } from "../../state/context";
export const SettingsNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="nav pl-1 form-check-label">SETTINGS</label>
</span>
<span className='d-flex justify-content-end align-items-center w-25'>
<CustomTooltip tooltipText={<FormattedMessage id="Missing values" />}>
<button onClick={async () => { await pluginactions.loadFiles() }} className='btn btn-sm text-warning'><FontAwesomeIcon icon={faTriangleExclamation} className="" /></button>
</CustomTooltip>
</span>
</div>
</>
);
}

@ -0,0 +1,42 @@
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CustomTooltip } from "@remix-ui/helper";
import React, { useContext, useEffect } from "react";
import { FormattedMessage } from "react-intl";
import { pluginActionsContext } from "../../state/context";
import { SourceControlBase } from "../buttons/sourceControlBase";
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons";
import { gitPluginContext } from "../gitui";
import LoaderIndicator from "./loaderindicator";
import { SourceControlMenu } from "./menu/sourcecontrolmenu";
export const SourceControlNavigation = ({ eventKey, activePanel, callback }) => {
const pluginactions = React.useContext(pluginActionsContext)
const context = React.useContext(gitPluginContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}>
<span data-id='sourcecontrol-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="nav pl-1 form-check-label">SOURCE CONTROL</label>
<LoaderIndicator></LoaderIndicator>
</span>
<SourceControlBase><SourceControlButtons/></SourceControlBase>
</div>
</>
);
}

@ -0,0 +1,52 @@
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faPlus, faMinus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CustomTooltip } from "@remix-ui/helper";
import React, { useContext, useEffect } from "react";
import { FormattedMessage } from "react-intl";
import { gitActionsContext, pluginActionsContext } from "../../state/context";
import { sourceControlGroup } from "../../types";
import { gitPluginContext } from "../gitui";
interface SourceControlGroupNavigationProps {
eventKey: string;
activePanel: string;
callback: (eventKey: string) => void;
group: sourceControlGroup
}
export const SourceControlGroupNavigation = (props: SourceControlGroupNavigationProps) => {
const { eventKey, activePanel, callback, group } = props;
const actions = React.useContext(gitActionsContext)
const pluginActions = React.useContext(pluginActionsContext)
const context = React.useContext(gitPluginContext)
const handleClick = () => {
if (!callback) return
if (activePanel === eventKey) {
callback('')
} else {
callback(eventKey)
}
}
return (
<>
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}>
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'>
{
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon>
}
<label className="pl-1 nav form-check-label">{group.name}</label>
</span>
{
activePanel === eventKey ?
<span className='d-flex justify-content-end align-items-center w-25'>
{group.name === 'Changes' ?
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}>
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button>
</CustomTooltip>: null}
</span> : null
}
</div>
</>
);
}

@ -0,0 +1,60 @@
import React, { useEffect, useState } from "react";
import { Alert } from "react-bootstrap";
import { gitActionsContext } from "../../state/context";
import { remote } from "../../types";
import GitUIButton from "../buttons/gituibutton";
import { gitPluginContext } from "../gitui";
import { LocalBranchDetails } from "./branches/localbranchdetails";
import { RemoteBranchDetails } from "./branches/remotebranchedetails";
export const Branches = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [newBranch, setNewBranch] = useState({ value: "" });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewBranch({ value: e.currentTarget.value });
};
return (
<>
<div data-id='branches-panel-content' className="pt-1">
{context.branches && context.branches.length ?
<div>
{context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => {
return (
<LocalBranchDetails key={index} branch={branch}></LocalBranchDetails>
);
})}
<hr />
</div> : null}
{context.currentBranch
&& context.currentBranch.name !== ''
&& (!context.branches || context.branches.length === 0) ?
<div className="text-muted">Current branch is `{context.currentBranch.name}` but you have no commits.<hr /></div>
: null}
<label>create branch</label>
<div className="form-group">
<input
placeholder="branch name"
onChange={handleChange}
className="form-control w-md-25 w-100"
data-id="newbranchname"
type="text"
id="newbranchname"
/>
</div>
<GitUIButton
data-id="sourcecontrol-create-branch"
onClick={async () => actions.createBranch(newBranch.value)}
className="btn w-md-25 w-100 btn-primary"
id="createbranch-btn"
>
create new branch
</GitUIButton>
</div>
</>
);
}

@ -0,0 +1,48 @@
import { ReadCommitResult } from "isomorphic-git";
import { Accordion } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import { CommitDetails } from "../commits/commitdetails";
import { CommitsNavigation } from "../../navigation/commits";
import { branch, remote } from "../../../types";
import { gitActionsContext } from "../../../state/context";
import { gitPluginContext } from "../../gitui";
export interface BrancheDifferenceProps {
commits: ReadCommitResult[];
title: string,
remote?: remote,
branch?: branch
ahead?: boolean,
behind?: boolean
}
export const BranchDifferenceDetails = (props: BrancheDifferenceProps) => {
const { commits, title, branch, remote, ahead, behind } = props;
const [activePanel, setActivePanel] = useState<string>("");
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
if (commits.length === 0) return null
const getRemote = () => {
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const getCommitChanges = async (commit: ReadCommitResult) => {
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote())
}
return (
<Accordion activeKey={activePanel} defaultActiveKey="">
<CommitsNavigation ahead={ahead} behind={behind} branch={branch} remote={remote} title={title} eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0">
<div data-id={`branchdifference-commits-${branch.name}${ahead?'-ahead':''}${behind?'-behind':''}`} className="ml-1">
{commits && commits.map((commit, index) => {
return (
<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={()=>{}} commit={commit}></CommitDetails>
);
})}
</div>
</Accordion.Collapse>
</Accordion>)
}

@ -0,0 +1,39 @@
import { branch, remote } from "../../../types";
import React, { useEffect, useState } from "react";
import { gitPluginContext } from "../../gitui";
import { CommitDetails } from "../commits/commitdetails";
import { BranchDifferenceDetails } from "./branchdifferencedetails";
export interface BrancheDetailsProps {
branch: branch;
showSummary?: boolean;
}
export const BranchDifferences = (props: BrancheDetailsProps) => {
const { branch, showSummary } = props;
const context = React.useContext(gitPluginContext)
const getRemote = (): remote | null => {
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const commitsAhead = (remote: remote) => {
if (!remote) return [];
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || [];
}
const commitsBehind = (remote: remote) => {
if (!remote) return [];
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits || [];
}
if (!getRemote()) return null;
return (
<div>
<BranchDifferenceDetails ahead={true} branch={branch} remote={getRemote()} title={`ahead of ${getRemote().name} by ${commitsAhead(getRemote()).length} commit(s)`} commits={commitsAhead(getRemote())}></BranchDifferenceDetails>
<BranchDifferenceDetails behind={true} branch={branch} remote={getRemote()} title={`behind ${getRemote().name} by ${commitsBehind(getRemote()).length} commit(s)`} commits={commitsBehind(getRemote())}></BranchDifferenceDetails>
{commitsAhead(getRemote()).length === 0 && commitsBehind(getRemote()).length === 0 ? null : <hr></hr>}
</div>)
}

@ -0,0 +1,86 @@
import { ReadCommitResult } from "isomorphic-git"
import React, { useEffect, useState } from "react";
import { Accordion } from "react-bootstrap";
import { CommitDetailsNavigation } from "../../navigation/commitdetails";
import { gitActionsContext } from "../../../state/context";
import { gitPluginContext } from "../../gitui";
import { branch } 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";
export interface BrancheDetailsProps {
branch: branch;
}
export const LocalBranchDetails = (props: BrancheDetailsProps) => {
const { branch } = props;
const actions = React.useContext(gitActionsContext)
const context = React.useContext(gitPluginContext)
const [activePanel, setActivePanel] = useState<string>("");
const [hasNextPage, setHasNextPage] = useState<boolean>(false)
const [lastPageNumber, setLastPageNumber] = useState<number>(0)
useEffect(() => {
if (activePanel === "0") {
if (lastPageNumber === 0)
actions.getBranchCommits(branch, 1)
actions.getBranchDifferences(branch, null, context)
}
}, [activePanel])
const checkout = (branch: branch) => {
actions.checkout({
ref: branch.name,
remote: branch.remote && branch.remote.name || null,
refresh: true
});
}
const loadNextPage = () => {
actions.getBranchCommits(branch, lastPageNumber + 1)
}
const checkoutCommit = async (oid: string) => {
try {
actions.checkout({ ref: oid })
;
} catch (e) {
//
}
};
const getRemote = () => {
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null
}
const getCommitChanges = async (commit: ReadCommitResult) => {
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote())
}
return (<Accordion activeKey={activePanel} defaultActiveKey="">
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0">
<>
<div className="ml-1">
<BranchDifferences branch={branch}></BranchDifferences>
<div data-id={`local-branch-commits-${branch && branch.name}`}>
{context.localBranchCommits && Object.entries(context.localBranchCommits).map(([key, value]) => {
if (key == branch.name) {
return value.map((commit, index) => {
return (<CommitDetails branch={branch} key={index} getCommitChanges={getCommitChanges} checkout={checkoutCommit} commit={commit}></CommitDetails>)
})
}
})}
</div>
</div>
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>}
</>
</Accordion.Collapse>
</Accordion>)
}

@ -0,0 +1,111 @@
import { ReadCommitResult } from "isomorphic-git"
import React, { useEffect, useState } from "react";
import { Accordion } from "react-bootstrap";
import { CommitDetailsNavigation } from "../../navigation/commitdetails";
import { gitActionsContext } from "../../../state/context";
import { gitPluginContext } from "../../gitui";
import { branch } from "../../../types";
import { BrancheDetailsNavigation } from "../../navigation/branchedetails";
import { CommitDetailsItems } from "../commits/commitdetailsitem";
import { CommitDetails } from "../commits/commitdetails";
import GitUIButton from "../../buttons/gituibutton";
export interface BrancheDetailsProps {
branch: branch;
}
export const RemoteBranchDetails = (props: BrancheDetailsProps) => {
const { branch } = props;
const actions = React.useContext(gitActionsContext)
const context = React.useContext(gitPluginContext)
const [activePanel, setActivePanel] = useState<string>("");
const [hasNextPage, setHasNextPage] = useState<boolean>(false)
const [lastPageNumber, setLastPageNumber] = useState<number>(0)
useEffect(() => {
if (activePanel === "0") {
if (lastPageNumber === 0)
actions.getBranchCommits(branch, 1)
}
}, [activePanel])
useEffect(() => {
let hasNextPage = false
let lastPageNumber = 0
context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => {
if (key == branch.name) {
value.map((page, index) => {
hasNextPage = page.hasNextPage
lastPageNumber = page.page
})
}
})
setHasNextPage(hasNextPage)
setLastPageNumber(lastPageNumber)
}, [context.remoteBranchCommits])
const checkout = async (branch: branch) => {
await actions.fetch({
remote: branch.remote,
ref: branch,
depth: 20,
singleBranch: true,
relative: false,
quiet: true
})
await actions.checkout({
ref: branch.name,
remote: branch.remote && branch.remote.name || null,
refresh: true
});
await actions.getBranches()
}
const loadNextPage = () => {
actions.getBranchCommits(branch, lastPageNumber + 1)
}
const checkoutCommit = async (oid: string) => {
try {
actions.checkout({ ref: oid })
} catch (e) {
//
}
};
const getCommitChanges = async (commit: ReadCommitResult) => {
const changes = await actions.getCommitChanges(commit.oid, commit.commit.parent[0], branch, branch.remote)
if (!changes) {
await actions.fetch({
remote: branch.remote,
ref: branch,
depth: 20,
singleBranch: true,
relative: false,
quiet: true
})
}
}
return (<Accordion activeKey={activePanel} defaultActiveKey="">
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} />
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0">
<>
<div data-id={`remote-branch-commits-${branch && branch.name}`} className="ml-1">
{context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => {
if (key == branch.name) {
return value.map((page, index) => {
return page.commits.map((commit, index) => {
return (<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={checkoutCommit} commit={commit}></CommitDetails>)
})
})
}
})}
</div>
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>}
</>
</Accordion.Collapse>
</Accordion>)
}

@ -0,0 +1,101 @@
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 = () => {
const context = React.useContext(gitPluginContext)
const actions = React.useContext(gitActionsContext)
const [cloneUrl, setCloneUrl] = useLocalStorage(
"CLONE_URL",
''
);
const [cloneDepth, setCloneDepth] = useLocalStorage(
"CLONE_DEPTH",
1
);
const [cloneBranch, setCloneBranch] = useLocalStorage(
"CLONE_BRANCH",
''
);
const [url, setUrl] = useLocalStorage(
"GITHUB_URL",
''
);
const clone = async () => {
await actions.clone({
url: cloneUrl,
branch: cloneBranch,
depth: cloneDepth,
singleBranch: !cloneAllBranches
})
}
const onCloneBranchChange = (value: string) => {
setCloneBranch(value)
}
const onGitHubCloneUrlChange = (value: string) => {
setCloneUrl(value)
}
const onDepthChange = (value: number) => {
setCloneDepth(value)
}
const [cloneAllBranches, setcloneAllBranches] = useLocalStorage(
"GITHUB_CLONE_ALL_BRANCES",
false
);
const onAllBranchChange = () => {
setcloneAllBranches((e: any) => !e)
}
return (
<>
<div data-id="clone-panel-content">
<InputGroup className="mb-1">
<FormControl data-id="clone-url" id="cloneulr" placeholder="url" name='cloneurl' value={cloneUrl} onChange={e => onGitHubCloneUrlChange(e.target.value)} aria-describedby="urlprepend" />
</InputGroup>
<input name='clonebranch' onChange={e => onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-1 mt-2" placeholder="branch" type="text" id="clonebranch" />
<GitUIButton disabledCondition={!cloneUrl} data-id='clone-btn' className='btn btn-primary mt-1 w-100' onClick={async () => {
clone()
}}>clone</GitUIButton>
<hr />
<SelectAndCloneRepositories cloneAllBranches={cloneAllBranches} cloneDepth={cloneDepth} />
<hr />
<label>options</label>
<InputGroup className="mt-1 mb-1">
<InputGroup.Prepend>
<InputGroup.Text id="clonedepthprepend">
--depth
</InputGroup.Text>
</InputGroup.Prepend>
<FormControl id="clonedepth" type="number" value={cloneDepth} onChange={e => onDepthChange(parseInt(e.target.value))} aria-describedby="clonedepthprepend" />
</InputGroup>
<RemixUiCheckbox
id={`cloneAllBranches`}
inputType="checkbox"
name="cloneAllBranches"
label={`Clone all branches`}
onClick={() => onAllBranchChange()}
checked={cloneAllBranches}
onChange={() => { }}
/>
<hr></hr>
</div>
</>)
}

@ -0,0 +1,14 @@
import React, { useEffect, useState } from "react";
import { PushPull } from "./commands/pushpull";
import { Fetch } from "./commands/fetch";
import { Merge } from "./commands/merge";
export const Commands = () => {
return (
<>
<PushPull></PushPull>
<hr></hr>
<Fetch></Fetch>
</>)
}

@ -0,0 +1,25 @@
import React, { useEffect, useState } from "react";
import { gitActionsContext } from "../../../state/context";
import GitUIButton from "../../buttons/gituibutton";
import { gitPluginContext } from "../../gitui";
export const Fetch = () => {
const actions = React.useContext(gitActionsContext)
const context = React.useContext(gitPluginContext)
const fetchIsDisabled = () => {
return (!context.upstream) || context.remotes.length === 0
}
return (
<>
<div className="btn-group w-100" role="group">
<GitUIButton data-id='sourcecontrol-fetch-remote' disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({
remote: context.upstream,
})} className="btn btn-primary mr-1 w-50"><div>Fetch {context.upstream && context.upstream.name}</div></GitUIButton>
<GitUIButton data-id='sourcecontrol-fetch-branch' disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({
remote: context.upstream,
ref: context.currentBranch
})} className="btn btn-primary w-50 long-and-truncated">Fetch {context.currentBranch.name}</GitUIButton>
</div>
</>)
}

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

Loading…
Cancel
Save