commitpull/3885/headb4f616a2c9
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 17:02:29 2023 +0200 fix bash commit50b5f5083d
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 16:10:27 2023 +0200 bash commitdffa78e3dc
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 15:10:28 2023 +0200 shells commitf34a4e394e
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 14:26:48 2023 +0200 update readme commitf53cba9205
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 14:18:57 2023 +0200 static commit897e81246f
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 14:01:07 2023 +0200 Update README.md commit5d7e112a35
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 13:56:34 2023 +0200 rm txt file commit0d77e68ddf
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 13:55:40 2023 +0200 add readme commitb7f59b65ac
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jul 4 13:54:52 2023 +0200 fix glob commit962395926e
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 12:50:44 2023 +0200 fix modals commit3e610699cc
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 12:18:01 2023 +0200 commits commitc0cc7328ef
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 10:57:11 2023 +0200 fix class commitd2eea4eea2
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 10:39:53 2023 +0200 linting commit673258ef87
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 10:11:57 2023 +0200 filter commit0257e2928e
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 10:10:13 2023 +0200 ci filter commit329de0d339
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 10:06:40 2023 +0200 fix lint commit29abc4076e
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jul 4 09:27:31 2023 +0200 install yarn commit366fd4969e
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 17:33:56 2023 +0200 yarn commitea7e69faa3
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:52:08 2023 +0200 console commit3b6561c488
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:51:44 2023 +0200 consoles commit57ef21ab94
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:50:20 2023 +0200 consoles commit867b9775c0
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:49:54 2023 +0200 consoles commita2dc8b996a
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:47:14 2023 +0200 rm lock commitbc6d4d23a3
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:46:16 2023 +0200 restore libs commit1a76ab87ba
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:44:40 2023 +0200 fix git commit870083ff49
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:29:33 2023 +0200 typo commit8987d73d20
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:26:14 2023 +0200 can use worker commit6045655cd2
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:26:07 2023 +0200 can use worker commit5ee444e030
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:13:38 2023 +0200 cleanup package commit7f5f0bfd37
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:10:01 2023 +0200 fix lib commit1d97df570c
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:05:26 2023 +0200 rm test app commit0483d13e56
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jul 3 16:03:17 2023 +0200 logs commit8edd1925b7
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 19:53:27 2023 +0200 debugger commit16f337a2d7
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 16:36:44 2023 +0200 fix env shell commit35a7690591
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 16:15:54 2023 +0200 terminal menu commit12c0894079
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 13:26:23 2023 +0200 custom components commit731aecd556
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 12:39:36 2023 +0200 terminals commit297c476b21
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 28 12:39:32 2023 +0200 terminals commitaeebd08602
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 15:57:26 2023 +0200 bugfix commitc104f7056a
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 15:54:07 2023 +0200 rm ripgrep commit9c3283c017
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 15:01:41 2023 +0200 xterm panels commite0a2f62f71
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jun 27 13:51:09 2023 +0200 terminals commit1607d4a586
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jun 27 13:02:36 2023 +0200 pre18 commitf4e3dfc01d
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Tue Jun 27 12:29:16 2023 +0200 xterm build commitcb32ecbd31
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 10:21:47 2023 +0200 ripgrep tests commitee56ee6a0e
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:41:05 2023 +0200 machine image commitf5bdd715ce
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:34:32 2023 +0200 14.17.6 commit5af1131851
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:26:58 2023 +0200 add orb commit6897b22cad
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:25:06 2023 +0200 node install commit84e58d7ad0
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:21:03 2023 +0200 18.04 commit716e39f00f
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:12:05 2023 +0200 docker commit26006c6e2a
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:04:35 2023 +0200 typo commit48444e18bf
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:03:13 2023 +0200 machine commitbf007bb1d1
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 08:01:54 2023 +0200 electronuserland/builder:14 commit80e367e827
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 07:56:17 2023 +0200 current commitfcc0366600
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 07:52:28 2023 +0200 run commit5a76be67fa
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 27 07:51:20 2023 +0200 linux commit3b04e0df7c
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Mon Jun 26 18:06:15 2023 +0200 ripgrep commitf927730171
Author: filip mertens <filip.mertens@ethereum.org> Date: Mon Jun 26 17:26:36 2023 +0200 fixes for git commite5ec564b1e
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Mon Jun 26 12:11:29 2023 +0200 git fixes commitcc3d9ea5fb
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 17:30:19 2023 +0200 less logs commitdc8cf19bf1
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 16:51:27 2023 +0200 use native git commitfe86f5927b
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 14:27:19 2023 +0200 icon commit76244314f4
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 14:10:51 2023 +0200 no asar commitf31ac8e008
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:51:41 2023 +0200 linux fields commit30c186d20b
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:42:38 2023 +0200 author commit2035636e20
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:36:00 2023 +0200 email commit7a28db2f52
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:23:27 2023 +0200 linux commit6d8b169c30
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:22:28 2023 +0200 config commita1050f1714
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:21:41 2023 +0200 linux commit0728709ce2
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:04:43 2023 +0200 desktopbuild commit539d992108
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 13:00:52 2023 +0200 unzip commitec52d9acf3
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:54:52 2023 +0200 typo commit3690a7b314
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:50:29 2023 +0200 typo commit7880ff4e36
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:47:15 2023 +0200 windows commit3424201925
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:45:26 2023 +0200 config commit1ad75731f7
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:41:34 2023 +0200 config commit8fa20b74a4
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:41:11 2023 +0200 config commitbe174d3da3
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:40:35 2023 +0200 config commit32bbaf31bc
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:35:18 2023 +0200 config commit1ec69c6e1e
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:34:52 2023 +0200 config commitacca77164c
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:33:25 2023 +0200 build desktop commit6ecd1e8b2e
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:23:44 2023 +0200 cache commitb75983af55
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:15:18 2023 +0200 ci commit9b4b8970ae
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 12:13:27 2023 +0200 fix path commit34acdb76a1
Author: bunsenstraat <bunsenstraat@gmail.com> Date: Sun Jun 25 11:28:41 2023 +0200 fix windows commit0de6b9cc74
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Sat Jun 24 14:22:09 2023 +0200 es6 commit7a51a12912
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Sat Jun 24 09:49:07 2023 +0200 USE_HARD_LINKS commitf77e8cb9a4
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 23 20:03:26 2023 +0200 mkdir commit16b492cb20
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 23 19:58:24 2023 +0200 m1 commit2daccdec42
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 17:22:31 2023 +0200 revert commit0e1c48d3a1
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 17:12:09 2023 +0200 targets commit3187054eb7
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:59:52 2023 +0200 large commite0765f2b1e
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:58:53 2023 +0200 large commitd2b9e1b557
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:58:19 2023 +0200 xlarge commita2d6acaab8
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:41:29 2023 +0200 cmd commit6108f51031
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:35:11 2023 +0200 windows commit9f68bdf415
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:24:27 2023 +0200 package commitbf57415b8d
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:09:00 2023 +0200 apps/remixdesktop/ commitf8b6de1cfc
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 16:03:46 2023 +0200 store commit02646d2f19
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:41:19 2023 +0200 other glob version commitf77e18dfa5
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:19:07 2023 +0200 rm lock commit276760eeee
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:15:43 2023 +0200 path commitad8811c068
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:14:04 2023 +0200 orb commit016b29a94b
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:13:26 2023 +0200 job commit2a115aa6e8
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 15:12:22 2023 +0200 windows test commit570fdd3ab3
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:54:32 2023 +0200 typo commitcb41e83dec
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:49:52 2023 +0200 mdkir commit24908d0ffa
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:35:41 2023 +0200 lock commitf67726f191
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:29:11 2023 +0200 lock commit90c6f5e1d9
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:12:35 2023 +0200 20 commit99d9b6cc33
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 12:05:21 2023 +0200 use20 commit6053f2d38a
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 11:54:05 2023 +0200 space commiteb6d01edb7
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 11:51:12 2023 +0200 ls commitabedb4a95d
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 11:50:09 2023 +0200 node v commit0b818ca9fd
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 11:47:51 2023 +0200 ci commitcca1a7d63d
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 11:44:12 2023 +0200 build CI commit07c392e0ca
Author: filip mertens <filip.mertens@ethereum.org> Date: Fri Jun 23 10:31:42 2023 +0200 gist fix commit9ecc3949df
Merge:54ffcd5dc
c7dbcf15c
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 14:42:55 2023 +0200 Merge branch 'rdesktop' of https://github.com/ethereum/remix-project into rdesktop commit54ffcd5dc3
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 14:38:00 2023 +0200 fix commitcedb19ed9e
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 14:01:54 2023 +0200 remixd test commit7f13f5d797
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:57:37 2023 +0200 fix test commit2216cebb4b
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:56:08 2023 +0200 fix test commit5dcfef2820
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:45:36 2023 +0200 required modules commit5c8a92ac0f
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:20:12 2023 +0200 remove recent folder commita69a6e648a
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:04:35 2023 +0200 filechanged commit3ce2e5b200
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 10:12:59 2023 +0200 context menu commit570658f54c
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 09:34:53 2023 +0200 menu commit3660c8bc33
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 08:44:51 2023 +0200 fix menu commita1dceb706d
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 08:21:41 2023 +0200 hometab & clone commite58d67345b
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 21 09:24:15 2023 +0200 provider events commit356d8ed3dc
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 20:24:44 2023 +0200 some git functions commitee259cd49f
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 17:34:31 2023 +0200 fix search commit193230f675
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 16:31:17 2023 +0200 glob commitf3ec824c62
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 14 15:45:55 2023 +0200 folder handling commitb888c2b894
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Tue Jun 13 17:16:36 2023 +0200 open folder commiteaefe37861
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 13 12:51:12 2023 +0200 loading engine commit5728f3c7db
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 13 07:47:33 2023 +0200 isogit commit429e845785
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 19:39:39 2023 +0200 dgit commitb106cc8cdf
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 17:22:30 2023 +0200 fs support commitac28db3a52
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 13:33:28 2023 +0200 refactor commit892915b138
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:54:41 2023 +0200 fs integration commit218a56bdc7
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:54:27 2023 +0200 update test1 commit6c25d81166
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:49:03 2023 +0200 update test app commit24039ebb43
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat Jun 3 13:42:10 2023 +0200 other builder commita5c71f4379
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 2 13:45:26 2023 +0200 cleanup commit69398c09af
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 2 13:33:38 2023 +0200 terminals commit89c146f1de
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 18:07:07 2023 +0200 serve commit6bb9beb950
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:52:42 2023 +0200 engine:activatePlugin commit3b26898fb1
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:50:57 2023 +0200 refactor commit881d0c1b12
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:47:48 2023 +0200 rename commitc28f72bca6
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:46:39 2023 +0200 cleanup commit96df90fa1c
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:43:04 2023 +0200 refactor commit92feaa0283
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:32:35 2023 +0200 close watcher commitbea74d4124
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:28:33 2023 +0200 close watcher commit6778a113f8
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:47:55 2023 +0200 refactor commit87f65eeba7
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:41:22 2023 +0200 refactor commit971e4d0265
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:02:37 2023 +0200 refactor commit78d3247725
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 00:39:47 2023 +0200 change fs commitee4b672de3
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 00:29:36 2023 +0200 add test app commit8287f68209
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Tue May 30 08:31:18 2023 +0200 fix path commit47f3fa47a3
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Mon May 29 18:39:08 2023 +0200 app build & serve commitc65465b056
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Mon May 29 09:52:36 2023 +0200 rm trash commit59210c07b5
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Sun May 28 10:56:08 2023 +0200 new app commit463d39d99e
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 15:29:49 2023 +0200 fix webpack commitf340185415
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 14:56:07 2023 +0200 prepackage commit620ede28b0
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:56:08 2023 +0200 testing commitcca42ea2a5
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:38:54 2023 +0200 dev server commit560cfc2371
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:17:18 2023 +0200 experiments commitc7dbcf15c2
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 14:01:54 2023 +0200 remixd test commit367761fe7f
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:57:37 2023 +0200 fix test commit3a25960735
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:56:08 2023 +0200 fix test commit3cd59ec57b
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:45:36 2023 +0200 required modules commit6c0ffc29af
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:20:12 2023 +0200 remove recent folder commitf170b08344
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 13:04:35 2023 +0200 filechanged commit68515019a9
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 10:12:59 2023 +0200 context menu commit443dcde260
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 09:34:53 2023 +0200 menu commit21e85f6881
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 08:44:51 2023 +0200 fix menu commit1d36d50ab6
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 22 08:21:41 2023 +0200 hometab & clone commitf542fd8d1b
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 21 09:24:15 2023 +0200 provider events commitcdf0ca95c2
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 20:24:44 2023 +0200 some git functions commit7dac9f1731
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 17:34:31 2023 +0200 fix search commitd1073ed322
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 20 16:31:17 2023 +0200 glob commit71eef01e5d
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 14 15:45:55 2023 +0200 folder handling commitb8ed5556cb
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Tue Jun 13 17:16:36 2023 +0200 open folder commit274a3c7cbd
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 13 12:51:12 2023 +0200 loading engine commit01433bae4f
Author: filip mertens <filip.mertens@ethereum.org> Date: Tue Jun 13 07:47:33 2023 +0200 isogit commit038c63e362
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 19:39:39 2023 +0200 dgit commit336d191c20
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 17:22:30 2023 +0200 fs support commit87c498fa91
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 13:33:28 2023 +0200 refactor commit34abe45ebc
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:54:41 2023 +0200 fs integration commit2e39267532
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:54:27 2023 +0200 update test1 commit6534e8aa47
Author: filip mertens <filip.mertens@ethereum.org> Date: Wed Jun 7 11:49:03 2023 +0200 update test app commit272bf13344
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat Jun 3 13:42:10 2023 +0200 other builder commita2d511d445
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 2 13:45:26 2023 +0200 cleanup commit15d1a0189c
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Fri Jun 2 13:33:38 2023 +0200 terminals commit3cd5c54262
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 18:07:07 2023 +0200 serve commitb8e344da37
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:52:42 2023 +0200 engine:activatePlugin commit05f5bad529
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:50:57 2023 +0200 refactor commit12b51f44bd
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:47:48 2023 +0200 rename commit5bff29ea3a
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:46:39 2023 +0200 cleanup commit0b878097cb
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:43:04 2023 +0200 refactor commitf68b189969
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:32:35 2023 +0200 close watcher commit03163d303e
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 11:28:33 2023 +0200 close watcher commit946135bde0
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:47:55 2023 +0200 refactor commiteae0a0f696
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:41:22 2023 +0200 refactor commit93973004e9
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 10:02:37 2023 +0200 refactor commit171518f49a
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 00:39:47 2023 +0200 change fs commitf275f0ae8a
Author: filip mertens <filip.mertens@ethereum.org> Date: Thu Jun 1 00:29:36 2023 +0200 add test app commitc9297a4d8f
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Tue May 30 08:31:18 2023 +0200 fix path commitacf2e01aa8
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Mon May 29 18:39:08 2023 +0200 app build & serve commit4819477577
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Mon May 29 09:52:36 2023 +0200 rm trash commitb4c9657e9b
Author: bunsenstraat <filip.mertens@ethereum.org> Date: Sun May 28 10:56:08 2023 +0200 new app commit542e20ea9c
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 15:29:49 2023 +0200 fix webpack commit94c19ac6d4
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 14:56:07 2023 +0200 prepackage commit2102b30044
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:56:08 2023 +0200 testing commit8220097fb7
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:38:54 2023 +0200 dev server commitf966c4cb9b
Author: filip mertens <filip.mertens@ethereum.org> Date: Sat May 27 13:17:18 2023 +0200 experiments
parent
ec495fb4f3
commit
753732a007
@ -0,0 +1,9 @@ |
|||||||
|
// SPDX-License-Identifier: GPL-3.0 |
||||||
|
|
||||||
|
pragma solidity >=0.4.22 <0.9.0; |
||||||
|
|
||||||
|
library TestsAccounts { |
||||||
|
function getAccount(uint index) pure public returns (address) { |
||||||
|
return address(0); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,225 @@ |
|||||||
|
// SPDX-License-Identifier: GPL-3.0 |
||||||
|
|
||||||
|
pragma solidity >=0.4.22 <0.9.0; |
||||||
|
|
||||||
|
library Assert { |
||||||
|
|
||||||
|
event AssertionEvent( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventUint( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
uint256 returned, |
||||||
|
uint256 expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventInt( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
int256 returned, |
||||||
|
int256 expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventBool( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
bool returned, |
||||||
|
bool expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventAddress( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
address returned, |
||||||
|
address expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventBytes32( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
bytes32 returned, |
||||||
|
bytes32 expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventString( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
string returned, |
||||||
|
string expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventUintInt( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
uint256 returned, |
||||||
|
int256 expected |
||||||
|
); |
||||||
|
|
||||||
|
event AssertionEventIntUint( |
||||||
|
bool passed, |
||||||
|
string message, |
||||||
|
string methodName, |
||||||
|
int256 returned, |
||||||
|
uint256 expected |
||||||
|
); |
||||||
|
|
||||||
|
function ok(bool a, string memory message) public returns (bool result) { |
||||||
|
result = a; |
||||||
|
emit AssertionEvent(result, message, "ok"); |
||||||
|
} |
||||||
|
|
||||||
|
function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a == b); |
||||||
|
emit AssertionEventUint(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function equal(int256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a == b); |
||||||
|
emit AssertionEventInt(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function equal(bool a, bool b, string memory message) public returns (bool result) { |
||||||
|
result = (a == b); |
||||||
|
emit AssertionEventBool(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: only for certain versions of solc |
||||||
|
//function equal(fixed a, fixed b, string message) public returns (bool result) { |
||||||
|
// result = (a == b); |
||||||
|
// emit AssertionEvent(result, message); |
||||||
|
//} |
||||||
|
|
||||||
|
// TODO: only for certain versions of solc |
||||||
|
//function equal(ufixed a, ufixed b, string message) public returns (bool result) { |
||||||
|
// result = (a == b); |
||||||
|
// emit AssertionEvent(result, message); |
||||||
|
//} |
||||||
|
|
||||||
|
function equal(address a, address b, string memory message) public returns (bool result) { |
||||||
|
result = (a == b); |
||||||
|
emit AssertionEventAddress(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { |
||||||
|
result = (a == b); |
||||||
|
emit AssertionEventBytes32(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function equal(string memory a, string memory b, string memory message) public returns (bool result) { |
||||||
|
result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); |
||||||
|
emit AssertionEventString(result, message, "equal", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a != b); |
||||||
|
emit AssertionEventUint(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a != b); |
||||||
|
emit AssertionEventInt(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function notEqual(bool a, bool b, string memory message) public returns (bool result) { |
||||||
|
result = (a != b); |
||||||
|
emit AssertionEventBool(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: only for certain versions of solc |
||||||
|
//function notEqual(fixed a, fixed b, string message) public returns (bool result) { |
||||||
|
// result = (a != b); |
||||||
|
// emit AssertionEvent(result, message); |
||||||
|
//} |
||||||
|
|
||||||
|
// TODO: only for certain versions of solc |
||||||
|
//function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { |
||||||
|
// result = (a != b); |
||||||
|
// emit AssertionEvent(result, message); |
||||||
|
//} |
||||||
|
|
||||||
|
function notEqual(address a, address b, string memory message) public returns (bool result) { |
||||||
|
result = (a != b); |
||||||
|
emit AssertionEventAddress(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { |
||||||
|
result = (a != b); |
||||||
|
emit AssertionEventBytes32(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { |
||||||
|
result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); |
||||||
|
emit AssertionEventString(result, message, "notEqual", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
/*----------------- Greater than --------------------*/ |
||||||
|
function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a > b); |
||||||
|
emit AssertionEventUint(result, message, "greaterThan", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a > b); |
||||||
|
emit AssertionEventInt(result, message, "greaterThan", a, b); |
||||||
|
} |
||||||
|
// TODO: safely compare between uint and int |
||||||
|
function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
if(b < int(0)) { |
||||||
|
// int is negative uint "a" always greater |
||||||
|
result = true; |
||||||
|
} else { |
||||||
|
result = (a > uint(b)); |
||||||
|
} |
||||||
|
emit AssertionEventUintInt(result, message, "greaterThan", a, b); |
||||||
|
} |
||||||
|
function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
if(a < int(0)) { |
||||||
|
// int is negative uint "b" always greater |
||||||
|
result = false; |
||||||
|
} else { |
||||||
|
result = (uint(a) > b); |
||||||
|
} |
||||||
|
emit AssertionEventIntUint(result, message, "greaterThan", a, b); |
||||||
|
} |
||||||
|
/*----------------- Lesser than --------------------*/ |
||||||
|
function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a < b); |
||||||
|
emit AssertionEventUint(result, message, "lesserThan", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
result = (a < b); |
||||||
|
emit AssertionEventInt(result, message, "lesserThan", a, b); |
||||||
|
} |
||||||
|
// TODO: safely compare between uint and int |
||||||
|
function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { |
||||||
|
if(b < int(0)) { |
||||||
|
// int is negative int "b" always lesser |
||||||
|
result = false; |
||||||
|
} else { |
||||||
|
result = (a < uint(b)); |
||||||
|
} |
||||||
|
emit AssertionEventUintInt(result, message, "lesserThan", a, b); |
||||||
|
} |
||||||
|
|
||||||
|
function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { |
||||||
|
if(a < int(0)) { |
||||||
|
// int is negative int "a" always lesser |
||||||
|
result = true; |
||||||
|
} else { |
||||||
|
result = (uint(a) < b); |
||||||
|
} |
||||||
|
emit AssertionEventIntUint(result, message, "lesserThan", a, b); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
import FileProvider from "./fileProvider" |
||||||
|
|
||||||
|
|
||||||
|
declare global { |
||||||
|
interface Window { |
||||||
|
remixFileSystem: any |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export class ElectronProvider extends FileProvider { |
||||||
|
_appManager: any |
||||||
|
constructor(appManager) { |
||||||
|
super('') |
||||||
|
this._appManager = appManager |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async init() { |
||||||
|
this._appManager.on('fs', 'change', (event, path) => { |
||||||
|
switch (event) { |
||||||
|
case 'add': |
||||||
|
this.event.emit('fileAdded', path) |
||||||
|
break |
||||||
|
case 'unlink': |
||||||
|
this.event.emit('fileRemoved', path) |
||||||
|
break |
||||||
|
case 'change': |
||||||
|
this.get(path, (_error, content) => { |
||||||
|
this.event.emit('fileExternallyChanged', path, content, false) |
||||||
|
}) |
||||||
|
break |
||||||
|
case 'rename': |
||||||
|
this.event.emit('fileRenamed', path) |
||||||
|
break |
||||||
|
case 'addDir': |
||||||
|
this.event.emit('folderAdded', path) |
||||||
|
break |
||||||
|
case 'unlinkDir': |
||||||
|
this.event.emit('fileRemoved', path) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// isDirectory is already included
|
||||||
|
// this is a more efficient version of the default implementation
|
||||||
|
async resolveDirectory(path, cb) { |
||||||
|
path = this.removePrefix(path) |
||||||
|
if (path.indexOf('/') !== 0) path = '/' + path |
||||||
|
try { |
||||||
|
const files = await window.remixFileSystem.readdir(path) |
||||||
|
const ret = {} |
||||||
|
if (files) { |
||||||
|
for (const element of files) { |
||||||
|
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
|
||||||
|
const file = element.file.replace(/^\/|\/$/g, '') // remove first and last slash
|
||||||
|
const absPath = (path === '/' ? '' : path) + '/' + file |
||||||
|
ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: element.isDirectory } |
||||||
|
// ^ ret does not accept path starting with '/'
|
||||||
|
} |
||||||
|
} |
||||||
|
//console.log(ret, 'ret resolveDirectory ELECTRON')
|
||||||
|
if (cb) cb(null, ret) |
||||||
|
return ret |
||||||
|
} catch (error) { |
||||||
|
if (cb) cb(error, null) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes the folder recursively |
||||||
|
* @param {*} path is the folder to be removed |
||||||
|
*/ |
||||||
|
async remove(path: string) { |
||||||
|
console.log('remove', path) |
||||||
|
try { |
||||||
|
await window.remixFileSystem.rmdir(path) |
||||||
|
return true |
||||||
|
} catch (error) { |
||||||
|
console.log(error) |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
export class electronConfig extends ElectronPlugin { |
||||||
|
constructor() { |
||||||
|
super({ |
||||||
|
displayName: 'electronconfig', |
||||||
|
name: 'electronconfig', |
||||||
|
description: 'electronconfig', |
||||||
|
}) |
||||||
|
this.methods = [] |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
let workingDir = null |
||||||
|
|
||||||
|
const fixPath = (path: string) => { |
||||||
|
return path |
||||||
|
} |
||||||
|
|
||||||
|
export class fsPlugin extends ElectronPlugin { |
||||||
|
public fs: any |
||||||
|
public fsSync: any |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super({ |
||||||
|
displayName: 'fs', |
||||||
|
name: 'fs', |
||||||
|
description: 'fs', |
||||||
|
}) |
||||||
|
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'setWorkingDir', 'getRecentFolders', 'glob', 'openWindow'] |
||||||
|
|
||||||
|
// List of commands all filesystems are expected to provide. `rm` is not
|
||||||
|
// included since it may not exist and must be handled as a special case
|
||||||
|
const commands = [ |
||||||
|
'readFile', |
||||||
|
'writeFile', |
||||||
|
'mkdir', |
||||||
|
'rmdir', |
||||||
|
'unlink', |
||||||
|
'stat', |
||||||
|
'lstat', |
||||||
|
'readdir', |
||||||
|
'readlink', |
||||||
|
'symlink', |
||||||
|
] |
||||||
|
|
||||||
|
this.fs = { |
||||||
|
|
||||||
|
exists: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
const exists = await this.call('fs', 'exists', path) |
||||||
|
return exists |
||||||
|
}, |
||||||
|
rmdir: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'rmdir', path) |
||||||
|
}, |
||||||
|
readdir: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
const files = await this.call('fs', 'readdir', path) |
||||||
|
return files |
||||||
|
}, |
||||||
|
glob: async (path: string, pattern: string, options?: any) => { |
||||||
|
path = fixPath(path) |
||||||
|
const files = await this.call('fs', 'glob', path, pattern, options) |
||||||
|
return files |
||||||
|
}, |
||||||
|
unlink: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'unlink', path) |
||||||
|
}, |
||||||
|
mkdir: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'mkdir', path) |
||||||
|
}, |
||||||
|
readFile: async (path: string, options) => { |
||||||
|
try { |
||||||
|
path = fixPath(path) |
||||||
|
const file = await this.call('fs', 'readFile', path, options) |
||||||
|
return file |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
} |
||||||
|
, |
||||||
|
rename: async (from: string, to: string) => { |
||||||
|
return await this.call('fs', 'rename', from, to) |
||||||
|
}, |
||||||
|
writeFile: async (path: string, content: string, options: any) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'writeFile', path, content, options) |
||||||
|
} |
||||||
|
, |
||||||
|
stat: async (path: string) => { |
||||||
|
try { |
||||||
|
path = fixPath(path) |
||||||
|
const stat = await this.call('fs', 'stat', path) |
||||||
|
if(!stat) return undefined |
||||||
|
stat.isDirectory = () => stat.isDirectoryValue |
||||||
|
stat.isFile = () => !stat.isDirectoryValue |
||||||
|
return stat |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
}, |
||||||
|
lstat: async (path: string) => { |
||||||
|
try { |
||||||
|
path = fixPath(path) |
||||||
|
const stat = await this.call('fs', 'lstat', path) |
||||||
|
if(!stat) return undefined |
||||||
|
stat.isDirectory = () => stat.isDirectoryValue |
||||||
|
stat.isFile = () => !stat.isDirectoryValue |
||||||
|
return stat |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
}, |
||||||
|
readlink: async (path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'readlink', path) |
||||||
|
}, |
||||||
|
symlink: async (target: string, path: string) => { |
||||||
|
path = fixPath(path) |
||||||
|
return await this.call('fs', 'symlink', target, path) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async onActivation() { |
||||||
|
|
||||||
|
(window as any).remixFileSystem = this.fs; |
||||||
|
|
||||||
|
|
||||||
|
this.on('fs', 'workingDirChanged', async (path: string) => { |
||||||
|
workingDir = path |
||||||
|
await this.call('fileManager', 'refresh') |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
export class isoGitPlugin extends ElectronPlugin { |
||||||
|
constructor() { |
||||||
|
super({ |
||||||
|
displayName: 'isogit', |
||||||
|
name: 'isogit', |
||||||
|
description: 'isogit', |
||||||
|
}) |
||||||
|
this.methods = [] |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async onActivation(): Promise<void> { |
||||||
|
|
||||||
|
setTimeout(async () => { |
||||||
|
const version = await this.call('isogit', 'version') |
||||||
|
if(version){ |
||||||
|
//this.call('terminal', 'log', version)
|
||||||
|
}else{ |
||||||
|
//this.call('terminal', 'log', 'Git is not installed on the system. Using builtin git instead. Performance will be affected. It is better to install git on the system and configure the credentials to connect to GitHub etc.')
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
}, 5000) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
export class electronTemplates extends ElectronPlugin { |
||||||
|
constructor() { |
||||||
|
super({ |
||||||
|
displayName: 'electronTemplates', |
||||||
|
name: 'electronTemplates', |
||||||
|
description: 'templates', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
|
||||||
|
}
|
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||||
|
|
||||||
|
export class xtermPlugin extends ElectronPlugin { |
||||||
|
constructor(){ |
||||||
|
super({ |
||||||
|
displayName: 'xterm', |
||||||
|
name: 'xterm', |
||||||
|
description: 'xterm', |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import * as templateWithContent from '@remix-project/remix-ws-templates' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'remix-templates', |
||||||
|
displayName: 'remix-templates', |
||||||
|
description: 'Remix Templates plugin', |
||||||
|
methods: ['getTemplate', 'loadTemplateInNewWindow'], |
||||||
|
} |
||||||
|
|
||||||
|
export class TemplatesPlugin extends Plugin { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
async getTemplate (template: string, opts?: any) { |
||||||
|
const templateList = Object.keys(templateWithContent) |
||||||
|
if (!templateList.includes(template)) return |
||||||
|
// @ts-ignore
|
||||||
|
const files = await templateWithContent[template](opts) |
||||||
|
return files |
||||||
|
} |
||||||
|
// electron only method
|
||||||
|
async loadTemplateInNewWindow (template: string, opts?: any) { |
||||||
|
const files = await this.getTemplate(template, opts) |
||||||
|
this.call('electronTemplates', 'loadTemplateInNewWindow', files) |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"electron.openFolder": "Open Folder", |
||||||
|
"electron.recentFolders": "Recent Folders" |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
# REMIX DESKTOP |
||||||
|
|
||||||
|
## Development |
||||||
|
|
||||||
|
In the main repo yarn, then run yarn serve |
||||||
|
In this directory apps/remixdesktop, yarn, then run: yarn start:dev to boot the electron app |
||||||
|
In chrome chrome://inspect/#devices you can add localhost:5858 to the network targets and then you will see an inspect button electron/js2c/browser_init |
||||||
|
file:/// |
||||||
|
You can use that to inspect the output of the electron app |
||||||
|
|
||||||
|
If you run into issues with yarn when native node modules are being rebuilt you need |
||||||
|
- Windows: install Visual Studio Tools with Desktop Development C++ enabled in the Workloads |
||||||
|
- MacOS: install Xcode or Xcode Command Line Tools |
||||||
|
- Linux: unknown, probably a C++ compiler |
||||||
|
|
||||||
|
## Builds |
||||||
|
|
||||||
|
Builds can be found in the artefacts of CI. |
||||||
|
|
||||||
|
## CI |
||||||
|
|
||||||
|
CI will only run the builds is the branch is master or contains the word: desktop |
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,105 @@ |
|||||||
|
{ |
||||||
|
"name": "remixdesktop", |
||||||
|
"version": "1.0.0", |
||||||
|
"main": "build/main.js", |
||||||
|
"license": "MIT", |
||||||
|
"type": "commonjs", |
||||||
|
"description": "Remix IDE Desktop", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git+https://github.com/ethereum/remix-project.git" |
||||||
|
}, |
||||||
|
"author": { |
||||||
|
"name": "Remix Team", |
||||||
|
"email": "remix@ethereum.org" |
||||||
|
}, |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/ethereum/remix-project/issues" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/ethereum/remix-project#readme", |
||||||
|
"appId": "org.ethereum.remixdesktop", |
||||||
|
"mac": { |
||||||
|
"category": "public.app-category.productivity" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"start:dev": "tsc && cross-env NODE_ENV=development electron --inspect=5858 .", |
||||||
|
"start:production": "tsc && cross-env NODE_ENV=production electron .", |
||||||
|
"dist": "tsc && electron-builder", |
||||||
|
"postinstall": "electron-builder install-app-deps" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@electron/rebuild": "^3.2.13", |
||||||
|
"cross-env": "^7.0.3", |
||||||
|
"electron": "^25.0.1", |
||||||
|
"electron-builder": "^23.6.0", |
||||||
|
"typescript": "^5.1.3" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@remix-project/remix-ws-templates": "^1.0.19", |
||||||
|
"@remixproject/engine": "0.3.37", |
||||||
|
"@remixproject/engine-electron": "0.3.37", |
||||||
|
"@remixproject/plugin": "0.3.37", |
||||||
|
"@remixproject/plugin-electron": "0.3.37", |
||||||
|
"@vscode/ripgrep": "^1.15.4", |
||||||
|
"chokidar": "^3.5.3", |
||||||
|
"glob": "9.3.5", |
||||||
|
"isomorphic-git": "^1.24.2", |
||||||
|
"node-pty": "^0.10.1" |
||||||
|
}, |
||||||
|
"build": { |
||||||
|
"productName": "Remix IDE", |
||||||
|
"appId": "org.ethereum.remix-ide", |
||||||
|
"asar": true, |
||||||
|
"icon": "assets/icon.png", |
||||||
|
"files": [ |
||||||
|
"build/**/*" |
||||||
|
], |
||||||
|
"mac": { |
||||||
|
"category": "public.app-category.productivity", |
||||||
|
"target": [ |
||||||
|
{ |
||||||
|
"target": "zip", |
||||||
|
"arch": [ |
||||||
|
"x64", |
||||||
|
"arm64" |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"target": "dmg", |
||||||
|
"arch": [ |
||||||
|
"x64", |
||||||
|
"arm64", |
||||||
|
"universal" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"darkModeSupport": true |
||||||
|
}, |
||||||
|
"dmg": { |
||||||
|
"writeUpdateInfo": false |
||||||
|
}, |
||||||
|
"nsis": { |
||||||
|
"createDesktopShortcut": "always", |
||||||
|
"allowToChangeInstallationDirectory": true, |
||||||
|
"oneClick": false, |
||||||
|
"shortcutName": "Remix IDE", |
||||||
|
"differentialPackage": false |
||||||
|
}, |
||||||
|
"win": { |
||||||
|
"target": [ |
||||||
|
"nsis" |
||||||
|
] |
||||||
|
}, |
||||||
|
"linux": { |
||||||
|
"target": [ |
||||||
|
"deb", |
||||||
|
"snap", |
||||||
|
"AppImage" |
||||||
|
], |
||||||
|
"category": "WebBrowser" |
||||||
|
}, |
||||||
|
"directories": { |
||||||
|
"output": "release" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
import { Engine, PluginManager } from '@remixproject/engine'; |
||||||
|
import { ipcMain } from 'electron'; |
||||||
|
import { FSPlugin } from './plugins/fsPlugin'; |
||||||
|
import { app } from 'electron'; |
||||||
|
import { XtermPlugin } from './plugins/xtermPlugin'; |
||||||
|
import git from 'isomorphic-git' |
||||||
|
import { IsoGitPlugin } from './plugins/isoGitPlugin'; |
||||||
|
import { ConfigPlugin } from './plugins/configPlugin'; |
||||||
|
import { TemplatesPlugin } from './plugins/templates'; |
||||||
|
|
||||||
|
const engine = new Engine() |
||||||
|
const appManager = new PluginManager() |
||||||
|
const fsPlugin = new FSPlugin() |
||||||
|
const xtermPlugin = new XtermPlugin() |
||||||
|
const isoGitPlugin = new IsoGitPlugin() |
||||||
|
const configPlugin = new ConfigPlugin() |
||||||
|
const templatesPlugin = new TemplatesPlugin() |
||||||
|
engine.register(appManager) |
||||||
|
engine.register(fsPlugin) |
||||||
|
engine.register(xtermPlugin) |
||||||
|
engine.register(isoGitPlugin) |
||||||
|
engine.register(configPlugin) |
||||||
|
engine.register(templatesPlugin) |
||||||
|
|
||||||
|
appManager.activatePlugin('electronconfig') |
||||||
|
appManager.activatePlugin('fs') |
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle('manager:activatePlugin', async (event, plugin) => { |
||||||
|
return await appManager.call(plugin, 'createClient', event.sender.id) |
||||||
|
}) |
||||||
|
|
||||||
|
ipcMain.on('fs:openFolder', async (event) => { |
||||||
|
fsPlugin.openFolder(event) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
ipcMain.on('terminal:new', async (event) => { |
||||||
|
xtermPlugin.new(event) |
||||||
|
}) |
||||||
|
|
||||||
|
ipcMain.on('template:open', async (event) => { |
||||||
|
templatesPlugin.openTemplate(event) |
||||||
|
}) |
||||||
|
|
||||||
|
ipcMain.on('git:startclone', async (event) => { |
||||||
|
isoGitPlugin.startClone(event) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle('getWebContentsID', (event, message) => { |
||||||
|
return event.sender.id |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
app.on('before-quit', async (event) => { |
||||||
|
await appManager.call('fs', 'removeCloseListener') |
||||||
|
await appManager.call('fs', 'closeWatch') |
||||||
|
await appManager.call('xterm', 'closeTerminals') |
||||||
|
}) |
@ -0,0 +1,114 @@ |
|||||||
|
import { app, BrowserWindow, dialog, Menu, MenuItem } from 'electron'; |
||||||
|
import path from 'path'; |
||||||
|
|
||||||
|
|
||||||
|
export let isPackaged = false; |
||||||
|
|
||||||
|
if ( |
||||||
|
process.mainModule && |
||||||
|
process.mainModule.filename.indexOf('app.asar') !== -1 |
||||||
|
) { |
||||||
|
isPackaged = true; |
||||||
|
} else if (process.argv.filter(a => a.indexOf('app.asar') !== -1).length > 0) { |
||||||
|
isPackaged = true; |
||||||
|
} |
||||||
|
|
||||||
|
// get system home dir
|
||||||
|
const homeDir = app.getPath('userData') |
||||||
|
|
||||||
|
const windowSet = new Set<BrowserWindow>([]); |
||||||
|
export const createWindow = async (dir?: string): Promise<void> => { |
||||||
|
// Create the browser window.
|
||||||
|
const mainWindow = new BrowserWindow({ |
||||||
|
height: 800, |
||||||
|
width: 1024, |
||||||
|
webPreferences: { |
||||||
|
preload: path.join(__dirname, 'preload.js') |
||||||
|
}, |
||||||
|
}); |
||||||
|
if (dir && dir.endsWith('/')) dir = dir.slice(0, -1) |
||||||
|
let params = dir ? `?opendir=${encodeURIComponent(dir)}` : ''; |
||||||
|
// and load the index.html of the app.
|
||||||
|
mainWindow.loadURL( |
||||||
|
process.env.NODE_ENV === 'production' || isPackaged ? `file://${__dirname}/remix-ide/index.html` + params : |
||||||
|
'http://localhost:8080' + params) |
||||||
|
|
||||||
|
mainWindow.maximize(); |
||||||
|
|
||||||
|
if (dir) { |
||||||
|
mainWindow.setTitle(dir) |
||||||
|
} |
||||||
|
|
||||||
|
// on close
|
||||||
|
mainWindow.on('close', (event) => { |
||||||
|
windowSet.delete(mainWindow) |
||||||
|
}) |
||||||
|
|
||||||
|
windowSet.add(mainWindow) |
||||||
|
//mainWindow.webContents.openDevTools();
|
||||||
|
}; |
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', async () => { |
||||||
|
require('./engine') |
||||||
|
}); |
||||||
|
|
||||||
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
// for applications and their menu bar to stay active until the user quits
|
||||||
|
// explicitly with Cmd + Q.
|
||||||
|
app.on('window-all-closed', () => { |
||||||
|
if (process.platform !== 'darwin') { |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
app.on('activate', () => { |
||||||
|
// On OS X it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) { |
||||||
|
createWindow(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const showAbout = () => { |
||||||
|
|
||||||
|
|
||||||
|
void dialog.showMessageBox({ |
||||||
|
title: `About Remix`, |
||||||
|
message: `Remix`, |
||||||
|
detail: `Remix`, |
||||||
|
buttons: [], |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
// In this file you can include the rest of your app's specific main process
|
||||||
|
// code. You can also put them in separate files and import them here.
|
||||||
|
|
||||||
|
const isMac = process.platform === 'darwin' |
||||||
|
|
||||||
|
import FileMenu from './menus/file'; |
||||||
|
import darwinMenu from './menus/darwin'; |
||||||
|
import WindowMenu from './menus/window'; |
||||||
|
import EditMenu from './menus/edit'; |
||||||
|
import GitMenu from './menus/git'; |
||||||
|
import ViewMenu from './menus/view'; |
||||||
|
import { execCommand } from './menus/commands'; |
||||||
|
|
||||||
|
|
||||||
|
const commandKeys: Record<string, string> = { |
||||||
|
'window:new': 'CmdOrCtrl+N', |
||||||
|
'folder:open': 'CmdOrCtrl+O', |
||||||
|
}; |
||||||
|
|
||||||
|
const menu = [...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []), |
||||||
|
FileMenu(commandKeys, execCommand), |
||||||
|
GitMenu(commandKeys, execCommand), |
||||||
|
EditMenu(commandKeys, execCommand), |
||||||
|
ViewMenu(commandKeys, execCommand), |
||||||
|
WindowMenu(commandKeys, execCommand, []), |
||||||
|
] |
||||||
|
|
||||||
|
Menu.setApplicationMenu(Menu.buildFromTemplate(menu)) |
||||||
|
|
@ -0,0 +1,39 @@ |
|||||||
|
import {app, Menu, BrowserWindow, ipcMain} from 'electron'; |
||||||
|
import { createWindow } from '../main'; |
||||||
|
|
||||||
|
|
||||||
|
const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = { |
||||||
|
'window:new': () => { |
||||||
|
// If window is created on the same tick, it will consume event too
|
||||||
|
setTimeout(createWindow, 0); |
||||||
|
}, |
||||||
|
'folder:open': (focusedWindow) => { |
||||||
|
if (focusedWindow) { |
||||||
|
ipcMain.emit('fs:openFolder', focusedWindow.webContents.id); |
||||||
|
} |
||||||
|
}, |
||||||
|
'terminal:new': (focusedWindow) => { |
||||||
|
if (focusedWindow) { |
||||||
|
ipcMain.emit('terminal:new', focusedWindow.webContents.id); |
||||||
|
} |
||||||
|
}, |
||||||
|
'template:open': (focusedWindow) => { |
||||||
|
if (focusedWindow) { |
||||||
|
ipcMain.emit('template:open', focusedWindow.webContents.id); |
||||||
|
} |
||||||
|
}, |
||||||
|
'git:startclone': (focusedWindow) => { |
||||||
|
if (focusedWindow) { |
||||||
|
ipcMain.emit('git:startclone', focusedWindow.webContents.id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
export const execCommand = (command: string, focusedWindow?: BrowserWindow) => { |
||||||
|
const fn = commands[command]; |
||||||
|
if (fn) { |
||||||
|
fn(focusedWindow); |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,28 @@ |
|||||||
|
// This menu label is overrided by OSX to be the appName
|
||||||
|
// The label is set to appName here so it matches actual behavior
|
||||||
|
import {app, BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void, |
||||||
|
showAbout: () => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
return { |
||||||
|
label: `${app.name}`, |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'About Remix', |
||||||
|
click() { |
||||||
|
showAbout(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator' |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'quit', |
||||||
|
label: 'Quit Remix' |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,53 @@ |
|||||||
|
import { BrowserWindow, MenuItemConstructorOptions } from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
) => { |
||||||
|
const submenu: MenuItemConstructorOptions[] = [ |
||||||
|
{ |
||||||
|
role: 'copy', |
||||||
|
command: 'editor:copy', |
||||||
|
accelerator: commandKeys['editor:copy'], |
||||||
|
registerAccelerator: true |
||||||
|
} as any, |
||||||
|
{ |
||||||
|
role: 'paste', |
||||||
|
accelerator: commandKeys['editor:paste'], |
||||||
|
registerAccelerator: true |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'cut', |
||||||
|
accelerator: commandKeys['editor:cut'], |
||||||
|
registerAccelerator: true
|
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'selectAll', |
||||||
|
accelerator: commandKeys['editor:selectall'], |
||||||
|
registerAccelerator: true |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'undo', |
||||||
|
accelerator: commandKeys['editor:undo'], |
||||||
|
registerAccelerator: true |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
if (process.platform !== 'darwin') { |
||||||
|
submenu.push( |
||||||
|
{ type: 'separator' }, |
||||||
|
{ |
||||||
|
label: 'Preferences...', |
||||||
|
accelerator: commandKeys['window:preferences'], |
||||||
|
click() { |
||||||
|
execCommand('window:preferences'); |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'Edit', |
||||||
|
submenu |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,49 @@ |
|||||||
|
import { BrowserWindow, MenuItemConstructorOptions } from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
const isMac = process.platform === 'darwin'; |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'File', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'New Window', |
||||||
|
accelerator: commandKeys['window:new'], |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('window:new', focusedWindow); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Open Folder', |
||||||
|
accelerator: commandKeys['folder:open'], |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('folder:open', focusedWindow); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Load Template in New Window', |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('template:open', focusedWindow); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'recentDocuments', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
role: 'clearRecentDocuments' |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'close', |
||||||
|
accelerator: commandKeys['window:close'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'quit', |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,20 @@ |
|||||||
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
const isMac = process.platform === 'darwin'; |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'Git', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'Clone Repository in New Window', |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('git:startclone', focusedWindow); |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,26 @@ |
|||||||
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
const isMac = process.platform === 'darwin'; |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'REMIX', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'Close', |
||||||
|
accelerator: commandKeys['pane:close'], |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('pane:close', focusedWindow); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: isMac ? 'Close Window' : 'Quit', |
||||||
|
role: 'close', |
||||||
|
accelerator: commandKeys['window:close'] |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,20 @@ |
|||||||
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
const isMac = process.platform === 'darwin'; |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'Terminal', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'New Terminal', |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('terminal:new', focusedWindow); |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,87 @@ |
|||||||
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
const isMac = process.platform === 'darwin'; |
||||||
|
|
||||||
|
return { |
||||||
|
label: 'View', |
||||||
|
submenu: [ |
||||||
|
{ |
||||||
|
label: 'Toggle Developer Tools', |
||||||
|
accelerator: (function() { |
||||||
|
if (process.platform === 'darwin') |
||||||
|
return 'Alt+Command+I'; |
||||||
|
else |
||||||
|
return 'Ctrl+Shift+I'; |
||||||
|
})(), |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow) |
||||||
|
focusedWindow.webContents.toggleDevTools(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Reload', |
||||||
|
accelerator: 'CmdOrCtrl+R', |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow) |
||||||
|
focusedWindow.reload(); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Toggle Full Screen', |
||||||
|
accelerator: (function() { |
||||||
|
if (process.platform === 'darwin') |
||||||
|
|
||||||
|
return 'Ctrl+Command+F'; |
||||||
|
else |
||||||
|
return 'F11'; |
||||||
|
})(), |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow) |
||||||
|
focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Zoom In', |
||||||
|
accelerator: 'CmdOrCtrl+=', |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow){ |
||||||
|
let factor = focusedWindow.webContents.getZoomFactor() |
||||||
|
if (factor < 4) { |
||||||
|
factor = factor + 0.25 |
||||||
|
focusedWindow.webContents.setZoomFactor(factor) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Zoom Out', |
||||||
|
accelerator: 'CmdOrCtrl+-', |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow){ |
||||||
|
let factor = focusedWindow.webContents.getZoomFactor() |
||||||
|
if (factor > 1.25) { |
||||||
|
factor = factor - 1.25 |
||||||
|
focusedWindow.webContents.setZoomFactor(factor) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Reset Zoom', |
||||||
|
accelerator: 'CmdOrCtrl+0', |
||||||
|
click: function(item, focusedWindow) { |
||||||
|
if (focusedWindow) |
||||||
|
{ |
||||||
|
focusedWindow.webContents.setZoomFactor(1) |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
] |
||||||
|
}; |
||||||
|
}; |
@ -0,0 +1,63 @@ |
|||||||
|
import { BrowserWindow, MenuItemConstructorOptions } from 'electron'; |
||||||
|
|
||||||
|
export default ( |
||||||
|
commandKeys: Record<string, string>, |
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void, |
||||||
|
openedWindows: BrowserWindow[] |
||||||
|
): MenuItemConstructorOptions => { |
||||||
|
|
||||||
|
const submenu: MenuItemConstructorOptions[] = [ |
||||||
|
{ |
||||||
|
role: 'minimize', |
||||||
|
accelerator: commandKeys['window:minimize'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator' |
||||||
|
}, |
||||||
|
{ |
||||||
|
// It's the same thing as clicking the green traffc-light on macOS
|
||||||
|
role: 'zoom', |
||||||
|
accelerator: commandKeys['window:zoom'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator' |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator' |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'front' |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Toggle Always on Top', |
||||||
|
click: (item, focusedWindow) => { |
||||||
|
execCommand('window:toggleKeepOnTop', focusedWindow); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
role: 'togglefullscreen', |
||||||
|
accelerator: commandKeys['window:toggleFullScreen'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator' |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
if(openedWindows.length > 1) { |
||||||
|
submenu.push({ |
||||||
|
label: 'Close', |
||||||
|
accelerator: commandKeys['pane:close'], |
||||||
|
click(item, focusedWindow) { |
||||||
|
execCommand('pane:close', focusedWindow); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { |
||||||
|
role: 'window', |
||||||
|
id: 'window', |
||||||
|
submenu |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,50 @@ |
|||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
|
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { readConfig, writeConfig } from "../utils/config"; |
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
displayName: 'electronconfig', |
||||||
|
name: 'electronconfig', |
||||||
|
description: 'Electron Config' |
||||||
|
} |
||||||
|
|
||||||
|
export class ConfigPlugin extends ElectronBasePlugin { |
||||||
|
clients: ConfigPluginClient[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, ConfigPluginClient) |
||||||
|
this.methods = [...super.methods, 'writeConfig', 'readConfig'] |
||||||
|
} |
||||||
|
|
||||||
|
async writeConfig(data: any): Promise<void> { |
||||||
|
writeConfig(data) |
||||||
|
} |
||||||
|
|
||||||
|
async readConfig(webContentsId: any): Promise<any> { |
||||||
|
return readConfig() |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'electronconfig', |
||||||
|
displayName: 'electronconfig', |
||||||
|
description: 'Electron Config', |
||||||
|
methods: ['writeConfig', 'readConfig'] |
||||||
|
} |
||||||
|
|
||||||
|
class ConfigPluginClient extends ElectronBasePluginClient { |
||||||
|
|
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile) |
||||||
|
} |
||||||
|
|
||||||
|
async writeConfig(data: any): Promise<void> { |
||||||
|
writeConfig(data) |
||||||
|
} |
||||||
|
|
||||||
|
async readConfig(): Promise<any> { |
||||||
|
return readConfig() |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,377 @@ |
|||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
import fs from 'fs/promises' |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import chokidar from 'chokidar' |
||||||
|
import { dialog } from "electron"; |
||||||
|
import { createWindow, isPackaged } from "../main"; |
||||||
|
import { writeConfig } from "../utils/config"; |
||||||
|
import { glob, GlobOptions } from 'glob' |
||||||
|
import { Path } from 'path-scurry' |
||||||
|
import path from "path"; |
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
displayName: 'fs', |
||||||
|
name: 'fs', |
||||||
|
description: 'fs' |
||||||
|
} |
||||||
|
|
||||||
|
const convertPathToPosix = (pathName: string): string => { |
||||||
|
return pathName.split(path.sep).join(path.posix.sep) |
||||||
|
} |
||||||
|
|
||||||
|
const getBaseName = (pathName: string): string => { |
||||||
|
return path.basename(pathName) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export class FSPlugin extends ElectronBasePlugin { |
||||||
|
clients: FSPluginClient[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, FSPluginClient) |
||||||
|
this.methods = [...super.methods, 'closeWatch', 'removeCloseListener'] |
||||||
|
} |
||||||
|
|
||||||
|
async onActivation(): Promise<void> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
const openedFolders = config && config.openedFolders || [] |
||||||
|
this.call('electronconfig', 'writeConfig', { 'openedFolders': openedFolders }) |
||||||
|
const foldersToDelete: string[] = [] |
||||||
|
if (openedFolders && openedFolders.length) { |
||||||
|
for (const folder of openedFolders) { |
||||||
|
try { |
||||||
|
const stat = await fs.stat(folder) |
||||||
|
if (stat.isDirectory()) { |
||||||
|
createWindow(folder) |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.log('error opening folder', folder, e) |
||||||
|
foldersToDelete.push(folder) |
||||||
|
} |
||||||
|
} |
||||||
|
if (foldersToDelete.length) { |
||||||
|
const newFolders = openedFolders.filter((f: string) => !foldersToDelete.includes(f)) |
||||||
|
this.call('electronconfig', 'writeConfig', { 'recentFolders': newFolders }) |
||||||
|
} |
||||||
|
}else{ |
||||||
|
createWindow() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async removeCloseListener(): Promise<void> { |
||||||
|
for (const client of this.clients) { |
||||||
|
client.window.removeAllListeners() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async closeWatch(): Promise<void> { |
||||||
|
for (const client of this.clients) { |
||||||
|
await client.closeWatch() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
openFolder(webContentsId: any): void { |
||||||
|
const client = this.clients.find(c => c.webContentsId === webContentsId) |
||||||
|
if (client) { |
||||||
|
client.openFolder() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'fs', |
||||||
|
displayName: 'fs', |
||||||
|
description: 'fs', |
||||||
|
methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir', 'openFolder', 'openFolderInSameWindow', 'getRecentFolders', 'removeRecentFolder', 'glob', 'openWindow', 'selectFolder'] |
||||||
|
} |
||||||
|
|
||||||
|
class FSPluginClient extends ElectronBasePluginClient { |
||||||
|
watcher: chokidar.FSWatcher |
||||||
|
workingDir: string = '' |
||||||
|
trackDownStreamUpdate: Record<string, string> = {} |
||||||
|
|
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile) |
||||||
|
this.onload(() => { |
||||||
|
if(!isPackaged) { |
||||||
|
this.window.webContents.openDevTools() |
||||||
|
} |
||||||
|
this.window.on('close', async () => { |
||||||
|
await this.removeFromOpenedFolders(this.workingDir) |
||||||
|
await this.closeWatch() |
||||||
|
}) |
||||||
|
|
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// best for non recursive
|
||||||
|
async readdir(path: string): Promise<string[]> { |
||||||
|
// call node fs.readdir
|
||||||
|
if (!path) return [] |
||||||
|
const startTime = Date.now() |
||||||
|
const files = await fs.readdir(this.fixPath(path), { |
||||||
|
withFileTypes: true |
||||||
|
}) |
||||||
|
|
||||||
|
const result: any[] = [] |
||||||
|
for (const file of files) { |
||||||
|
const isDirectory = file.isDirectory() |
||||||
|
result.push({ |
||||||
|
file: file.name, |
||||||
|
isDirectory |
||||||
|
}) |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
async glob(path: string, pattern: string, options?: GlobOptions): Promise<string[] | Path[]> { |
||||||
|
|
||||||
|
path = convertPathToPosix(this.fixPath(path)) |
||||||
|
const files = await glob(path + pattern, { |
||||||
|
withFileTypes: true, |
||||||
|
...options |
||||||
|
}) |
||||||
|
|
||||||
|
const result: any[] = [] |
||||||
|
|
||||||
|
for (const file of files) { |
||||||
|
let pathWithoutWorkingDir = (file as Path).path.replace(this.workingDir, '') |
||||||
|
if (!pathWithoutWorkingDir.endsWith('/')) { |
||||||
|
pathWithoutWorkingDir = pathWithoutWorkingDir + '/' |
||||||
|
} |
||||||
|
if (pathWithoutWorkingDir.startsWith('/')) { |
||||||
|
pathWithoutWorkingDir = pathWithoutWorkingDir.slice(1) |
||||||
|
} |
||||||
|
if(pathWithoutWorkingDir.startsWith('\\')) { |
||||||
|
pathWithoutWorkingDir = pathWithoutWorkingDir.slice(1) |
||||||
|
} |
||||||
|
result.push({ |
||||||
|
path: pathWithoutWorkingDir + (file as Path).name, |
||||||
|
isDirectory: (file as Path).isDirectory(), |
||||||
|
}) |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
async readFile(path: string, options: any): Promise<string | undefined> { |
||||||
|
// hacky fix for TS error
|
||||||
|
if (!path) return undefined |
||||||
|
try { |
||||||
|
return (fs as any).readFile(this.fixPath(path), options) |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async writeFile(path: string, content: string, options: any): Promise<void> { |
||||||
|
this.trackDownStreamUpdate[path] = content |
||||||
|
return (fs as any).writeFile(this.fixPath(path), content, options) |
||||||
|
} |
||||||
|
|
||||||
|
async mkdir(path: string): Promise<void> { |
||||||
|
return fs.mkdir(this.fixPath(path)) |
||||||
|
} |
||||||
|
|
||||||
|
async rmdir(path: string): Promise<void> { |
||||||
|
return fs.rm(this.fixPath(path), { |
||||||
|
recursive: true |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async unlink(path: string): Promise<void> { |
||||||
|
return fs.unlink(this.fixPath(path)) |
||||||
|
} |
||||||
|
|
||||||
|
async rename(oldPath: string, newPath: string): Promise<void> { |
||||||
|
return fs.rename(this.fixPath(oldPath), this.fixPath(newPath)) |
||||||
|
} |
||||||
|
|
||||||
|
async stat(path: string): Promise<any> { |
||||||
|
try { |
||||||
|
const stat = await fs.stat(this.fixPath(path)) |
||||||
|
const isDirectory = stat.isDirectory() |
||||||
|
return { |
||||||
|
...stat, |
||||||
|
isDirectoryValue: isDirectory |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async lstat(path: string): Promise<any> { |
||||||
|
try { |
||||||
|
const stat = await fs.lstat(this.fixPath(path)) |
||||||
|
const isDirectory = stat.isDirectory() |
||||||
|
return { |
||||||
|
...stat, |
||||||
|
isDirectoryValue: isDirectory |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
return undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async exists(path: string): Promise<boolean> { |
||||||
|
return fs.access(this.fixPath(path)).then(() => true).catch(() => false) |
||||||
|
} |
||||||
|
|
||||||
|
async currentPath(): Promise<string> { |
||||||
|
return process.cwd() |
||||||
|
} |
||||||
|
|
||||||
|
async watch(): Promise<void> { |
||||||
|
if (this.watcher) this.watcher.close() |
||||||
|
this.watcher = |
||||||
|
chokidar.watch(this.workingDir, { |
||||||
|
ignorePermissionErrors: true, ignoreInitial: true, |
||||||
|
ignored: [ |
||||||
|
'**/node_modules/**', |
||||||
|
'**/.git/index.lock', // this file is created and unlinked all the time when git is running on Windows
|
||||||
|
] |
||||||
|
}).on('all', async (eventName, path, stats) => { |
||||||
|
|
||||||
|
|
||||||
|
let pathWithoutPrefix = path.replace(this.workingDir, '') |
||||||
|
pathWithoutPrefix = convertPathToPosix(pathWithoutPrefix) |
||||||
|
if (pathWithoutPrefix.startsWith('/')) pathWithoutPrefix = pathWithoutPrefix.slice(1) |
||||||
|
|
||||||
|
if (eventName === 'change') { |
||||||
|
// remove workingDir from path
|
||||||
|
const newContent = await fs.readFile(path, 'utf-8') |
||||||
|
|
||||||
|
const currentContent = this.trackDownStreamUpdate[pathWithoutPrefix] |
||||||
|
|
||||||
|
if (currentContent !== newContent) { |
||||||
|
try { |
||||||
|
this.emit('change', eventName, pathWithoutPrefix) |
||||||
|
} catch (e) { |
||||||
|
console.log('error emitting change', e) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
try { |
||||||
|
|
||||||
|
this.emit('change', eventName, pathWithoutPrefix) |
||||||
|
} catch (e) { |
||||||
|
console.log('error emitting change', e) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async closeWatch(): Promise<void> { |
||||||
|
if (this.watcher) this.watcher.close() |
||||||
|
} |
||||||
|
|
||||||
|
async updateRecentFolders(path: string): Promise<void> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
config.recentFolders = config.recentFolders || [] |
||||||
|
config.recentFolders = config.recentFolders.filter((p: string) => p !== path) |
||||||
|
config.recentFolders.push(path) |
||||||
|
writeConfig(config) |
||||||
|
} |
||||||
|
|
||||||
|
async updateOpenedFolders(path: string): Promise<void> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
config.openedFolders = config.openedFolders || [] |
||||||
|
config.openedFolders = config.openedFolders.filter((p: string) => p !== path) |
||||||
|
config.openedFolders.push(path) |
||||||
|
writeConfig(config) |
||||||
|
} |
||||||
|
|
||||||
|
async removeFromOpenedFolders(path: string): Promise<void> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
config.openedFolders = config.openedFolders || [] |
||||||
|
config.openedFolders = config.openedFolders.filter((p: string) => p !== path) |
||||||
|
writeConfig(config) |
||||||
|
} |
||||||
|
|
||||||
|
async getRecentFolders(): Promise<string[]> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
return config.recentFolders || [] |
||||||
|
} |
||||||
|
|
||||||
|
async removeRecentFolder(path: string): Promise<void> { |
||||||
|
const config = await this.call('electronconfig' as any, 'readConfig') |
||||||
|
config.recentFolders = config.recentFolders || [] |
||||||
|
config.recentFolders = config.recentFolders.filter((p: string) => p !== path) |
||||||
|
writeConfig(config) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async selectFolder(path?: string): Promise<string> { |
||||||
|
let dirs: string[] | undefined |
||||||
|
if (!path) { |
||||||
|
dirs = dialog.showOpenDialogSync(this.window, { |
||||||
|
properties: ['openDirectory', 'createDirectory', "showHiddenFiles"] |
||||||
|
}) |
||||||
|
} |
||||||
|
path = dirs && dirs.length && dirs[0] ? dirs[0] : path |
||||||
|
if (!path) return '' |
||||||
|
return path |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async openFolder(path?: string): Promise<void> { |
||||||
|
let dirs: string[] | undefined |
||||||
|
if (!path) { |
||||||
|
dirs = dialog.showOpenDialogSync(this.window, { |
||||||
|
properties: ['openDirectory', 'createDirectory', "showHiddenFiles"] |
||||||
|
}) |
||||||
|
} |
||||||
|
path = dirs && dirs.length && dirs[0] ? dirs[0] : path |
||||||
|
if (!path) return |
||||||
|
|
||||||
|
await this.updateRecentFolders(path) |
||||||
|
await this.updateOpenedFolders(path) |
||||||
|
this.openWindow(path) |
||||||
|
} |
||||||
|
|
||||||
|
async openFolderInSameWindow(path?: string): Promise<void> { |
||||||
|
let dirs: string[] | undefined |
||||||
|
if (!path) { |
||||||
|
dirs = dialog.showOpenDialogSync(this.window, { |
||||||
|
properties: ['openDirectory', 'createDirectory', "showHiddenFiles"] |
||||||
|
}) |
||||||
|
} |
||||||
|
path = dirs && dirs.length && dirs[0] ? dirs[0] : path |
||||||
|
if (!path) return |
||||||
|
this.workingDir = path |
||||||
|
await this.updateRecentFolders(path) |
||||||
|
await this.updateOpenedFolders(path) |
||||||
|
this.window.setTitle(this.workingDir) |
||||||
|
this.watch() |
||||||
|
this.emit('workingDirChanged', path) |
||||||
|
} |
||||||
|
|
||||||
|
async setWorkingDir(path: string): Promise<void> { |
||||||
|
this.workingDir = path |
||||||
|
await this.updateRecentFolders(path) |
||||||
|
await this.updateOpenedFolders(path) |
||||||
|
this.window.setTitle(getBaseName(this.workingDir)) |
||||||
|
this.watch() |
||||||
|
this.emit('workingDirChanged', path) |
||||||
|
await this.call('fileManager', 'closeAllFiles') |
||||||
|
} |
||||||
|
|
||||||
|
fixPath(path: string): string { |
||||||
|
if (this.workingDir === '') throw new Error('workingDir is not set') |
||||||
|
if (path) { |
||||||
|
if (path.startsWith('/')) { |
||||||
|
path = path.slice(1) |
||||||
|
} |
||||||
|
} |
||||||
|
path = this.workingDir + (!this.workingDir.endsWith('/') ? '/' : '') + path |
||||||
|
return path |
||||||
|
} |
||||||
|
|
||||||
|
openWindow(path: string): void { |
||||||
|
createWindow(path) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,368 @@ |
|||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
import fs from 'fs/promises' |
||||||
|
import git from 'isomorphic-git' |
||||||
|
import { dialog } from "electron"; |
||||||
|
import http from 'isomorphic-git/http/web' |
||||||
|
import { gitProxy } from "../tools/git"; |
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
name: 'isogit', |
||||||
|
displayName: 'isogit', |
||||||
|
description: 'isogit plugin', |
||||||
|
} |
||||||
|
|
||||||
|
export class IsoGitPlugin extends ElectronBasePlugin { |
||||||
|
clients: IsoGitPluginClient[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, IsoGitPluginClient) |
||||||
|
} |
||||||
|
|
||||||
|
startClone(webContentsId: any): void { |
||||||
|
const client = this.clients.find(c => c.webContentsId === webContentsId) |
||||||
|
if (client) { |
||||||
|
client.startClone() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const parseInput = (input: any) => { |
||||||
|
return { |
||||||
|
corsProxy: 'https://corsproxy.remixproject.org/', |
||||||
|
http, |
||||||
|
onAuth: (url: any) => { |
||||||
|
url |
||||||
|
const auth = { |
||||||
|
username: input.token, |
||||||
|
password: '' |
||||||
|
} |
||||||
|
return auth |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'isogit', |
||||||
|
displayName: 'isogit', |
||||||
|
description: 'isogit plugin', |
||||||
|
methods: ['init', 'localStorageUsed', 'version', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'reset', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'openFolder'] |
||||||
|
} |
||||||
|
|
||||||
|
class IsoGitPluginClient extends ElectronBasePluginClient { |
||||||
|
workingDir: string = '' |
||||||
|
gitIsInstalled: boolean = false |
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile) |
||||||
|
this.onload(() => { |
||||||
|
this.on('fs' as any, 'workingDirChanged', async (path: string) => { |
||||||
|
this.workingDir = path |
||||||
|
this.gitIsInstalled = await gitProxy.version() ? true : false |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async version() { |
||||||
|
return gitProxy.version() |
||||||
|
} |
||||||
|
|
||||||
|
async getGitConfig() { |
||||||
|
return { |
||||||
|
fs, |
||||||
|
dir: this.workingDir, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async status(cmd: any) { |
||||||
|
|
||||||
|
|
||||||
|
if (this.workingDir === '') { |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
const status = await gitProxy.status(this.workingDir) |
||||||
|
return status |
||||||
|
} |
||||||
|
|
||||||
|
const status = await git.statusMatrix({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
//console.log('STATUS', status, await this.getGitConfig())
|
||||||
|
return status |
||||||
|
} |
||||||
|
|
||||||
|
async log(cmd: any) { |
||||||
|
|
||||||
|
/* we will use isomorphic git for now |
||||||
|
if(this.gitIsInstalled){ |
||||||
|
const log = await gitProxy.log(this.workingDir, cmd.ref) |
||||||
|
console.log('LOG', log) |
||||||
|
return log |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
if (this.workingDir === '') { |
||||||
|
return [] |
||||||
|
} |
||||||
|
|
||||||
|
const log = await git.log({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return log |
||||||
|
} |
||||||
|
|
||||||
|
async add(cmd: any) { |
||||||
|
const add = await git.add({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return add |
||||||
|
} |
||||||
|
|
||||||
|
async rm(cmd: any) { |
||||||
|
|
||||||
|
const rm = await git.remove({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return rm |
||||||
|
} |
||||||
|
|
||||||
|
async reset(cmd: any) { |
||||||
|
|
||||||
|
const reset = await git.resetIndex({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return reset |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async commit(cmd: any) { |
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
const status = await gitProxy.commit(this.workingDir, cmd.message) |
||||||
|
return status |
||||||
|
} |
||||||
|
|
||||||
|
const commit = await git.commit({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return commit |
||||||
|
} |
||||||
|
|
||||||
|
async init(input: any) { |
||||||
|
await git.init({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
defaultBranch: (input && input.branch) || 'main' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async branch(cmd: any) { |
||||||
|
|
||||||
|
const branch = await git.branch({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return branch |
||||||
|
} |
||||||
|
|
||||||
|
async lsfiles(cmd: any) { |
||||||
|
|
||||||
|
const lsfiles = await git.listFiles({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
return lsfiles |
||||||
|
} |
||||||
|
|
||||||
|
async resolveref(cmd: any) { |
||||||
|
|
||||||
|
const resolveref = await git.resolveRef({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return resolveref |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async readblob(cmd: any) { |
||||||
|
|
||||||
|
const readblob = await git.readBlob({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return readblob |
||||||
|
} |
||||||
|
|
||||||
|
async checkout(cmd: any) { |
||||||
|
|
||||||
|
const checkout = await git.checkout({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return checkout |
||||||
|
} |
||||||
|
|
||||||
|
async push(cmd: any) { |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
await gitProxy.push(this.workingDir, cmd.remote, cmd.ref, cmd.remoteRef, cmd.force) |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
const push = await git.push({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd, |
||||||
|
...parseInput(cmd.input) |
||||||
|
}) |
||||||
|
return push |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async pull(cmd: any) { |
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
await gitProxy.pull(this.workingDir, cmd.remote, cmd.ref, cmd.remoteRef) |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
const pull = await git.pull({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd, |
||||||
|
...parseInput(cmd.input) |
||||||
|
}) |
||||||
|
|
||||||
|
return pull |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fetch(cmd: any) { |
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
await gitProxy.fetch(this.workingDir, cmd.remote, cmd.remoteRef) |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
const fetch = await git.fetch({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd, |
||||||
|
...parseInput(cmd.input) |
||||||
|
}) |
||||||
|
|
||||||
|
return fetch |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async clone(cmd: any) { |
||||||
|
|
||||||
|
|
||||||
|
if (this.gitIsInstalled) { |
||||||
|
await gitProxy.clone(cmd.url, cmd.dir) |
||||||
|
|
||||||
|
} else { |
||||||
|
try { |
||||||
|
const clone = await git.clone({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd, |
||||||
|
...parseInput(cmd.input), |
||||||
|
dir: cmd.dir || this.workingDir |
||||||
|
}) |
||||||
|
|
||||||
|
return clone |
||||||
|
} catch (e) { |
||||||
|
console.log('CLONE ERROR', e) |
||||||
|
throw e |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async addremote(cmd: any) { |
||||||
|
|
||||||
|
const addremote = await git.addRemote({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return addremote |
||||||
|
} |
||||||
|
|
||||||
|
async delremote(cmd: any) { |
||||||
|
|
||||||
|
const delremote = await git.deleteRemote({ |
||||||
|
...await this.getGitConfig(), |
||||||
|
...cmd |
||||||
|
}) |
||||||
|
|
||||||
|
return delremote |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
remotes = async () => { |
||||||
|
let remotes = [] |
||||||
|
remotes = await git.listRemotes({ ...await this.getGitConfig() }) |
||||||
|
return remotes |
||||||
|
} |
||||||
|
|
||||||
|
async currentbranch() { |
||||||
|
try { |
||||||
|
const defaultConfig = await this.getGitConfig() |
||||||
|
const name = await git.currentBranch(defaultConfig) |
||||||
|
|
||||||
|
return name |
||||||
|
} catch (e) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async branches() { |
||||||
|
try { |
||||||
|
let cmd: any = { ...await this.getGitConfig() } |
||||||
|
const remotes = await this.remotes() |
||||||
|
let branches = [] |
||||||
|
branches = (await git.listBranches(cmd)).map((branch) => { return { remote: undefined, name: branch } }) |
||||||
|
for (const remote of remotes) { |
||||||
|
cmd = { |
||||||
|
...cmd, |
||||||
|
remote: remote.remote |
||||||
|
} |
||||||
|
|
||||||
|
const remotebranches = (await git.listBranches(cmd)).map((branch) => { return { remote: remote.remote, name: branch } }) |
||||||
|
branches = [...branches, ...remotebranches] |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return branches |
||||||
|
} catch (e) { |
||||||
|
return [] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async startClone() { |
||||||
|
this.call('filePanel' as any, 'clone') |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@ |
|||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
import * as templateWithContent from '@remix-project/remix-ws-templates' |
||||||
|
import fs from 'fs/promises' |
||||||
|
import { createWindow } from "../main"; |
||||||
|
import path from 'path' |
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
name: 'electronTemplates', |
||||||
|
displayName: 'electronTemplates', |
||||||
|
description: 'Templates plugin', |
||||||
|
} |
||||||
|
|
||||||
|
export class TemplatesPlugin extends ElectronBasePlugin { |
||||||
|
clients: TemplatesPluginClient[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, TemplatesPluginClient) |
||||||
|
} |
||||||
|
|
||||||
|
openTemplate(webContentsId: any): void { |
||||||
|
const client = this.clients.find(c => c.webContentsId === webContentsId) |
||||||
|
if (client) { |
||||||
|
client.openTemplate() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'electronTemplates', |
||||||
|
displayName: 'electronTemplates', |
||||||
|
description: 'Templates plugin', |
||||||
|
methods: ['loadTemplateInNewWindow', 'openTemplate'], |
||||||
|
} |
||||||
|
|
||||||
|
export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721' |
||||||
|
|
||||||
|
class TemplatesPluginClient extends ElectronBasePluginClient { |
||||||
|
|
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile) |
||||||
|
} |
||||||
|
|
||||||
|
async loadTemplateInNewWindow (files: any) { |
||||||
|
|
||||||
|
let folder = await this.call('fs' as any, 'selectFolder') |
||||||
|
if (!folder || folder === '') return |
||||||
|
// @ts-ignore
|
||||||
|
|
||||||
|
for (const file in files) { |
||||||
|
try { |
||||||
|
if(!folder.endsWith('/')) folder += '/' |
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(folder + file), { recursive: true}) |
||||||
|
await fs.writeFile(folder + file, files[file], { |
||||||
|
encoding: 'utf8' |
||||||
|
}) |
||||||
|
} catch (error) { |
||||||
|
console.error(error) |
||||||
|
} |
||||||
|
} |
||||||
|
createWindow(folder) |
||||||
|
} |
||||||
|
|
||||||
|
async openTemplate(){ |
||||||
|
this.call('filePanel' as any, 'loadTemplate') |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,153 @@ |
|||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" |
||||||
|
|
||||||
|
import os from 'os'; |
||||||
|
import * as pty from "node-pty" |
||||||
|
|
||||||
|
import process from 'node:process'; |
||||||
|
import { userInfo } from 'node:os'; |
||||||
|
import { findExecutable } from "../utils/findExecutable"; |
||||||
|
|
||||||
|
export const detectDefaultShell = () => { |
||||||
|
const { env } = process; |
||||||
|
|
||||||
|
if (process.platform === 'win32') { |
||||||
|
return env.SHELL || 'powershell.exe'; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
const { shell } = userInfo(); |
||||||
|
if (shell) { |
||||||
|
return shell; |
||||||
|
} |
||||||
|
} catch { } |
||||||
|
|
||||||
|
if (process.platform === 'darwin') { |
||||||
|
return env.SHELL || '/bin/zsh'; |
||||||
|
} |
||||||
|
|
||||||
|
return env.SHELL || '/bin/sh'; |
||||||
|
}; |
||||||
|
|
||||||
|
// Stores default shell when imported.
|
||||||
|
const defaultShell = detectDefaultShell(); |
||||||
|
|
||||||
|
|
||||||
|
export default defaultShell; |
||||||
|
|
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
name: 'xterm', |
||||||
|
displayName: 'xterm', |
||||||
|
description: 'xterm plugin', |
||||||
|
} |
||||||
|
|
||||||
|
export class XtermPlugin extends ElectronBasePlugin { |
||||||
|
clients: XtermPluginClient[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile, clientProfile, XtermPluginClient) |
||||||
|
this.methods = [...super.methods, 'closeTerminals'] |
||||||
|
} |
||||||
|
|
||||||
|
new(webContentsId: any): void { |
||||||
|
const client = this.clients.find(c => c.webContentsId === webContentsId) |
||||||
|
if (client) { |
||||||
|
client.new() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async closeTerminals(): Promise<void> { |
||||||
|
for (const client of this.clients) { |
||||||
|
await client.closeAll() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
const clientProfile: Profile = { |
||||||
|
name: 'xterm', |
||||||
|
displayName: 'xterm', |
||||||
|
description: 'xterm plugin', |
||||||
|
methods: ['createTerminal', 'close', 'keystroke', 'getShells'] |
||||||
|
} |
||||||
|
|
||||||
|
class XtermPluginClient extends ElectronBasePluginClient { |
||||||
|
|
||||||
|
terminals: pty.IPty[] = [] |
||||||
|
constructor(webContentsId: number, profile: Profile) { |
||||||
|
super(webContentsId, profile) |
||||||
|
this.onload(() => { |
||||||
|
this.emit('loaded') |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async keystroke(key: string, pid: number): Promise<void> { |
||||||
|
this.terminals[pid].write(key) |
||||||
|
} |
||||||
|
|
||||||
|
async getShells(): Promise<string[]> { |
||||||
|
if(os.platform() === 'win32') { |
||||||
|
const bash = await findExecutable('bash.exe') |
||||||
|
if(bash) { |
||||||
|
const shells = ['powershell.exe', 'cmd.exe', ...bash] |
||||||
|
// filter out duplicates
|
||||||
|
return shells.filter((v, i, a) => a.indexOf(v) === i) |
||||||
|
} |
||||||
|
return ['powershell.exe', 'cmd.exe'] |
||||||
|
} |
||||||
|
return [defaultShell] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async createTerminal(path?: string, shell?: string): Promise<number> { |
||||||
|
|
||||||
|
|
||||||
|
// filter undefined out of the env
|
||||||
|
const env = Object.keys(process.env) |
||||||
|
.filter(key => process.env[key] !== undefined) |
||||||
|
.reduce((env, key) => { |
||||||
|
env[key] = process.env[key] || ''; |
||||||
|
return env; |
||||||
|
}, {} as Record<string, string>); |
||||||
|
|
||||||
|
|
||||||
|
const ptyProcess = pty.spawn(shell || defaultShell, [], { |
||||||
|
name: 'xterm-color', |
||||||
|
cols: 80, |
||||||
|
rows: 20, |
||||||
|
cwd: path || process.cwd(), |
||||||
|
env: env |
||||||
|
}); |
||||||
|
|
||||||
|
ptyProcess.onData((data: string) => { |
||||||
|
this.sendData(data, ptyProcess.pid); |
||||||
|
}) |
||||||
|
this.terminals[ptyProcess.pid] = ptyProcess |
||||||
|
|
||||||
|
return ptyProcess.pid |
||||||
|
} |
||||||
|
|
||||||
|
async close(pid: number): Promise<void> { |
||||||
|
this.terminals[pid].kill() |
||||||
|
delete this.terminals[pid] |
||||||
|
this.emit('close', pid) |
||||||
|
} |
||||||
|
|
||||||
|
async closeAll(): Promise<void> { |
||||||
|
for (const pid in this.terminals) { |
||||||
|
this.terminals[pid].kill() |
||||||
|
delete this.terminals[pid] |
||||||
|
this.emit('close', pid) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async sendData(data: string, pid: number) { |
||||||
|
this.emit('data', data, pid) |
||||||
|
} |
||||||
|
|
||||||
|
async new(): Promise<void> { |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
|
||||||
|
import { Message } from '@remixproject/plugin-utils' |
||||||
|
import { contextBridge, ipcRenderer } from 'electron' |
||||||
|
|
||||||
|
console.log('preload.ts') |
||||||
|
|
||||||
|
/* preload script needs statically defined API for each plugin */ |
||||||
|
|
||||||
|
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates'] |
||||||
|
|
||||||
|
let webContentsId: number | undefined |
||||||
|
|
||||||
|
ipcRenderer.invoke('getWebContentsID').then((id: number) => { |
||||||
|
webContentsId = id |
||||||
|
}) |
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', { |
||||||
|
activatePlugin: (name: string) => { |
||||||
|
return ipcRenderer.invoke('manager:activatePlugin', name) |
||||||
|
}, |
||||||
|
|
||||||
|
getWindowId: () => ipcRenderer.invoke('getWindowID'), |
||||||
|
|
||||||
|
plugins: exposedPLugins.map(name => { |
||||||
|
return { |
||||||
|
name, |
||||||
|
on: (cb:any) => ipcRenderer.on(`${name}:send`, cb), |
||||||
|
send: (message: Partial<Message>) => { |
||||||
|
ipcRenderer.send(`${name}:on:${webContentsId}`, message) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,151 @@ |
|||||||
|
import { exec } from 'child_process'; |
||||||
|
import { CommitObject, ReadCommitResult } from 'isomorphic-git'; |
||||||
|
import { promisify } from 'util'; |
||||||
|
const execAsync = promisify(exec); |
||||||
|
|
||||||
|
const statusTransFormMatrix = (status: string) => { |
||||||
|
switch (status) { |
||||||
|
case '??': |
||||||
|
return [0, 2, 0] |
||||||
|
case 'A ': |
||||||
|
return [0, 2, 2] |
||||||
|
case 'M ': |
||||||
|
return [1, 2, 2] |
||||||
|
case 'MM': |
||||||
|
return [1, 2, 3] |
||||||
|
case ' M': |
||||||
|
return [1, 2, 0] |
||||||
|
case ' D': |
||||||
|
return [1, 0, 1] |
||||||
|
case 'D ': |
||||||
|
return [1, 0, 0] |
||||||
|
case 'AM': |
||||||
|
return [0, 2, 3] |
||||||
|
default: |
||||||
|
return [-1, -1, -1] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const gitProxy = { |
||||||
|
|
||||||
|
version: async () => { |
||||||
|
try { |
||||||
|
const result = await execAsync('git --version'); |
||||||
|
return result.stdout |
||||||
|
} catch (error) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
clone: async (url: string, path: string) => { |
||||||
|
const { stdout, stderr } = await execAsync(`git clone ${url} ${path}`); |
||||||
|
}, |
||||||
|
|
||||||
|
async push(path: string, remote: string, src: string, branch: string, force: boolean = false) { |
||||||
|
const { stdout, stderr } = await execAsync(`git push ${force ? ' -f' : ''} ${remote} ${src}:${branch}`, { cwd: path }); |
||||||
|
}, |
||||||
|
|
||||||
|
async pull(path: string, remote: string, src: string, branch: string) { |
||||||
|
const { stdout, stderr } = await execAsync(`git pull ${remote} ${src}:${branch}`, { cwd: path }); |
||||||
|
}, |
||||||
|
|
||||||
|
async fetch(path: string, remote: string, branch: string) { |
||||||
|
const { stdout, stderr } = await execAsync(`git fetch ${remote} ${branch}`, { cwd: path }); |
||||||
|
}, |
||||||
|
|
||||||
|
async commit(path: string, message: string) { |
||||||
|
|
||||||
|
await execAsync(`git commit -m ${message}`, { cwd: path }); |
||||||
|
const { stdout, stderr } = await execAsync(`git rev-parse HEAD`, { cwd: path }); |
||||||
|
return stdout; |
||||||
|
|
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
status: async (path: string) => { |
||||||
|
const result = await execAsync('git status --porcelain -uall', { cwd: path }) |
||||||
|
//console.log('git status --porcelain -uall', result.stdout)
|
||||||
|
// parse the result.stdout
|
||||||
|
const lines = result.stdout.split('\n') |
||||||
|
const files: any = [] |
||||||
|
const fileNames: any = [] |
||||||
|
//console.log('lines', lines)
|
||||||
|
lines.forEach((line: string) => { |
||||||
|
// get the first two characters of the line
|
||||||
|
const status = line.slice(0, 2) |
||||||
|
|
||||||
|
const file = line.split(' ').pop() |
||||||
|
|
||||||
|
//console.log('line', line)
|
||||||
|
if (status && file) { |
||||||
|
fileNames.push(file) |
||||||
|
files.push([ |
||||||
|
file, |
||||||
|
...statusTransFormMatrix(status) |
||||||
|
]) |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
// sort files by first column
|
||||||
|
files.sort((a: any, b: any) => { |
||||||
|
if (a[0] < b[0]) { |
||||||
|
return -1 |
||||||
|
} |
||||||
|
if (a[0] > b[0]) { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
return 0 |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
return files |
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
// buggy, doesn't work properly yet on windows
|
||||||
|
log: async (path: string, ref: string) => { |
||||||
|
const result = await execAsync('git log ' + ref + ' --pretty=format:"{ oid:%H, message:"%s", author:"%an", email: "%ae", timestamp:"%at", tree: "%T", committer: "%cn", committer-email: "%ce", committer-timestamp: "%ct", parent: "%P" }" -n 20', { cwd: path }) |
||||||
|
console.log('git log', result.stdout) |
||||||
|
const lines = result.stdout.split('\n') |
||||||
|
const commits: ReadCommitResult[] = [] |
||||||
|
console.log('lines', lines) |
||||||
|
lines.forEach((line: string) => { |
||||||
|
console.log('line', normalizeJson(line)) |
||||||
|
line = normalizeJson(line) |
||||||
|
const data = JSON.parse(line) |
||||||
|
let commit: ReadCommitResult = {} as ReadCommitResult |
||||||
|
commit.oid = data.oid |
||||||
|
commit.commit = {} as CommitObject |
||||||
|
commit.commit.message = data.message |
||||||
|
commit.commit.tree = data.tree |
||||||
|
commit.commit.committer = {} as any |
||||||
|
commit.commit.committer.name = data.committer |
||||||
|
commit.commit.committer.email = data['committer-email'] |
||||||
|
commit.commit.committer.timestamp = data['committer-timestamp'] |
||||||
|
commit.commit.author = {} as any |
||||||
|
commit.commit.author.name = data.author |
||||||
|
commit.commit.author.email = data.email |
||||||
|
commit.commit.author.timestamp = data.timestamp |
||||||
|
commit.commit.parent = [data.parent] |
||||||
|
console.log('commit', commit) |
||||||
|
commits.push(commit) |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
return commits |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function normalizeJson(str: string) { |
||||||
|
return str.replace(/[\s\n\r\t]/gs, '').replace(/,([}\]])/gs, '$1') |
||||||
|
.replace(/([,{\[]|)(?:("|'|)([\w_\- ]+)\2:|)("|'|)(.*?)\4([,}\]])/gs, (str, start, q1, index, q2, item, end) => { |
||||||
|
item = item.replace(/"/gsi, '').trim(); |
||||||
|
if (index) { index = '"' + index.replace(/"/gsi, '').trim() + '"'; } |
||||||
|
if (!item.match(/^[0-9]+(\.[0-9]+|)$/) && !['true', 'false'].includes(item)) { item = '"' + item + '"'; } |
||||||
|
if (index) { return start + index + ':' + item + end; } |
||||||
|
return start + item + end; |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import fs from 'fs' |
||||||
|
import os from 'os' |
||||||
|
import path from 'path' |
||||||
|
|
||||||
|
const cacheDir = path.join(os.homedir(), '.cache_remix_ide') |
||||||
|
|
||||||
|
console.log('cacheDir', cacheDir) |
||||||
|
|
||||||
|
try { |
||||||
|
if (!fs.existsSync(cacheDir)) { |
||||||
|
fs.mkdirSync(cacheDir) |
||||||
|
} |
||||||
|
if(!fs.existsSync(cacheDir + '/remixdesktop.json')) { |
||||||
|
fs.writeFileSync(cacheDir + '/remixdesktop.json', JSON.stringify({})) |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
export const writeConfig = (data: any) => { |
||||||
|
const cache = readConfig() |
||||||
|
try { |
||||||
|
fs.writeFileSync(cacheDir + '/remixdesktop.json', JSON.stringify({ ...cache, ...data })) |
||||||
|
} catch (e) { |
||||||
|
console.error('Can\'t write config file', e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const readConfig = () => { |
||||||
|
if (fs.existsSync(cacheDir + '/remixdesktop.json')) { |
||||||
|
try { |
||||||
|
// read the cache file
|
||||||
|
const cache = fs.readFileSync(cacheDir + '/remixdesktop.json') |
||||||
|
const data = JSON.parse(cache.toString()) |
||||||
|
return data |
||||||
|
} catch (e) { |
||||||
|
console.error('Can\'t read config file', e) |
||||||
|
} |
||||||
|
} |
||||||
|
return undefined |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
import path from "path"; |
||||||
|
import process from "process"; |
||||||
|
import { Stats } from "fs"; |
||||||
|
import fs from 'fs/promises' |
||||||
|
|
||||||
|
export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string[]> { |
||||||
|
// If we have an absolute path then we take it.
|
||||||
|
if (path.isAbsolute(command)) { |
||||||
|
return [command]; |
||||||
|
} |
||||||
|
if (cwd === undefined) { |
||||||
|
cwd = process.cwd(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const dir = path.dirname(command); |
||||||
|
if (dir !== '.') { |
||||||
|
// We have a directory and the directory is relative (see above). Make the path absolute
|
||||||
|
// to the current working directory.
|
||||||
|
return [path.join(cwd, command)]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (paths === undefined && typeof process.env['PATH'] === 'string') {
|
||||||
|
paths = (process && process.env['PATH'] && process.env['PATH'].split(path.delimiter)) || []; |
||||||
|
} |
||||||
|
// No PATH environment. Make path absolute to the cwd.
|
||||||
|
if (paths === undefined || paths.length === 0) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function fileExists(path: string): Promise<boolean> { |
||||||
|
|
||||||
|
try { |
||||||
|
if (await fs.stat(path)) { |
||||||
|
let statValue: Stats | undefined; |
||||||
|
try { |
||||||
|
statValue = await fs.stat(path); |
||||||
|
} catch (e: any) { |
||||||
|
if (e.message.startsWith('EACCES')) { |
||||||
|
// it might be symlink
|
||||||
|
statValue = await fs.lstat(path); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return statValue ? !statValue.isDirectory() : false; |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// We have a simple file name. We get the path variable from the env
|
||||||
|
// and try to find the executable on the path.
|
||||||
|
|
||||||
|
const results = []; |
||||||
|
|
||||||
|
for (const pathEntry of paths) { |
||||||
|
|
||||||
|
// The path entry is absolute.
|
||||||
|
let fullPath: string; |
||||||
|
if (path.isAbsolute(pathEntry)) { |
||||||
|
fullPath = path.join(pathEntry, command); |
||||||
|
} else { |
||||||
|
fullPath = path.join(cwd, pathEntry, command); |
||||||
|
} |
||||||
|
if (await fileExists(fullPath)) { |
||||||
|
results.push(fullPath); |
||||||
|
} |
||||||
|
let withExtension = fullPath + '.com'; |
||||||
|
if (await fileExists(withExtension)) { |
||||||
|
results.push(withExtension); |
||||||
|
} |
||||||
|
withExtension = fullPath + '.exe'; |
||||||
|
if (await fileExists(withExtension)) { |
||||||
|
results.push(withExtension); |
||||||
|
} |
||||||
|
} |
||||||
|
if (results.length > 0) { |
||||||
|
return results; |
||||||
|
} |
||||||
|
return []; |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es6", |
||||||
|
"module": "commonjs", |
||||||
|
"skipLibCheck": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"noImplicitAny": true, |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"sourceMap": true, |
||||||
|
"strictPropertyInitialization": false, |
||||||
|
"strict": true, |
||||||
|
"outDir": "build", |
||||||
|
"rootDir": "./src/", |
||||||
|
"noEmitOnError": true, |
||||||
|
"typeRoots": ["node_modules/@types", "./types"] |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */ |
||||||
|
import React, { useState, useRef, useReducer } from 'react' |
||||||
|
import { FormattedMessage } from 'react-intl' |
||||||
|
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||||
|
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||||
|
const _paq = window._paq = window._paq || [] // eslint-disable-line
|
||||||
|
import { CustomTooltip } from '@remix-ui/helper'; |
||||||
|
|
||||||
|
interface HomeTabFileProps { |
||||||
|
plugin: any |
||||||
|
} |
||||||
|
|
||||||
|
export const HomeTabFileElectron = ({ plugin }: HomeTabFileProps) => { |
||||||
|
|
||||||
|
const loadTemplate = async () => { |
||||||
|
plugin.call('filePanel', 'loadTemplate') |
||||||
|
} |
||||||
|
|
||||||
|
const clone = async () => { |
||||||
|
plugin.call('filePanel', 'clone') |
||||||
|
} |
||||||
|
|
||||||
|
const importFromGist = () => { |
||||||
|
_paq.push(['trackEvent', 'hometab', 'filesSection', 'importFromGist']) |
||||||
|
plugin.call('gistHandler', 'load', '') |
||||||
|
plugin.verticalIcons.select('filePanel') |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="justify-content-start mt-1 p-2 d-flex flex-column" id="hTFileSection"> |
||||||
|
<label style={{ fontSize: "1.2rem" }}><FormattedMessage id='home.files' /></label> |
||||||
|
<label style={{ fontSize: "0.8rem" }} className="pt-2"><FormattedMessage id='home.loadFrom' /></label> |
||||||
|
<div className="d-flex"> |
||||||
|
|
||||||
|
<button className="btn p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={async () => await loadTemplate()}>Project Template</button> |
||||||
|
<button className="btn p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={async () => await clone()}>Clone a Git Repository</button> |
||||||
|
<button className="btn p-2 border mr-2" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
import React, { MouseEventHandler, useContext, useEffect, useState } from "react" |
||||||
|
import { FileSystemContext } from "../contexts" |
||||||
|
import isElectron from 'is-electron' |
||||||
|
import { FormattedMessage } from "react-intl" |
||||||
|
import '../css/electron-menu.css' |
||||||
|
import { CustomTooltip } from '@remix-ui/helper' |
||||||
|
|
||||||
|
export const ElectronMenu = () => { |
||||||
|
const global = useContext(FileSystemContext) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (isElectron()) { |
||||||
|
global.dispatchGetElectronRecentFolders() |
||||||
|
} |
||||||
|
}, []) |
||||||
|
|
||||||
|
const openFolderElectron = async (path: string) => { |
||||||
|
global.dispatchOpenElectronFolder(path) |
||||||
|
} |
||||||
|
|
||||||
|
const lastFolderName = (path: string) => { |
||||||
|
const pathArray = path.split('/') |
||||||
|
return pathArray[pathArray.length - 1] |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
!isElectron() ? null : |
||||||
|
(global.fs.browser.isSuccessfulWorkspace ? null : |
||||||
|
<> |
||||||
|
<div onClick={async () => { await openFolderElectron(null) }} className='btn btn-primary'><FormattedMessage id="electron.openFolder" /></div> |
||||||
|
{global.fs.browser.recentFolders.length > 0 ? |
||||||
|
<> |
||||||
|
<label className="py-2 pt-3 align-self-center m-0"> |
||||||
|
<FormattedMessage id="electron.recentFolders" /> |
||||||
|
</label> |
||||||
|
<ul> |
||||||
|
{global.fs.browser.recentFolders.map((folder, index) => { |
||||||
|
return <li key={index}> |
||||||
|
<CustomTooltip |
||||||
|
tooltipText={folder} |
||||||
|
tooltipId={`electron-recent-folder-${index}`} |
||||||
|
placement='bottom' |
||||||
|
> |
||||||
|
<div className="recentfolder pb-1"> |
||||||
|
<span onClick={async () => { await openFolderElectron(folder) }} className="pl-2 recentfolder_name pr-2">{lastFolderName(folder)}</span> |
||||||
|
<span onClick={async () => { await openFolderElectron(folder) }} data-id={{ folder }} className="recentfolder_path pr-2">{folder}</span> |
||||||
|
<i |
||||||
|
onClick={() => { |
||||||
|
global.dispatchRemoveRecentFolder(folder) |
||||||
|
}} |
||||||
|
className="fas fa-times recentfolder_delete pr-2" |
||||||
|
> |
||||||
|
|
||||||
|
</i> |
||||||
|
</div> |
||||||
|
</CustomTooltip> |
||||||
|
</li> |
||||||
|
})} |
||||||
|
</ul> |
||||||
|
</> |
||||||
|
: null} |
||||||
|
</> |
||||||
|
) |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
.recentfolder { |
||||||
|
display: flex; |
||||||
|
min-width: 0; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.recentfolder_path { |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.recentfolder_name { |
||||||
|
flex-shrink: 0; |
||||||
|
color: var(--text); |
||||||
|
} |
||||||
|
|
||||||
|
.recentfolder_name:hover { |
||||||
|
color: var(--primary); |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
.recentfolder_delete { |
||||||
|
flex-shrink: 0; |
||||||
|
margin-left: auto; |
||||||
|
color: var(--text); |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
export * from './lib/components/remix-ui-xterm' |
||||||
|
export * from './lib/components/remix-ui-xterminals' |
@ -0,0 +1,48 @@ |
|||||||
|
import React, { useState, useEffect, forwardRef } from 'react' // eslint-disable-line
|
||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron' |
||||||
|
import { Xterm } from './xterm-wrap' |
||||||
|
import { FitAddon } from './xterm-fit-addOn'; |
||||||
|
|
||||||
|
|
||||||
|
const fitAddon = new FitAddon() |
||||||
|
|
||||||
|
export interface RemixUiXtermProps { |
||||||
|
plugin: ElectronPlugin |
||||||
|
pid: number |
||||||
|
send: (data: string, pid: number) => void |
||||||
|
timeStamp: number |
||||||
|
setTerminalRef: (pid: number, ref: any) => void |
||||||
|
theme: { |
||||||
|
backgroundColor: string |
||||||
|
textColor: string |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const RemixUiXterm = (props: RemixUiXtermProps) => { |
||||||
|
const { plugin, pid, send, timeStamp } = props |
||||||
|
const xtermRef = React.useRef(null) |
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
props.setTerminalRef(pid, xtermRef.current) |
||||||
|
}, [xtermRef.current]) |
||||||
|
|
||||||
|
const onKey = (event: { key: string; domEvent: KeyboardEvent }) => { |
||||||
|
send(event.key, pid) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
|
||||||
|
<Xterm |
||||||
|
addons={[fitAddon]} |
||||||
|
options={{ theme: { background: props.theme.backgroundColor, foreground: props.theme.textColor } }} |
||||||
|
onRender={() => fitAddon.fit()} |
||||||
|
ref={xtermRef} |
||||||
|
onKey={onKey}></Xterm> |
||||||
|
|
||||||
|
|
||||||
|
) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default RemixUiXterm |
@ -0,0 +1,234 @@ |
|||||||
|
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
||||||
|
import { ElectronPlugin } from '@remixproject/engine-electron' |
||||||
|
import RemixUiXterm from './remix-ui-xterm' |
||||||
|
import '../css/index.css' |
||||||
|
import { Button, ButtonGroup, Dropdown, Tab, Tabs } from 'react-bootstrap' |
||||||
|
import { CustomIconsToggle } from '@remix-ui/helper' |
||||||
|
import { RemixUiTerminal } from '@remix-ui/terminal' |
||||||
|
export interface RemixUiXterminalsProps { |
||||||
|
plugin: ElectronPlugin |
||||||
|
onReady: (api: any) => void |
||||||
|
} |
||||||
|
|
||||||
|
export interface xtermState { |
||||||
|
pid: number |
||||||
|
queue: string |
||||||
|
timeStamp: number |
||||||
|
ref: any |
||||||
|
hidden: boolean |
||||||
|
} |
||||||
|
|
||||||
|
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { |
||||||
|
const [terminals, setTerminals] = useState<xtermState[]>([]) |
||||||
|
const [workingDir, setWorkingDir] = useState<string>('') |
||||||
|
const [showOutput, setShowOutput] = useState<boolean>(true) |
||||||
|
const [theme, setTheme] = useState<any>(themeCollection[0]) |
||||||
|
const [terminalsEnabled, setTerminalsEnabled] = useState<boolean>(false) |
||||||
|
const [shells, setShells] = useState<string[]>([]) |
||||||
|
const { plugin } = props |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setTimeout(async () => { |
||||||
|
plugin.on('xterm', 'loaded', async () => { |
||||||
|
console.log('xterm loaded') |
||||||
|
}) |
||||||
|
|
||||||
|
plugin.on('xterm', 'data', async (data: string, pid: number) => { |
||||||
|
writeToTerminal(data, pid) |
||||||
|
}) |
||||||
|
|
||||||
|
plugin.on('xterm', 'close', async (pid: number) => { |
||||||
|
setTerminals(prevState => { |
||||||
|
const removed = prevState.filter(xtermState => xtermState.pid !== pid) |
||||||
|
if (removed.length > 0) |
||||||
|
removed[removed.length - 1].hidden = false |
||||||
|
if(removed.length === 0) |
||||||
|
setShowOutput(true) |
||||||
|
return [...removed] |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
plugin.on('fs', 'workingDirChanged', (path: string) => { |
||||||
|
setWorkingDir(path) |
||||||
|
setTerminalsEnabled(true) |
||||||
|
}) |
||||||
|
|
||||||
|
plugin.on('theme', 'themeChanged', async (theme) => { |
||||||
|
handleThemeChange(theme) |
||||||
|
}) |
||||||
|
|
||||||
|
const theme = await plugin.call('theme', 'currentTheme') |
||||||
|
handleThemeChange(theme) |
||||||
|
|
||||||
|
|
||||||
|
const shells = await plugin.call('xterm', 'getShells') |
||||||
|
setShells(shells) |
||||||
|
}, 2000) |
||||||
|
}, []) |
||||||
|
|
||||||
|
|
||||||
|
const handleThemeChange = (theme: any) => { |
||||||
|
themeCollection.forEach((themeItem) => { |
||||||
|
if (themeItem.themeName === theme.name) { |
||||||
|
setTheme(themeItem) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const writeToTerminal = (data: string, pid: number) => { |
||||||
|
setTerminals(prevState => { |
||||||
|
const terminal = prevState.find(xtermState => xtermState.pid === pid) |
||||||
|
if (terminal.ref && terminal.ref.terminal) { |
||||||
|
terminal.ref.terminal.write(data) |
||||||
|
} else { |
||||||
|
terminal.queue += data |
||||||
|
} |
||||||
|
return [...prevState] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const send = (data: string, pid: number) => { |
||||||
|
plugin.call('xterm', 'keystroke', data, pid) |
||||||
|
} |
||||||
|
|
||||||
|
const createTerminal = async (shell?: string) => { |
||||||
|
const pid = await plugin.call('xterm', 'createTerminal', workingDir, shell) |
||||||
|
setShowOutput(false) |
||||||
|
setTerminals(prevState => { |
||||||
|
// set all to hidden
|
||||||
|
prevState.forEach(xtermState => { |
||||||
|
xtermState.hidden = true |
||||||
|
}) |
||||||
|
return [...prevState, { |
||||||
|
pid: pid, |
||||||
|
queue: '', |
||||||
|
timeStamp: Date.now(), |
||||||
|
ref: null, |
||||||
|
hidden: false |
||||||
|
}] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const setTerminalRef = (pid: number, ref: any) => { |
||||||
|
setTerminals(prevState => { |
||||||
|
const terminal = prevState.find(xtermState => xtermState.pid === pid) |
||||||
|
terminal.ref = ref |
||||||
|
if (terminal.queue) { |
||||||
|
ref.terminal.write(terminal.queue) |
||||||
|
terminal.queue = '' |
||||||
|
} |
||||||
|
return [...prevState] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const selectTerminal = (state: xtermState) => { |
||||||
|
setTerminals(prevState => { |
||||||
|
// set all to hidden
|
||||||
|
prevState.forEach(xtermState => { |
||||||
|
xtermState.hidden = true |
||||||
|
}) |
||||||
|
const terminal = prevState.find(xtermState => xtermState.pid === state.pid) |
||||||
|
terminal.hidden = false |
||||||
|
return [...prevState] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const closeTerminal = () => { |
||||||
|
const pid = terminals.find(xtermState => xtermState.hidden === false).pid |
||||||
|
if (pid) |
||||||
|
plugin.call('xterm', 'close', pid) |
||||||
|
} |
||||||
|
|
||||||
|
const selectOutput = () => { |
||||||
|
setShowOutput(true) |
||||||
|
} |
||||||
|
|
||||||
|
const showTerminal = () => { |
||||||
|
setShowOutput(false) |
||||||
|
if (terminals.length === 0) createTerminal() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return (<> |
||||||
|
|
||||||
|
<div className='xterm-panel'> |
||||||
|
<div className='xterm-panel-header bg-light'> |
||||||
|
<div className='xterm-panel-header-left p-1'> |
||||||
|
<button className={`btn btn-sm btn-secondary mr-2 ${!showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={selectOutput}>ouput</button> |
||||||
|
<button className={`btn btn-sm btn-secondary ${terminalsEnabled ? '' : 'd-none'} ${showOutput ? 'xterm-btn-none' : 'xterm-btn-active'}`} onClick={showTerminal}><span className="far fa-terminal border-0 ml-1"></span></button> |
||||||
|
</div> |
||||||
|
<div className={`xterm-panel-header-right ${showOutput ? 'd-none' : ''}`}> |
||||||
|
|
||||||
|
<Dropdown as={ButtonGroup}> |
||||||
|
<button className="btn btn-sm btn-secondary" onClick={async() => createTerminal()}><span className="far fa-plus border-0 p-0 m-0"></span></button> |
||||||
|
|
||||||
|
|
||||||
|
<Dropdown.Toggle split variant="secondary" id="dropdown-split-basic" /> |
||||||
|
|
||||||
|
<Dropdown.Menu className='custom-dropdown-items remixui_menuwidth'> |
||||||
|
{shells.map((shell, index) => { |
||||||
|
return (<Dropdown.Item key={index} onClick={async() => await createTerminal(shell)}>{shell}</Dropdown.Item>) |
||||||
|
})} |
||||||
|
</Dropdown.Menu> |
||||||
|
</Dropdown> |
||||||
|
<button className="btn ml-2 btn-sm btn-secondary" onClick={closeTerminal}><span className="far fa-trash border-0 ml-1"></span></button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
<div className='remix-ui-xterminals-container'> |
||||||
|
<> |
||||||
|
<div className={`${!showOutput ? 'd-none' : 'd-block w-100'} `}> |
||||||
|
<RemixUiTerminal |
||||||
|
plugin={props.plugin} |
||||||
|
onReady={props.onReady} /> |
||||||
|
</div> |
||||||
|
<div className={`remix-ui-xterminals-section ${showOutput ? 'd-none' : 'd-flex'} `}> |
||||||
|
{terminals.map((xtermState) => { |
||||||
|
return ( |
||||||
|
<div className={`h-100 xterm-terminal ${xtermState.hidden ? 'hide-xterm' : 'show-xterm'}`} key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}> |
||||||
|
<RemixUiXterm theme={theme} setTerminalRef={setTerminalRef} timeStamp={xtermState.timeStamp} send={send} pid={xtermState.pid} plugin={plugin}></RemixUiXterm> |
||||||
|
</div> |
||||||
|
) |
||||||
|
})} |
||||||
|
<div className='remix-ui-xterminals-buttons border-left'> |
||||||
|
{terminals.map((xtermState, index) => { |
||||||
|
return (<button key={index} onClick={async () => selectTerminal(xtermState)} className={`btn btn-sm mt-2 btn-secondary ${xtermState.hidden ? 'xterm-btn-none' : 'xterm-btn-active'}`}><span className="fa fa-terminal border-0 p-0 m-0"></span></button>) |
||||||
|
})} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
</>) |
||||||
|
} |
||||||
|
|
||||||
|
const themeCollection = [ |
||||||
|
{ themeName: 'HackerOwl', backgroundColor: '#011628', textColor: '#babbcc', |
||||||
|
shapeColor: '#8694a1',fillColor: '#011C32'}, |
||||||
|
{ themeName: 'Cerulean', backgroundColor: '#ffffff', textColor: '#343a40', |
||||||
|
shapeColor: '#343a40',fillColor: '#f8f9fa'}, |
||||||
|
{ themeName: 'Cyborg', backgroundColor: '#060606', textColor: '#adafae', |
||||||
|
shapeColor: '#adafae', fillColor: '#222222'}, |
||||||
|
{ themeName: 'Dark', backgroundColor: '#222336', textColor: '#babbcc', |
||||||
|
shapeColor: '#babbcc',fillColor: '#2a2c3f'}, |
||||||
|
{ themeName: 'Flatly', backgroundColor: '#ffffff', textColor: '#343a40', |
||||||
|
shapeColor: '#7b8a8b',fillColor: '#ffffff'}, |
||||||
|
{ themeName: 'Black', backgroundColor: '#1a1a1a', textColor: '#babbcc', |
||||||
|
shapeColor: '#b5b4bc',fillColor: '#1f2020'}, |
||||||
|
{ themeName: 'Light', backgroundColor: '#eef1f6', textColor: '#3b445e', |
||||||
|
shapeColor: '#343a40',fillColor: '#ffffff'}, |
||||||
|
{ themeName: 'Midcentury', backgroundColor: '#DBE2E0', textColor: '#11556c', |
||||||
|
shapeColor: '#343a40',fillColor: '#eeede9'}, |
||||||
|
{ themeName: 'Spacelab', backgroundColor: '#ffffff', textColor: '#343a40', |
||||||
|
shapeColor: '#333333', fillColor: '#eeeeee'}, |
||||||
|
{ themeName: 'Candy', backgroundColor: '#d5efff', textColor: '#11556c', |
||||||
|
shapeColor: '#343a40',fillColor: '#fbe7f8' }, |
||||||
|
{ themeName: 'Violet', backgroundColor: '#f1eef6', textColor: '#3b445e', |
||||||
|
shapeColor: '#343a40',fillColor: '#f8fafe' }, |
||||||
|
{ themeName: 'Pride', backgroundColor: '#f1eef6', textColor: '#343a40', |
||||||
|
shapeColor: '#343a40',fillColor: '#f8fafe' }, |
||||||
|
] |
@ -0,0 +1,92 @@ |
|||||||
|
/** |
||||||
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved. |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
import { Terminal, ITerminalAddon } from 'xterm'; |
||||||
|
|
||||||
|
|
||||||
|
interface ITerminalDimensions { |
||||||
|
/** |
||||||
|
* The number of rows in the terminal. |
||||||
|
*/ |
||||||
|
rows: number; |
||||||
|
|
||||||
|
/** |
||||||
|
* The number of columns in the terminal. |
||||||
|
*/ |
||||||
|
cols: number; |
||||||
|
} |
||||||
|
|
||||||
|
const MINIMUM_COLS = 2; |
||||||
|
const MINIMUM_ROWS = 1; |
||||||
|
|
||||||
|
export class FitAddon implements ITerminalAddon { |
||||||
|
private _terminal: Terminal | undefined; |
||||||
|
|
||||||
|
constructor() {} |
||||||
|
|
||||||
|
public activate(terminal: Terminal): void { |
||||||
|
this._terminal = terminal; |
||||||
|
} |
||||||
|
|
||||||
|
public dispose(): void {} |
||||||
|
|
||||||
|
public fit(): void { |
||||||
|
const dims = this.proposeDimensions(); |
||||||
|
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Remove reliance on private API
|
||||||
|
const core = (this._terminal as any)._core; |
||||||
|
|
||||||
|
// Force a full render
|
||||||
|
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) { |
||||||
|
core._renderService.clear(); |
||||||
|
this._terminal.resize(dims.cols, dims.rows); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public proposeDimensions(): ITerminalDimensions | undefined { |
||||||
|
if (!this._terminal) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
if (!this._terminal.element || !this._terminal.element.parentElement) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Remove reliance on private API
|
||||||
|
const core = (this._terminal as any)._core; |
||||||
|
const dims = core._renderService.dimensions; |
||||||
|
|
||||||
|
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
const scrollbarWidth = this._terminal.options.scrollback === 0 ? |
||||||
|
0 : core.viewport.scrollBarWidth; |
||||||
|
|
||||||
|
|
||||||
|
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement); |
||||||
|
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); |
||||||
|
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'))); |
||||||
|
const elementStyle = window.getComputedStyle(this._terminal.element); |
||||||
|
const elementPadding = { |
||||||
|
top: parseInt(elementStyle.getPropertyValue('padding-top')), |
||||||
|
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')), |
||||||
|
right: parseInt(elementStyle.getPropertyValue('padding-right')), |
||||||
|
left: parseInt(elementStyle.getPropertyValue('padding-left')) |
||||||
|
}; |
||||||
|
const elementPaddingVer = elementPadding.top + elementPadding.bottom; |
||||||
|
const elementPaddingHor = elementPadding.right + elementPadding.left; |
||||||
|
const availableHeight = parentElementHeight - elementPaddingVer; |
||||||
|
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth; |
||||||
|
const geometry = { |
||||||
|
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)), |
||||||
|
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height)) |
||||||
|
}; |
||||||
|
return geometry; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,237 @@ |
|||||||
|
import * as React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
import 'xterm/css/xterm.css' |
||||||
|
|
||||||
|
// We are using these as types.
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { Terminal, ITerminalOptions, ITerminalAddon } from 'xterm' |
||||||
|
|
||||||
|
interface IProps { |
||||||
|
/** |
||||||
|
* Class name to add to the terminal container. |
||||||
|
*/ |
||||||
|
className?: string |
||||||
|
|
||||||
|
/** |
||||||
|
* Options to initialize the terminal with. |
||||||
|
*/ |
||||||
|
options?: ITerminalOptions |
||||||
|
|
||||||
|
/** |
||||||
|
* An array of XTerm addons to load along with the terminal. |
||||||
|
*/ |
||||||
|
addons?: Array<ITerminalAddon> |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a binary event fires. This is used to |
||||||
|
* enable non UTF-8 conformant binary messages to be sent to the backend. |
||||||
|
* Currently this is only used for a certain type of mouse reports that |
||||||
|
* happen to be not UTF-8 compatible. |
||||||
|
* The event value is a JS string, pass it to the underlying pty as |
||||||
|
* binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`. |
||||||
|
*/ |
||||||
|
onBinary?(data: string): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for the cursor moves. |
||||||
|
*/ |
||||||
|
onCursorMove?(): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a data event fires. This happens for |
||||||
|
* example when the user types or pastes into the terminal. The event value |
||||||
|
* is whatever `string` results, in a typical setup, this should be passed |
||||||
|
* on to the backing pty. |
||||||
|
*/ |
||||||
|
onData?(data: string): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a key is pressed. The event value contains the |
||||||
|
* string that will be sent in the data event as well as the DOM event that |
||||||
|
* triggered it. |
||||||
|
*/ |
||||||
|
onKey?(event: { key: string; domEvent: KeyboardEvent }): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a line feed is added. |
||||||
|
*/ |
||||||
|
onLineFeed?(): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a scroll occurs. The event value is the |
||||||
|
* new position of the viewport. |
||||||
|
* @returns an `IDisposable` to stop listening. |
||||||
|
*/ |
||||||
|
onScroll?(newPosition: number): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when a selection change occurs. |
||||||
|
*/ |
||||||
|
onSelectionChange?(): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when rows are rendered. The event value |
||||||
|
* contains the start row and end rows of the rendered area (ranges from `0` |
||||||
|
* to `Terminal.rows - 1`). |
||||||
|
*/ |
||||||
|
onRender?(event: { start: number; end: number }): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when the terminal is resized. The event value |
||||||
|
* contains the new size. |
||||||
|
*/ |
||||||
|
onResize?(event: { cols: number; rows: number }): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds an event listener for when an OSC 0 or OSC 2 title change occurs. |
||||||
|
* The event value is the new title. |
||||||
|
*/ |
||||||
|
onTitleChange?(newTitle: string): void |
||||||
|
|
||||||
|
/** |
||||||
|
* Attaches a custom key event handler which is run before keys are |
||||||
|
* processed, giving consumers of xterm.js ultimate control as to what keys |
||||||
|
* should be processed by the terminal and what keys should not. |
||||||
|
* |
||||||
|
* @param event The custom KeyboardEvent handler to attach. |
||||||
|
* This is a function that takes a KeyboardEvent, allowing consumers to stop |
||||||
|
* propagation and/or prevent the default action. The function returns |
||||||
|
* whether the event should be processed by xterm.js. |
||||||
|
*/ |
||||||
|
customKeyEventHandler?(event: KeyboardEvent): boolean |
||||||
|
} |
||||||
|
|
||||||
|
export class Xterm extends React.Component<IProps> { |
||||||
|
/** |
||||||
|
* The ref for the containing element. |
||||||
|
*/ |
||||||
|
terminalRef: React.RefObject<HTMLDivElement> |
||||||
|
|
||||||
|
/** |
||||||
|
* XTerm.js Terminal object. |
||||||
|
*/ |
||||||
|
terminal!: Terminal // This is assigned in the setupTerminal() which is called from the constructor
|
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
className: PropTypes.string, |
||||||
|
options: PropTypes.object, |
||||||
|
addons: PropTypes.array, |
||||||
|
onBinary: PropTypes.func, |
||||||
|
onCursorMove: PropTypes.func, |
||||||
|
onData: PropTypes.func, |
||||||
|
onKey: PropTypes.func, |
||||||
|
onLineFeed: PropTypes.func, |
||||||
|
onScroll: PropTypes.func, |
||||||
|
onSelectionChange: PropTypes.func, |
||||||
|
onRender: PropTypes.func, |
||||||
|
onResize: PropTypes.func, |
||||||
|
onTitleChange: PropTypes.func, |
||||||
|
customKeyEventHandler: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
constructor(props: IProps) { |
||||||
|
super(props) |
||||||
|
|
||||||
|
this.terminalRef = React.createRef() |
||||||
|
|
||||||
|
// Bind Methods
|
||||||
|
this.onData = this.onData.bind(this) |
||||||
|
this.onCursorMove = this.onCursorMove.bind(this) |
||||||
|
this.onKey = this.onKey.bind(this) |
||||||
|
this.onBinary = this.onBinary.bind(this) |
||||||
|
this.onLineFeed = this.onLineFeed.bind(this) |
||||||
|
this.onScroll = this.onScroll.bind(this) |
||||||
|
this.onSelectionChange = this.onSelectionChange.bind(this) |
||||||
|
this.onRender = this.onRender.bind(this) |
||||||
|
this.onResize = this.onResize.bind(this) |
||||||
|
this.onTitleChange = this.onTitleChange.bind(this) |
||||||
|
|
||||||
|
this.setupTerminal() |
||||||
|
} |
||||||
|
|
||||||
|
setupTerminal() { |
||||||
|
// Setup the XTerm terminal.
|
||||||
|
this.terminal = new Terminal(this.props.options) |
||||||
|
|
||||||
|
// Load addons if the prop exists.
|
||||||
|
if (this.props.addons) { |
||||||
|
this.props.addons.forEach((addon) => { |
||||||
|
this.terminal.loadAddon(addon) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Create Listeners
|
||||||
|
this.terminal.onBinary(this.onBinary) |
||||||
|
this.terminal.onCursorMove(this.onCursorMove) |
||||||
|
this.terminal.onData(this.onData) |
||||||
|
this.terminal.onKey(this.onKey) |
||||||
|
this.terminal.onLineFeed(this.onLineFeed) |
||||||
|
this.terminal.onScroll(this.onScroll) |
||||||
|
this.terminal.onSelectionChange(this.onSelectionChange) |
||||||
|
this.terminal.onRender(this.onRender) |
||||||
|
this.terminal.onResize(this.onResize) |
||||||
|
this.terminal.onTitleChange(this.onTitleChange) |
||||||
|
|
||||||
|
// Add Custom Key Event Handler
|
||||||
|
if (this.props.customKeyEventHandler) { |
||||||
|
this.terminal.attachCustomKeyEventHandler(this.props.customKeyEventHandler) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
if (this.terminalRef.current) { |
||||||
|
// Creates the terminal within the container element.
|
||||||
|
this.terminal.open(this.terminalRef.current) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
// When the component unmounts dispose of the terminal and all of its listeners.
|
||||||
|
this.terminal.dispose() |
||||||
|
} |
||||||
|
|
||||||
|
private onBinary(data: string) { |
||||||
|
if (this.props.onBinary) this.props.onBinary(data) |
||||||
|
} |
||||||
|
|
||||||
|
private onCursorMove() { |
||||||
|
if (this.props.onCursorMove) this.props.onCursorMove() |
||||||
|
} |
||||||
|
|
||||||
|
private onData(data: string) { |
||||||
|
if (this.props.onData) this.props.onData(data) |
||||||
|
} |
||||||
|
|
||||||
|
private onKey(event: { key: string; domEvent: KeyboardEvent }) { |
||||||
|
if (this.props.onKey) this.props.onKey(event) |
||||||
|
} |
||||||
|
|
||||||
|
private onLineFeed() { |
||||||
|
if (this.props.onLineFeed) this.props.onLineFeed() |
||||||
|
} |
||||||
|
|
||||||
|
private onScroll(newPosition: number) { |
||||||
|
if (this.props.onScroll) this.props.onScroll(newPosition) |
||||||
|
} |
||||||
|
|
||||||
|
private onSelectionChange() { |
||||||
|
if (this.props.onSelectionChange) this.props.onSelectionChange() |
||||||
|
} |
||||||
|
|
||||||
|
private onRender(event: { start: number; end: number }) { |
||||||
|
if (this.props.onRender) this.props.onRender(event) |
||||||
|
} |
||||||
|
|
||||||
|
private onResize(event: { cols: number; rows: number }) { |
||||||
|
if (this.props.onResize) this.props.onResize(event) |
||||||
|
} |
||||||
|
|
||||||
|
private onTitleChange(newTitle: string) { |
||||||
|
if (this.props.onTitleChange) this.props.onTitleChange(newTitle) |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return <div className={this.props.className} ref={this.terminalRef} /> |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
.remix-ui-xterminals-container { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
} |
||||||
|
.xterm-panel { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
.remix-ui-xterminals-buttons { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
|
||||||
|
.hide-xterm{ |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.show-xterm{ |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.xterm-btn-active { |
||||||
|
background-color: var(--primary); |
||||||
|
} |
||||||
|
.xterm-btn-none { |
||||||
|
background-color: var(--secondary); |
||||||
|
} |
||||||
|
|
||||||
|
.xterm-terminal { |
||||||
|
flex-grow: 1; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.xterm-panel-header-right { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
justify-content: flex-end; |
||||||
|
align-self: flex-end; |
||||||
|
} |
||||||
|
|
||||||
|
.xterm-panel-header { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
} |
||||||
|
|
||||||
|
.xterm-panel-header-left { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
flex-grow: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.remix-ui-xterminals-section { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
width: 100%; |
||||||
|
z-index: 3; |
||||||
|
} |
||||||
|
|
||||||
|
.hide-terminals { |
||||||
|
width: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.show-terminals { |
||||||
|
width: 100%; |
||||||
|
} |
Loading…
Reference in new issue