Compare commits

..
Author SHA1 Message Date
muzam1l 2c3cfebe3a Fix HookOn initial value. 2023-03-28 15:14:43 +05:30
muzamil 6265a9cdbf Merge pull request #300 from XRPLF/fix/to-hex
Fix hex logic.
2023-03-27 21:20:08 +05:30
muzamil 1321b498cf Merge pull request #299 from XRPLF/fix/invoke-tx
Add `Destination` field to Invoke transaction.
2023-03-27 21:18:35 +05:30
muzam1l 801d9778cb Fix hex logic. 2023-03-27 19:21:14 +05:30
muzam1l 2cf18ef61c Add Destination field to Invoke transaction. 2023-03-27 15:32:25 +05:30
muzamil 4d2dc16ce5 Merge pull request #296 from XRPLF/feat/account-ui
Extend transactions Account UI.
2023-03-23 20:45:27 +05:30
muzam1l 9ecf5478e6 Remove tx Destination default values. 2023-03-23 19:39:56 +05:30
muzam1l 6d1ef110b7 Token Issuer account UI. 2023-03-23 17:52:17 +05:30
muzam1l b9edfcd63b Fix: nextjs build. 2023-03-23 16:55:32 +05:30
muzam1l b653d9a9cb Fix: Remove last traces of custom Destination handling! 2023-03-23 16:47:25 +05:30
muzam1l da28e0a7d1 Use "creatable select" for accounts. 2023-03-23 16:45:24 +05:30
muzam1l 8a5b83d57f Fix: Don't remove fields in JSON mode if empty. 2023-03-23 16:26:29 +05:30
muzam1l 025eff6cf2 Add Select UI for account. 2023-03-23 16:12:35 +05:30
muzam1l 62d521b2cc Add owner field to NFTokenCreateOffer. 2023-03-23 15:51:36 +05:30
muzam1l 7aafca21df Remove Destination as special field. 2023-03-23 15:49:17 +05:30
muzamil 80f58e903c Merge pull request #294 from XRPLF/feat/amount-ui
Transaction amount UI.
2023-03-20 14:04:30 +05:30
muzam1l c4af3df017 Add DeliverMin field to tx CheckCash. 2023-03-17 20:10:57 +05:30
muzam1l 5d8d142bc4 Catch all estimate fee errors. 2023-03-17 19:01:32 +05:30
muzam1l e27a71d713 Estimate fee correct error message and amount input type. 2023-03-17 18:57:03 +05:30
muzam1l e08b07cbeb Update tx OfferCreate. 2023-03-17 18:03:37 +05:30
muzam1l e4936c03ef Css changes. 2023-03-17 17:17:29 +05:30
muzam1l 21a69ac8ea minor type fix. 2023-03-17 16:59:12 +05:30
muzam1l 52e4f219f7 Transaction amount UI. 2023-03-17 16:49:00 +05:30
muzamil e1f34c4beb Merge pull request #291 from XRPLF/fix/sequence-ui
Fix Account sequence UI reset.
2023-03-14 21:30:35 +05:30
muzam1l 54a89c969e Fix account sequence reset. 2023-03-14 15:38:09 +05:30
muzamil ded867d997 Merge pull request #289 from XRPLF/fix/tx
Account sequence.
2023-03-10 17:31:55 +05:30
muzam1l 0fce9af77c Fetch sequence on account creation. 2023-03-10 16:18:35 +05:30
muzam1l 55c68c580a Account Sequence UI. 2023-03-10 16:02:32 +05:30
muzamil 832a7997d1 Merge pull request #288 from XRPLF/fix/tx
Fix tx json saving and discarding.
2023-03-08 21:32:36 +05:30
muzam1l 4528e5a16e Actually fix Json 'save'. 2023-03-08 20:33:23 +05:30
muzam1l 38f064c6d8 Fix json saving and discarding. 2023-03-08 17:13:36 +05:30
muzamil fbf4565dbc Merge pull request #287 from XRPLF/feat/memos-ui
Memos UI
2023-03-07 17:32:32 +05:30
muzam1l 9001c64fed minor label changes. 2023-03-07 16:58:32 +05:30
muzam1l 03b768db4e UI for memos fields. 2023-03-07 16:03:47 +05:30
muzam1l 825af0db89 Add memos field in transactions. 2023-03-06 21:14:14 +05:30
muzamil 31043f33ab Merge pull request #286 from XRPLF/feat/tx-params-ui
HookParameters UI for transactions.
2023-03-06 16:06:19 +05:30
muzam1l 39699a1cb9 HookParameters UI for transactions. 2023-03-03 18:32:03 +05:30
muzam1l b50b300307 Refactor tx. 2023-03-03 16:00:43 +05:30
muzamil 82c06cbb12 Merge pull request #285 from XRPLF/fix/params
Add invoke hookon option.
2023-03-02 19:17:38 +05:30
muzam1l 423ee18e6a Add Invoke HookOn option. 2023-03-02 15:11:44 +05:30
muzamil 3bb26d0c9b Merge pull request #281 from XRPLF/feat/testnet-v3
Testnet v3.
2023-02-10 16:12:22 +05:30
muzam1l 43c83d0de6 Fix delete hook and some refactor. 2023-02-10 14:43:51 +05:30
muzam1l 6bb407cb0f Better package loading experience. 2023-02-09 22:15:22 +05:30
muzamil d7b29ba809 Merge pull request #282 from XRPLF/feat/bundle-xrpl
Bundle patched `xrpl-accountlib` and expose through require syntax.
2023-02-09 20:22:43 +05:30
muzam1l ca81d8ad41 Bundle xrpl-accountlib and expose throw require syntax. 2023-02-09 15:41:06 +05:30
muzam1l a7e59d7b73 Add UriToken transaction. 2023-02-07 14:32:06 +05:30
muzam1l 8f6b28cef5 Add Invoke transaction type. 2023-02-07 14:15:07 +05:30
muzam1l c1815f272b refactor. 2023-02-07 14:00:57 +05:30
muzam1l 76871a8041 Testnet v3. 2023-02-06 21:09:11 +05:30
mariopil d2033c8035 Hooks docs update (#279)
* Added hooks-guard-call-non-const checker doc, updated hooks-guard-in-for doc.
2023-01-09 13:43:32 +01:00
muzamil 48daf1c5c8 Merge pull request #278 from XRPLF/feat/compile-wat
Increase wat file priority in sorting.
2022-12-07 15:03:40 +05:30
muzam1l a3365e4beb Increase wat file priority in sorting. 2022-12-07 15:02:09 +05:30
muzamil 45d813cdad Merge pull request #277 from XRPLF/feat/compile-wat
Compile raw wat files.
2022-12-07 14:46:05 +05:30
muzam1l 911416aa2f Compile wat files. 2022-12-07 13:31:59 +05:30
muzamil 2d836af9ed Merge pull request #276 from XRPLF/feat/flags-ui
Transaction flags UI.
2022-11-01 17:14:44 +05:30
muzam1l 4f0fc838be Minor css fix. 2022-11-01 11:20:09 +05:30
muzam1l fa93912c38 Remove reductant comment. 2022-10-28 14:58:11 +05:30
muzam1l f5cb76c302 Merge branch 'main' into feat/flags-ui 2022-10-28 14:48:41 +05:30
muzamil df1d65dcab Merge pull request #275 from XRPLF/dependabot/npm_and_yarn/jose-4.10.0
Bump jose from 4.6.0 to 4.10.0
2022-10-28 14:48:02 +05:30
muzam1l 1513f78991 Add tx specific flags. 2022-10-28 14:43:37 +05:30
muzam1l 3a064f307b Add global flags. 2022-10-28 14:21:22 +05:30
muzam1l 6fca05f310 Add flags UI to Payment transaction. 2022-10-28 12:29:57 +05:30
muzamil 31e67d382f Merge pull request #273 from XRPLF/fix/renaming-ext
Update file language on renaming.
2022-10-26 18:10:43 +05:30
dependabot[bot] 27475301e4 Bump jose from 4.6.0 to 4.10.0
Bumps [jose](https://github.com/panva/jose) from 4.6.0 to 4.10.0.
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v4.6.0...v4.10.0)

---
updated-dependencies:
- dependency-name: jose
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-14 08:29:14 +00:00
muzam1l 934283976a Add plain text language to monaco. 2022-09-09 14:07:13 +05:30
muzamil 2d9ca2674e Merge pull request #266 from XRPLF/feat/engine-code-links
Linkify engine error codes and some refactor.
2022-08-19 20:00:22 +05:30
muzam1l 221c727af6 Fix mistake in set-codes json. 2022-08-19 18:33:31 +05:30
muzam1l 3b0a8c44c9 Remove link title from log. 2022-08-19 17:46:02 +05:30
muzam1l 0d9e9e7b45 Merge branch 'main' into fix/renaming-ext 2022-08-19 15:14:52 +05:30
muzam1l 094f739b80 Merge branch 'main' into feat/engine-code-links 2022-08-19 15:07:44 +05:30
muzamil a2077f9592 Merge pull request #270 from XRPLF/feat/prettier
Prettier config!
2022-08-17 22:11:44 +05:30
muzam1l 59d9c5356c Run format again on new changes. 2022-08-17 22:07:36 +05:30
muzam1l 2a10d525fb Merge branch 'main' into feat/prettier 2022-08-17 22:05:50 +05:30
muzam1l 2c2bf59bcd Update file language on renaming. 2022-08-17 12:46:17 +05:30
muzamil 79bd5da3d2 Merge pull request #269 from XRPLF/feat/md-preview
Markdown Preview.
2022-08-17 12:39:42 +05:30
muzam1l 1fab69ef9b Minor changes based on feedback. 2022-08-17 12:11:36 +05:30
muzam1l 6418094b0f Run prettier through everything. 2022-08-17 11:50:49 +05:30
muzam1l 2d82966b3b Tooltip for SetHook codes in debug stream. 2022-08-16 18:16:49 +05:30
muzam1l dbbbfdb2f0 Increase C files priority in sorting. 2022-08-16 13:42:38 +05:30
muzam1l 9923dd9390 Add prettier config. 2022-08-12 23:15:05 +05:30
muzam1l 052be354d1 Fix sorting logic 2022-08-12 23:06:58 +05:30
muzam1l 737523fe8d Log full error on console in fetchFiles. 2022-08-12 16:31:25 +05:30
muzam1l 034fc3423b Link comp in markdown. 2022-08-12 15:54:34 +05:30
muzam1l b23b0066bb Change carbon and starter templates to forked. 2022-08-12 15:43:00 +05:30
muzam1l 380b6b1afd Fix linting error 2022-08-12 15:40:15 +05:30
muzam1l c7d77b26b5 Save markdown before previewing. 2022-08-12 15:36:46 +05:30
muzam1l aa62251780 Fix gist name. 2022-08-12 15:32:53 +05:30
muzam1l 4b2b4b25c0 Give md extension higher priority for sorting! 2022-08-12 15:22:57 +05:30
muzam1l 6dd5712573 Refactor fetchFiles.ts 2022-08-12 15:21:59 +05:30
muzam1l 6611a94652 Add markdown preview and toggle to md files. 2022-08-11 15:12:31 +05:30
muzam1l 93c5ef231e Fix display of nullish values in script logs. 2022-08-09 17:18:22 +05:30
muzam1l 53c2104b94 Point groups of error codes to their individual pages. 2022-08-09 17:00:30 +05:30
muzam1l c336ff8334 Linkify engine error codes and some refactor. 2022-08-09 02:07:15 +05:30
muzam1l 2086291d4d Revert "Linkify engine error codes and some refactor."
This reverts commit 5505f0ac87.
2022-08-09 01:55:42 +05:30
muzam1l 5505f0ac87 Linkify engine error codes and some refactor. 2022-08-09 01:50:29 +05:30
muzamil 2cf92b908d Merge pull request #263 from XRPLF/fix/delete-account-effects
Update tx state accounts on delete account.
2022-08-08 15:56:28 +05:30
Valtteri Karesto 70de876f75 Merge pull request #265 from XRPLF/fix/update-next-auth
Update next-auth to fix dependabot alert
2022-08-08 13:23:43 +03:00
Valtteri Karesto 58cde29fff Update next-auth to fix dependabot alert 2022-08-08 09:07:45 +03:00
muzamil a06fb06610 Merge pull request #264 from XRPLF/fix/tab-switching
Switch to correct tab after renaming/closing.
2022-08-05 16:23:38 +05:30
muzam1l 1f5a9731bb Readonly files are not renamable now! 2022-08-04 18:54:20 +05:30
muzam1l ef4f95ca3e Switch to correct tab after renaming/closing. 2022-08-03 16:02:17 +05:30
muzamil fb9814ec76 Merge pull request #262 from XRPLF/fix/script-log-appending
Clear script log on running new script.
2022-08-03 14:33:59 +05:30
muzam1l 7f6b989f15 Update tx state accounts on delete account. 2022-08-02 15:23:11 +05:30
muzam1l d459b2ee92 Clear script log on running new script. 2022-08-01 17:04:26 +05:30
muzamil 6ee1a09aaa Merge pull request #259 from XRPLF/feat/deploy-default-fields
Deploy config default fields from source files.
2022-07-29 15:59:01 +05:30
muzam1l dd2228fb35 Reset deploy form fields when file changes. 2022-07-29 14:33:28 +05:30
muzam1l ca52a5e064 Enforce required prop in default tags. 2022-07-28 18:12:58 +05:30
muzam1l df0f8abe62 Add required margin to param field. 2022-07-27 17:31:43 +05:30
muzam1l a6c4db1951 Invoke options defaults. 2022-07-26 17:05:33 +05:30
muzam1l 1c91003164 Deploy config default fields from source files. 2022-07-25 20:22:58 +05:30
muzamil 66be0efbbd Merge pull request #255 from XRPLF/feat/tab-renames
Implement file renaming.
2022-07-22 16:35:47 +05:30
muzamil 9ab64ec062 Merge pull request #256 from XRPLF/fix/compile-result
Fix incorrect compilation result.
2022-07-22 14:43:55 +05:30
muzamil e77a5e234f Merge pull request #257 from XRPLF/fix/account-select
Fixes #240.
2022-07-22 14:43:32 +05:30
muzamil d2f618512a Merge pull request #258 from XRPLF/fix/acc-import-limit
Remove account import limit.
2022-07-22 14:43:01 +05:30
muzam1l f5063de2c9 Fix matching file error algo for rename/newfile. 2022-07-22 14:40:40 +05:30
muzam1l 1ee8dcb536 Select comp: remove hightlight from selected option 2022-07-21 16:58:07 +05:30
muzam1l 7f6f9c11db Fix dialog z-index for safari. 2022-07-21 16:44:33 +05:30
muzam1l b2b7059774 Remove account import limit. 2022-07-21 15:31:06 +05:30
muzam1l 41ba096ef9 Account selector change. 2022-07-21 15:26:37 +05:30
muzam1l 8b72086c04 Differentiate between netwrok error and invalid binary error 2022-07-21 15:18:28 +05:30
muzam1l 895b34cc68 Fix compile result message on invalid wasm 2022-07-21 15:03:16 +05:30
muzam1l b9da659f83 Fix that weird anonymous zero in the ui. 2022-07-21 14:14:40 +05:30
muzamil 3897f2d823 Merge pull request #246 from XRPLF/feat/tabs-comp
Tabs and Context menu components.
2022-07-20 17:46:04 +05:30
muzam1l 6a3ff3e1d7 Implement tab renaming. 2022-07-20 16:56:55 +05:30
muzam1l bf792f1495 Fix dependecy issues. 2022-07-20 14:55:59 +05:30
muzam1l df3210a663 Add context menu component 2022-07-20 14:12:48 +05:30
muzam1l bad7730c32 Fix extension requirement error. 2022-07-20 14:05:02 +05:30
muzam1l adb6a78549 update active compiled file based on active editor file. 2022-07-20 14:05:02 +05:30
muzam1l 8cc27f20c3 Migrate File navigation to tabs component. 2022-07-20 14:05:02 +05:30
muzamil 8e49a3f5f1 Merge pull request #253 from XRPLF/fix/json-tx
Fix json mode.
2022-07-20 13:59:42 +05:30
muzam1l 3179757469 Fix TxFields type. 2022-07-19 17:46:13 +05:30
muzam1l 554cfb3db9 Add fee field to all transactions. 2022-07-19 17:45:41 +05:30
muzam1l 637a066f69 Fix tx reset state button 2022-07-19 17:36:09 +05:30
muzam1l c9a852e9be Add destination field to NFTokenCreateOffer 2022-07-19 16:05:10 +05:30
muzam1l 307a5407eb Fix handling of Destination field in transactions. 2022-07-19 16:01:21 +05:30
muzam1l faa28845c8 Ensure editor value updation on state change 2022-07-18 21:26:05 +05:30
muzam1l 168d11d48e Remove editorSavedValue from tx state 2022-07-18 19:10:33 +05:30
muzam1l 60f2bb558c Cancel button on json discard dialog. 2022-07-18 17:10:13 +05:30
muzamil fdf33b9f45 Merge pull request #251 from XRPLF/fix/binary-codec
Fix binary codec (#250)
2022-07-15 16:17:43 +05:30
muzam1l d05180d148 Fix array fields. 2022-07-15 14:49:54 +05:30
muzamilanddangell7 bfaa6be17d Fix binary codec (#250)
* TokenID -> NFTokenID

* update xrpl-accountlib

* fixup patch

* add missing transaction types

* fix: repositories

* fix: update @types/react, move middleware

* revert yarn.lock

* revert

* revert: yarn.lock

* fix: transactions

* update NFTokenCancelOffer

* add missing fee

* add: nft transaction type + recalculate `start`

Co-authored-by: dangell7 <[email protected]>
2022-07-15 13:34:19 +05:30
muzamil 9e368dec84 Merge pull request #248 from XRPLF/fix/trust-set
Fix TrustSet transaction example.
2022-07-15 13:13:21 +05:30
muzam1l 25eec6980f Fix TrustSet transaction example. 2022-07-14 20:05:32 +05:30
muzamil 8e2f20c5ac Merge pull request #239 from XRPLF/feat/monaco-comp
Monaco component.
2022-07-14 13:51:24 +05:30
muzam1l a3d094e873 Fix some more spelling errors. 2022-07-13 20:11:21 +05:30
muzam1l ef70bfb13a Fix spelling error 2022-07-13 19:49:54 +05:30
muzam1l c26c7c13d1 Improve compile error handling. 2022-07-13 16:50:47 +05:30
muzam1l fc461ddd0d Content changed warning on deploy page. 2022-07-13 16:17:31 +05:30
muzam1l c7001f6089 'Exit editor mode' button in wat editor. 2022-07-13 15:40:08 +05:30
muzam1l 243cbfec08 Upgrade hooks editor to comp. 2022-07-13 15:09:45 +05:30
muzam1l 1295e7fa41 Deploy editor to comp 2022-07-13 15:09:45 +05:30
muzam1l 793623d216 Migrate transaction json editor to comp. 2022-07-13 15:09:45 +05:30
muzam1l 0cde0eb240 Monaco component 2022-07-13 15:09:45 +05:30
muzamil e2acb48e03 Merge pull request #236 from XRPLF/feat/jsdoc-to-ui
User declaration of input fields via JSDOC in script files.
2022-07-12 16:53:27 +05:30
muzam1l a4373bb970 fix spelling error 2022-07-12 12:40:35 +05:30
muzam1l cfb791092a Update template files of some examples to forked versions. 2022-07-11 14:44:35 +05:30
muzamil 3fcbac5ed9 Merge pull request #231 from XRPLF/feat/create-account-name
Allow passing desired name while creating account.
2022-07-11 14:29:38 +05:30
muzam1l c40b272ce8 Fix disabled prop behaviour 2022-07-07 23:34:14 +05:30
muzam1l 860ff66a8a fix error in error handler 2022-07-07 23:25:53 +05:30
muzam1l f4f700bea1 Handle required prop on fields. 2022-07-06 19:30:25 +05:30
muzam1l 789bc00ac3 Enhance account secret label! 2022-07-06 19:03:29 +05:30
muzam1l 6a0aabdeda Handle jsdoc errors 2022-07-05 19:38:26 +05:30
muzam1l 175b6266e8 Add script error handling 2022-07-05 19:09:08 +05:30
muzam1l 621482e2ee enhance empty log display 2022-07-05 18:52:45 +05:30
muzam1l e55f48bc83 Use Single LogBox comp for scripts too 2022-07-05 18:45:52 +05:30
muzam1l 3e9e26a46a Data variables in process.env instead of process 2022-07-05 16:58:07 +05:30
muzam1l f0e730bb9b Remove secret tag on type Account 2022-07-05 16:35:23 +05:30
muzam1l 6ce4828fc6 Remove console logs 2022-07-05 16:25:15 +05:30
muzam1l bb0a246ae5 User declaration of input fields via JSDOC in script files. 2022-07-05 16:02:15 +05:30
Valtteri KarestoandVaclav Barta 3af2bad536 Make ping interval longer (#232)
* Make ping interval 45s

Co-authored-by: Vaclav Barta <[email protected]>
2022-07-04 12:22:06 +02:00
muzam1l 4f1b877db0 Added optional tag to create account label. 2022-07-01 19:41:52 +05:30
muzamil 0289d64f5e Merge pull request #233 from XRPLF/fix/tab-names
Fix tab names
2022-07-01 19:29:33 +05:30
muzamil 868a0bcf78 Merge pull request #234 from XRPLF/feat/account-in-deploy-dialog
Add account selectable in deploy dialog.
2022-07-01 18:51:04 +05:30
muzam1l aab2476a05 Add account selectable in deploy dialog. 2022-07-01 18:06:01 +05:30
muzam1l cb25986d72 update transaction tab labels 2022-07-01 17:30:57 +05:30
muzam1l 309ad57173 Skip auto appneding test file extension. 2022-07-01 17:26:10 +05:30
muzam1l 53afb1d3d1 Fix html erros. 2022-07-01 17:08:34 +05:30
muzam1l 31ff7c0e28 Name field in import account dialog. 2022-07-01 16:43:15 +05:30
muzam1l dfa35df465 reset input value on submit 2022-07-01 16:27:13 +05:30
muzam1l f163b052e1 Allow passing desired name while creating account. 2022-07-01 14:33:28 +05:30
Valtteri Karesto 25c5b9c015 Merge pull request #229 from XRPLF/feat/links-to-explorer
Link from hashes/addresses to Hook Explorer
2022-06-30 15:40:57 +03:00
Valtteri Karesto 407e3946ce Added underline on hover to links 2022-06-30 15:30:43 +03:00
Valtteri Karesto dc5b0d71eb Simplified hook state, since endpoint now works with hookhashes 2022-06-30 08:54:43 +03:00
Valtteri Karesto 3fd6c3f50e Remove debug code 2022-06-29 18:03:26 +03:00
Valtteri Karesto ec8bfc5eee Add links to account modal 2022-06-29 15:26:33 +03:00
Valtteri Karesto b4a0bcb90d Merge pull request #227 from XRPLF/feat/remember-deploy-values
Remember deploy values / Add feedback button
2022-06-29 14:08:47 +03:00
Valtteri Karesto 2c729e2aa4 Update button text 2022-06-29 14:04:06 +03:00
Valtteri Karesto 1cb2542170 Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/remember-deploy-values 2022-06-29 13:47:41 +03:00
Wietse Wind 00b309df34 Merge pull request #228 from XRPLF/feature/add-plausible-analytics
Add plausible analytics to builder
2022-06-29 12:10:58 +02:00
Joni Juup a6fc730de6 add plausible analytics to builder 2022-06-29 12:58:17 +03:00
Valtteri Karesto 2245c5a221 Test gh integration 2022-06-29 12:18:38 +03:00
Valtteri Karesto 60c33661ad Add proper defaultvalue 2022-06-29 12:14:52 +03:00
Valtteri Karesto ea21c85038 Add noopener and noreferrer to link 2022-06-29 11:37:22 +03:00
Valtteri Karesto 5478f43609 persist deploy values in memory 2022-06-29 11:32:15 +03:00
Valtteri Karesto a9b64abb85 Add feedback button, show modal only on homepage 2022-06-29 11:31:46 +03:00
Valtteri Karesto c6ced424d8 Merge pull request #226 from XRPLF/feat/long-navigation-support
Fix #215 scrollbar issues
2022-06-28 14:59:35 +03:00
Valtteri Karesto 3a1159cffc Make thumbs more visible 2022-06-28 14:49:34 +03:00
Valtteri Karesto 3136de1bd1 Slight style adjustments 2022-06-28 14:38:59 +03:00
Valtteri Karesto 67ffd3f1b4 Fix #215 scrollbar issues 2022-06-28 14:01:08 +03:00
Valtteri Karesto 8508cb69c4 Merge pull request #224 from XRPLF/feat/debug-stream-fixes
Feat/debug stream fixes
2022-06-28 11:31:03 +03:00
Valtteri Karesto 89217d2633 Remove console.log 2022-06-28 11:03:56 +03:00
Valtteri Karesto ba1b64391c ping socket connection 2022-06-28 09:36:45 +03:00
Valtteri Karesto 098d919a77 Bring back dispose 2022-06-27 15:03:49 +03:00
Valtteri Karesto b2af37ab4b Use reconnecting-websocket and refactor debug stream 2022-06-27 15:03:42 +03:00
Valtteri KarestoandVaclav Barta dcb7e94e86 New gists provided by XRPL (#223)
* Prepare logic for new gists

* Remove unused imports

* updated gist IDs for xrplfgists

* Add macro.h to apiheaderfiles

* Update headers

Co-authored-by: Vaclav Barta <[email protected]>
2022-06-27 08:25:25 +02:00
Valtteri Karesto 67848b3d8d Merge pull request #222 from XRPLF/fix/suggest-button-disabled
Fix/suggest button disabled
2022-06-23 11:12:04 +03:00
Valtteri Karesto 31a86263a1 Disable suggest if no account selected 2022-06-22 14:42:50 +03:00
Valtteri Karesto 4d0025afc1 Fix splitscreen error 2022-06-22 14:42:39 +03:00
Valtteri Karesto f85bd2398d Merge pull request #220 from XRPLF/fix/decimals-again
fixes #201
2022-06-22 12:28:12 +03:00
Valtteri Karesto a2a6596cc5 Prevent pasting decimals 2022-06-22 12:16:36 +03:00
Valtteri Karesto 37208ce97e fixes #201 2022-06-22 12:03:43 +03:00
Valtteri Karesto bf4042926d Merge pull request #218 from XRPLF/feat/ui-fixes
Fixes #213 and fixes #200
2022-06-22 11:41:31 +03:00
Valtteri Karesto 3ccc1c16ac Fixed deploy 2022-06-22 11:32:07 +03:00
Valtteri Karesto 135f0c91a1 Fixes #213 and fixes #200 2022-06-22 11:06:15 +03:00
Valtteri Karesto 8f5786e242 Merge pull request #216 from XRPLF/feat/change-default-optimization
fixes #214 change default optimization
2022-06-22 09:56:15 +03:00
Valtteri Karesto 810eb4ca27 Add new default to compileCode as well 2022-06-22 09:52:47 +03:00
Valtteri Karesto e6574f9f12 fixes #214 change default optimization 2022-06-22 09:47:30 +03:00
Valtteri Karesto 1a6726fabf Merge pull request #212 from XRPLF/feat/improve-supp-js
Improve supplementary JS feature
2022-06-21 11:28:42 +03:00
Valtteri Karesto 89f8671217 Clear log should now work 2022-06-21 10:53:52 +03:00
Valtteri Karesto fb5259221b Changed color of starting running 2022-06-21 09:43:29 +03:00
Valtteri Karesto fd17f59616 Show LogBoxForScrips if js file active 2022-06-20 23:54:56 +03:00
Valtteri Karesto 91bbc7ea61 Catch template errors, add better labels, styling 2022-06-20 23:54:33 +03:00
Valtteri Karesto 783d832c6d Remove export for unused component 2022-06-20 23:53:32 +03:00
Valtteri Karesto 698ca376e7 Add showbuttons prop to LogBoxForScripts 2022-06-20 23:53:15 +03:00
Valtteri Karesto bfd9e21ab8 Remove unused component 2022-06-20 23:52:45 +03:00
Valtteri Karesto e46411f245 Rename template helpers 2022-06-20 14:53:30 +03:00
Valtteri Karesto 08447c6b29 Add support for select parameters 2022-06-20 14:16:16 +03:00
Valtteri Karesto 9216cc6bf7 When downloading zip, include wasm if it exists 2022-06-20 11:01:13 +03:00
Valtteri Karesto 5108b08e39 Do not show scripts panel if no supplementary scripts 2022-06-20 10:40:47 +03:00
Valtteri Karesto 6c46a4f809 Merge pull request #211 from XRPLF/feat/user-provided-scripts
Feat/user provided scripts
2022-06-20 10:16:06 +03:00
Valtteri Karesto 0ea88f0d32 Remove unused imports 2022-06-17 21:48:32 +03:00
Valtteri Karesto 4c2e1f36f3 Add empty line 2022-06-17 21:42:50 +03:00
Valtteri Karesto fa5315fc0e Fix errors on development 2022-06-17 17:32:21 +03:00
Valtteri Karesto eda8b1550c If not string, stringify it 2022-06-17 13:42:19 +03:00
Valtteri Karesto 742b11374f Just empty body 2022-06-16 10:12:40 +03:00
Valtteri Karesto d16e83dcfa No need for specific log() or error() functions anymore 2022-06-16 00:50:16 +03:00
Valtteri Karesto 155aa57784 Fix warning about useLayoutEffect 2022-06-16 00:49:43 +03:00
Valtteri Karesto b88b6da7d9 Add one more example env value 2022-06-16 00:49:17 +03:00
Valtteri Karesto fa13f7e282 Add logic for scripts 2022-06-15 15:15:08 +03:00
Valtteri Karesto f1a43ef758 Add dependencies and fix handlebars errors 2022-06-15 15:14:51 +03:00
Vaclav Barta 4217813fd7 take default header names from /api/header-files JSON (#210)
This is a client-side upgrade for the server-side XRPLF/xrpl-hooks-compiler#17 - not hard-coding expected header names but taking them from the compilation backend.
2022-06-13 08:22:55 +02:00
muzamil c588f7b1f3 Merge pull request #209 from XRPLF/feat/json-in-ui
Display tx json in UI inside textarea.
2022-06-10 16:50:28 +05:30
muzam1l 985e8ee820 Merge branch 'main' into feat/json-in-ui 2022-06-10 16:50:08 +05:30
muzamil 8832e76a0a Merge pull request #208 from XRPLF/feat/default-tx
Set payment as default transaction
2022-06-10 16:47:01 +05:30
muzam1l 9777f1dbd1 Increase vert. padding of textarea 2022-06-08 15:03:51 +05:30
muzam1l 213d468aab Merge branch 'main' into feat/json-in-ui 2022-06-07 19:06:57 +05:30
muzam1l 46becb0e7b Display tx json in UI inside textarea. 2022-06-07 19:06:13 +05:30
Valtteri Karesto fad6bd100a Merge pull request #205 from XRPLF/feat/prevent-decimals
Prevent inputing decimals
2022-06-07 14:55:05 +03:00
muzam1l 5a11f83fea Set payment as default transaction 2022-06-07 00:52:59 +05:30
Valtteri Karesto 525338abf7 Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/prevent-decimals 2022-06-03 17:21:00 +03:00
Valtteri Karesto ea977816a4 Merge pull request #203 from XRPLF/feat/tab-ordering
Sort the files after fetching
2022-06-03 17:20:27 +03:00
Valtteri Karesto 0ee599a2b6 Update sort 2022-06-03 16:50:31 +03:00
Valtteri Karesto 02c59f8d79 Update sorting function 2022-06-03 16:34:46 +03:00
Valtteri Karesto 3d5b77e60a Fix issue if no filenames 2022-06-03 16:34:36 +03:00
Valtteri Karesto 92a167d47a Prevent also pasting 2022-06-03 15:33:46 +03:00
Valtteri Karesto d41e263942 Merge pull request #204 from XRPLF/feat/remove-cleaner-ui
Remove cleaner options from UI
2022-06-03 15:08:50 +03:00
Valtteri Karesto bd1226fe90 Remove log 2022-06-02 16:35:18 +03:00
Valtteri Karesto 57403e42dd Adjustments to sorting 2022-06-02 16:34:53 +03:00
Valtteri Karesto 2b42a96c4a Update ordering 2022-06-02 16:15:39 +03:00
Valtteri Karesto 80d6bb691d Prevent inputing decimals 2022-06-02 15:22:28 +03:00
Valtteri Karesto c7e4cd7c92 Remove unused imports 2022-06-02 14:11:18 +03:00
Valtteri Karesto 4a22861860 Sort the files after fetching 2022-06-02 14:04:52 +03:00
muzamil b09d029931 Merge pull request #196 from XRPLF/feat/fee-hint
Fee hints in transactions.
2022-06-01 16:21:24 +05:30
muzam1l b2dc49754f Error toast in suggest button! 2022-06-01 15:51:08 +05:30
muzam1l 6f636645f7 Fix json saving mismatch 2022-05-31 00:53:58 +05:30
muzam1l 377c963c7a FIx json mode schema 2022-05-30 23:32:53 +05:30
muzam1l ae038f17ff Suggest fee button in transaction ui 2022-05-30 23:01:46 +05:30
Vaclav Barta 0d8f2c31e7 Feature/hook documentation (#197)
* updated hooks-entry-points-check

* added hooks-trivial-cbak

* updated hooks-hash-buf-len: nonce => etxn_nonce + ledger_nonce
2022-05-30 12:49:19 +02:00
muzam1l da9986eb66 Remove unnecessary console logs 2022-05-27 22:46:15 +05:30
muzam1l a21350770e Fee hints in transactions. 2022-05-27 16:44:01 +05:30
Valtteri Karesto 49dfd43220 Merge pull request #193 from XRPLF/feat/fee-estimates
Feat/fee estimates
2022-05-27 12:23:50 +03:00
Valtteri Karesto 4472957f5c Updated based on feedback 2022-05-27 10:43:16 +03:00
muzamil ca46707bb5 Merge pull request #195 from XRPLF/feat/tx_hash_link
Show tx hash instead of server ledger index in deploy log.
2022-05-26 15:28:31 +05:30
muzam1l 704ebe4b92 Show tx hash instead of server ledger index in deploy log. 2022-05-26 14:55:24 +05:30
Valtteri Karesto 9a6ef2c393 Minor fixes based on feedback 2022-05-25 13:39:52 +03:00
Valtteri Karesto 56203ce9c6 Remove package-lock 2022-05-25 13:10:05 +03:00
Valtteri Karesto 933bdb5968 Add fee estimate button to fee and update the deploying 2022-05-25 13:05:04 +03:00
Valtteri Karesto 864711697b Update accounts 2022-05-25 13:03:49 +03:00
Valtteri Karesto e5eaf09721 Just return the fee values, no mutating 2022-05-25 13:03:38 +03:00
Valtteri Karesto d0dde56c67 Merge pull request #192 from XRPLF/fix/fix-sequence-number
Fix/fix sequence number
2022-05-24 18:20:31 +03:00
Valtteri Karesto 45c6927e72 Take next sequence number from response 2022-05-24 18:16:56 +03:00
Valtteri Karesto 6014b6e79f Update sequence after successful request 2022-05-24 18:14:01 +03:00
Valtteri Karesto 04a99227df Merge pull request #191 from XRPLF/fix/v2-updates
Fix/v2 updates
2022-05-24 15:44:00 +03:00
Valtteri Karesto 0965a1e898 Merge pull request #190 from XRPLF/feat/update-descriptions
Update meta tags
2022-05-24 15:43:47 +03:00
Valtteri Karesto 32445dbebf Mutate tx with fee estimation 2022-05-24 15:27:20 +03:00
Valtteri Karesto 1a1d4901aa Add estimate fee function 2022-05-24 15:26:38 +03:00
Valtteri Karesto 8b646c56dc Fix recursion 2022-05-24 15:26:03 +03:00
Valtteri Karesto ac38bbc72c Turn the cleaner on by default 2022-05-24 15:25:33 +03:00
Valtteri Karesto bf1182351a Added link to project 2022-05-23 13:28:41 +03:00
Valtteri Karesto 55e48a943b Update readme 2022-05-23 13:26:46 +03:00
Valtteri Karesto faf417be69 Update meta tags 2022-05-23 08:55:03 +03:00
Valtteri Karesto c2eb57211f hotfix/Remove debug code 2022-05-12 14:31:00 +03:00
Valtteri Karesto 0e97df3c8e Merge pull request #188 from eqlabs/fix/old-account-fix
Fixes to users who has old accounts
2022-05-12 08:35:02 +03:00
Valtteri Karesto 5dd0dfdc18 Fixes to users who has old accounts 2022-05-11 16:07:49 +03:00
Valtteri Karesto ef48bac8f6 Merge pull request #187 from eqlabs/feat/fix-modal
Updated main modal
2022-05-11 14:31:53 +03:00
Valtteri Karesto 3a3d984098 Updated some copy 2022-05-11 14:22:48 +03:00
Valtteri Karesto 2300c201f8 Lift up modal a bit 2022-05-11 13:37:05 +03:00
Valtteri Karesto 329dc4a355 Remove unused import 2022-05-11 13:31:27 +03:00
Valtteri Karesto cd6a5b23d4 Icons are now components so we can control the color 2022-05-11 13:30:02 +03:00
Valtteri Karesto 4dd7cbe2ca Updated main modal 2022-05-11 13:07:08 +03:00
Valtteri Karesto 260de7c838 Merge pull request #186 from eqlabs/fix/change-label
Added experimental to label
2022-05-11 10:02:03 +03:00
Valtteri Karesto e0ed31f220 Added experimental to label 2022-05-11 08:50:00 +03:00
muzamil eba183497f Merge pull request #176 from eqlabs/feat/json-transactions
Json transactions
2022-05-10 18:15:00 +05:30
Valtteri Karesto 4378afa9a1 Merge pull request #185 from eqlabs/feat/add-cleaner-ui
Feat/add cleaner UI
2022-05-10 14:43:04 +03:00
Valtteri Karesto 491e10920b Updated label 2022-05-10 13:25:41 +03:00
Valtteri Karesto 65bb209713 Fixed wrong key 2022-05-10 11:53:31 +03:00
Valtteri Karesto c07e70acc9 Add popover descriptions 2022-05-10 11:52:03 +03:00
Valtteri Karesto 8fd7f8ecad Change state key 2022-05-10 11:51:49 +03:00
Valtteri Karesto 2bb3c646db Add compile logic to ui 2022-05-09 14:18:32 +03:00
Valtteri Karesto 87f10a11b0 Add switch component and bg color to popover 2022-05-09 14:18:23 +03:00
Valtteri Karesto 949fb45ae2 Add colors 2022-05-09 14:18:02 +03:00
Valtteri Karesto ea52f014dd Add radix switch 2022-05-09 14:17:55 +03:00
Valtteri Karesto 77eab8d88d Merge pull request #182 from eqlabs/fix/save-before-sync
"Save" files before syncing to github
2022-05-09 12:56:37 +03:00
Vaclav Barta 4ca8f5f236 Merge pull request #184 from eqlabs/feature/hook-doc-upd
documentation for new checks
2022-05-09 08:36:07 +02:00
muzam1l 53f2a71b08 minor changes 2022-05-05 21:02:33 +05:30
muzam1l 866f6257f1 json schema 2022-05-05 20:00:10 +05:30
Vaclav Barta 814b074cc0 added hooks-control-string-arg, hooks-release-define and hooks-skip-hash-buf-len docs 2022-05-04 14:22:25 +02:00
muzam1l 386619619b support object Amount 2022-05-04 16:57:32 +05:30
muzam1l d8bf10d0b8 fix handling Destination field 2022-05-04 16:12:50 +05:30
muzam1l d18c893025 Replace native alerts 2022-05-04 14:38:59 +05:30
Valtteri Karesto 822f7a30f5 "Save" files before syncing to github 2022-05-03 16:33:17 +03:00
Valtteri Karesto 1d66137c23 Merge pull request #181 from eqlabs/feat/add-optimization-settings
Feat/add optimization settings
2022-05-03 15:30:51 +03:00
Valtteri Karesto 4c42e75686 Remove example 2022-05-03 14:46:42 +03:00
Valtteri Karesto 501b7fefec Removed unnecessary setting 2022-05-03 14:17:36 +03:00
Valtteri Karesto aa7e1517a2 Removed unused import 2022-05-03 14:07:43 +03:00
Valtteri Karesto e33093f160 Remove console.log 2022-05-03 14:04:18 +03:00
Valtteri Karesto 923b689c98 Add compile options to ui 2022-05-03 14:03:14 +03:00
Valtteri Karesto 246e7f137f Add compile options to compile function 2022-05-03 14:03:02 +03:00
Valtteri Karesto 5defd12a11 Add popover component 2022-05-03 14:02:43 +03:00
Valtteri Karesto abb7c2bb28 Add compileoptions to global state 2022-05-03 14:02:26 +03:00
Valtteri Karesto 12013907f8 Added radix popover 2022-05-03 14:02:16 +03:00
Valtteri Karesto ec75fff74b Merge pull request #179 from eqlabs/fix/issue-175
Initial fix for issue #175
2022-05-03 11:20:19 +03:00
muzam1l 5e997044ed alert dialog component 2022-05-02 21:05:18 +05:30
muzam1l e88720327e Allow using only imported accounts 2022-04-28 18:51:50 +05:30
muzam1l bf568c3f46 Update button text 2022-04-28 18:33:20 +05:30
Valtteri Karesto 7c1068449f Initial fix for issue #175 2022-04-28 11:46:24 +03:00
Valtteri Karesto b66d2a09a0 Merge pull request #178 from eqlabs/fix/issue-177
Fixes issue #177
2022-04-28 10:53:26 +03:00
muzam1l 1d3bd128f8 Implement auto save 2022-04-27 23:18:00 +05:30
muzam1l ab1f45febd Editable json and unified state. 2022-04-27 18:34:28 +05:30
Valtteri Karesto 54265e024c Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into fix/issue-177 2022-04-27 15:38:53 +03:00
Valtteri Karesto 20cb66ba18 Fixes issue #177 2022-04-27 15:33:42 +03:00
muzam1l 56a9806b70 add path to tx editor and refactor its value providing 2022-04-25 15:47:58 +05:30
muzam1l b3f2d0fb6d Readonly tx json view 2022-04-22 18:46:35 +05:30
Vaclav Barta b7d62dda83 Merge pull request #174 from eqlabs/feat/req-bin-hook-params
requiring user-quoted Hook Parameter values
2022-04-22 13:25:48 +02:00
Vaclav Barta c690334f92 requiring user-quoted Hook Parameter values 2022-04-21 09:02:59 +02:00
muzamil 587f09ec00 Merge pull request #168 from eqlabs/feat/improved-logs
Background logs.
2022-04-20 16:45:14 +05:30
muzam1l 9296ea1acc remove a console.log 2022-04-20 16:44:49 +05:30
muzam1l 582fb17c94 Link ledger index to explorer 2022-04-20 13:45:17 +05:30
muzam1l aff0142870 Debug history log only after clear timestamp 2022-04-19 20:51:11 +05:30
muzamil df51d87cb2 Merge branch 'main' into feat/improved-logs 2022-04-19 20:43:05 +05:30
muzam1l 6a46f5f173 Merge branch 'main' into feat/improved-logs 2022-04-19 18:52:13 +05:30
muzamil 9e25cefef9 Merge pull request #162 from eqlabs/feat/transaction-persistence
Persisted transactions and debug stream state.
2022-04-19 18:42:46 +05:30
muzam1l 95966fa514 Merge branch 'main' into feat/transaction-persistence 2022-04-19 18:38:56 +05:30
Valtteri Karesto f49d69e75d Merge pull request #172 from eqlabs/fix/regression-#171
Fix/regression #171
2022-04-14 15:41:59 +03:00
Valtteri Karesto da4b2e68ab Fix issue #171 2022-04-14 15:28:28 +03:00
Valtteri Karesto 5557b1bcba Remove console.logs 2022-04-14 15:27:08 +03:00
Valtteri Karesto f4b5f98a44 Merge pull request #169 from eqlabs/feat/add-namespace-to-sethook
Add namespace to sethook modal
2022-04-14 15:19:08 +03:00
Valtteri Karesto b1d39740de Changed labels, added default value to form so the initial render has correct value 2022-04-14 15:10:38 +03:00
muzam1l dfe5589074 fix 2022-04-14 17:05:36 +05:30
muzam1l cdc50da840 don't show legacy logs in debug stream 2022-04-14 16:31:19 +05:30
muzam1l 4893b41936 revert to browser time parsing only 2022-04-14 15:02:00 +05:30
Valtteri Karesto 16cbdafb27 Add computed sha256 field to sethook form 2022-04-14 11:52:24 +03:00
Valtteri Karesto 5559fb7be3 rename hash to sha256 2022-04-14 11:51:56 +03:00
Valtteri Karesto 3c4305127b Use user input namespace value 2022-04-13 23:58:02 +03:00
Valtteri Karesto 2a76fa0c35 Add namespace field to set hook modal 2022-04-13 23:57:47 +03:00
muzam1l bf21fe36c3 timestamp and log fixes 2022-04-13 21:44:24 +05:30
muzam1l a33a3eb6e2 Background logs 2022-04-13 16:59:02 +05:30
Valtteri Karesto 919c4e173c Merge pull request #166 from eqlabs/feat/remove-peek-from-context-menu
Feat/remove peek from context menu
2022-04-12 09:19:19 +03:00
Valtteri Karesto 650324f434 Hacky way to hide Peek from context menu 2022-04-11 17:07:25 +03:00
Valtteri Karesto 74db96e8a5 add ; 2022-04-11 17:07:13 +03:00
Valtteri Karesto c99c821081 Update monaco editor and next-themes 2022-04-11 17:07:06 +03:00
Valtteri Karesto e53a533026 Merge pull request #161 from eqlabs/fix/add-account-creation-error
Fixes issue #68
2022-04-11 14:54:12 +03:00
Valtteri Karesto 5f118e00cb Added error check 2022-04-11 11:46:49 +03:00
muzam1l 46e6927c68 Fix debug stream default account being enforcive 2022-04-08 16:33:46 +05:30
muzam1l de95a82c5a Fix tx fields not updating 2022-04-08 16:25:03 +05:30
muzam1l 3b66d64c14 Fix debug stream default account when changing tx tabs 2022-04-08 16:03:37 +05:30
muzam1l 6e90a4c3d7 some refactoring 2022-04-08 15:17:54 +05:30
muzam1l 2287e7babb Added Label comp and minor style fixes 2022-04-08 14:41:08 +05:30
muzam1l c219f7ea00 Persisted transaction tabs state 2022-04-07 16:10:16 +05:30
Valtteri Karesto e795ce4472 Fixes issue #68 2022-04-06 14:15:53 +03:00
muzam1l 04e2274dbf some refactoring 2022-04-06 14:42:39 +05:30
Valtteri Karesto 6e39b90c1e Merge pull request #160 from eqlabs/feat/fetch-headers-from-api
Fetch header files from c2wasm api
2022-04-06 10:28:51 +03:00
Valtteri Karesto f186a807c1 Remove unused file 2022-04-06 09:42:03 +03:00
Valtteri Karesto 5ad9ed1688 Fetch header files from c2wasm api 2022-04-05 14:52:19 +03:00
muzam1l 842b8a5226 Set debug stream default account from transaction account 2022-04-04 21:29:08 +05:30
Vaclav Barta 234832138f fixes #155 2022-04-01 08:42:10 +02:00
Valtteri Karesto 28d94a1475 Merge pull request #152 from eqlabs/fix/cloud-upload-button
Fix/cloud upload button
2022-03-31 10:15:31 +03:00
Valtteri Karesto 594aee6cd2 Merge pull request #154 from eqlabs/feat/add-header-templates
Feat/add header templates
2022-03-30 12:31:08 +03:00
Valtteri Karesto d75910972f Change order 2022-03-30 12:09:16 +03:00
Valtteri Karesto 589c604a12 Add header files as hard coded 2022-03-30 12:00:27 +03:00
Valtteri Karesto 8394a11705 Merge pull request #151 from eqlabs/fix/disable-delete-hook
Fixes issue #148
2022-03-29 23:34:12 +03:00
Valtteri Karesto 4ad329882c Add tooltips to navigation items 2022-03-29 16:52:48 +03:00
Valtteri Karesto ee86b91e82 Add tooltip component 2022-03-29 16:52:39 +03:00
Valtteri Karesto d2addf782e Add extra isDisabled style for button 2022-03-29 16:52:24 +03:00
Valtteri Karesto 51f7bd509b Add radix tooltip 2022-03-29 16:52:02 +03:00
Valtteri Karesto e064251ff9 Merge pull request #150 from eqlabs/feat/fix-newlines
Fixes issue #86 again, regressed in #107 for Firefox
2022-03-29 15:29:51 +03:00
Valtteri Karesto 5aeed7c246 Few more changes to deleteHook function 2022-03-29 15:23:30 +03:00
Valtteri Karesto 8d03edc299 Fixes issue #148 2022-03-29 14:03:06 +03:00
Valtteri Karesto 95022ef121 Fixes issue #86 again, regressed in #107 for Firefox 2022-03-29 13:55:10 +03:00
Valtteri Karesto 4519906b78 hotfix/remove-console-log 2022-03-28 15:35:27 +03:00
Valtteri Karesto 88a47c49a4 Merge pull request #149 from eqlabs/fix/fix-hook-params
Convert hook params to hex blobs
2022-03-28 15:33:23 +03:00
Valtteri Karesto 1ab03f9bed Fix types 2022-03-28 15:22:10 +03:00
Valtteri Karesto 84ff667135 Convert hook params to hex blobs 2022-03-28 15:20:14 +03:00
Valtteri Karesto 0d10e782f3 Ask faucets account only once if you dont have one 2022-03-25 11:35:23 +02:00
Valtteri Karesto 84e6763495 hotfix/keep-accounts 2022-03-25 10:49:29 +02:00
Vaclav Barta 7ffcfd694d Feature/new hook doc (#144)
* added hooks-param-buf-len doc file

* showing help for hooks-param-buf-len

* added hooks-param-set-buf-len
2022-03-25 09:23:53 +01:00
Valtteri Karesto 77e4917d38 Merge pull request #145 from eqlabs/hotfix/fix-accounts
Fix problem with accounts
2022-03-25 10:21:47 +02:00
Valtteri Karesto e4238a40cc Fix problem with accounts 2022-03-25 10:18:55 +02:00
Valtteri Karesto 42c0b20512 Merge pull request #136 from eqlabs/feat/hooks-v2-preparations
Feat/hooks v2 preparations
2022-03-25 09:48:19 +02:00
Valtteri Karesto 43154ff6d8 Updated hooks templates 2022-03-25 09:41:29 +02:00
Valtteri Karesto 8197b510f9 delete hook and delete account features, #140 #47 2022-03-24 22:18:59 +02:00
Valtteri Karesto fc7652f48e Remove logging 2022-03-24 21:22:27 +02:00
Valtteri Karesto bd32555617 improve toast message 2022-03-24 21:21:35 +02:00
Valtteri Karesto fc6f420e1e Add funds feature added 2022-03-24 21:20:29 +02:00
Valtteri Karesto d3c36765de Fix select option focus color 2022-03-24 17:36:31 +02:00
Valtteri Karesto 2628a12673 Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/hooks-v2-preparations 2022-03-24 17:32:10 +02:00
Valtteri Karesto f6c1869b5d Bring back styled logs after debug round 2022-03-24 17:31:49 +02:00
Valtteri Karesto 62c8b4f217 Merge pull request #130 from eqlabs/fix/do-not-allow-special-chars
Prevent special characters on filename
2022-03-24 16:54:59 +02:00
Valtteri Karesto 8798e5a233 Comment out code temporarily 2022-03-24 16:09:05 +02:00
Valtteri Karesto 5f7d42843c temporarily print raw debug messages 2022-03-24 16:07:02 +02:00
Valtteri Karesto 302b36dde8 Give new ids to account requests 2022-03-24 16:06:37 +02:00
Valtteri Karesto 3e7c7b1969 Updated regex 2022-03-24 14:49:30 +02:00
Valtteri Karesto 936bbc503a Updated refetch time to 5 seconds 2022-03-24 14:40:58 +02:00
Valtteri Karesto 81890c8833 Added loading state to set hook modal 2022-03-23 13:28:59 +02:00
Valtteri Karesto 50fa20c39a Fix styling of the text 2022-03-23 12:41:14 +02:00
Valtteri Karesto 11f2cffc87 Loosen up filters a bit 2022-03-23 09:52:57 +02:00
Valtteri Karesto bbd1d162f0 Fix styling issues 2022-03-22 17:36:10 +02:00
Valtteri Karesto b301a860bf Fix overflow problems and other styling issues 2022-03-22 17:08:38 +02:00
Valtteri Karesto ff697b96ea reverse hookon calculation logic 2022-03-22 17:08:19 +02:00
Valtteri Karesto 48e9898e31 Add smarter filtering for debugstream 2022-03-22 17:08:00 +02:00
Valtteri Karesto 2e25242ebe Update urls 2022-03-22 17:07:41 +02:00
Valtteri Karesto e32e07f7fd Added xor 2022-03-22 17:07:00 +02:00
Valtteri Karesto 0d2a17008e Update envs 2022-03-22 17:06:50 +02:00
Valtteri Karesto a87b3de6c4 Add few more fixes and styling to modals 2022-03-17 19:10:27 +02:00
Valtteri Karesto 23068ff477 Style select again to support multiselect 2022-03-17 19:10:02 +02:00
Vaclav Barta a12a5dfbac Merge pull request #138 from eqlabs/bugfix/timeago-init
alternative TimeAgo setup - terminal problems unfortunately persist, but at least the warning is gone...
2022-03-17 14:08:20 +01:00
Vaclav Barta 5a598cb091 alternative TimeAgo setup 2022-03-17 08:50:40 +01:00
Valtteri Karesto be39054a2f Minor updates to debugstream, related to v2 2022-03-16 19:01:13 +02:00
Valtteri Karesto 0add65dd1c Update hook deployment logic for v2 2022-03-16 19:01:01 +02:00
Valtteri Karesto 82170ad4f8 update set hook functionality 2022-03-16 19:00:30 +02:00
Valtteri Karesto af49426eb0 Move fonts to correct location 2022-03-16 19:00:15 +02:00
Valtteri Karesto 48a86e3386 Fix problems with middleware 2022-03-16 18:59:48 +02:00
Valtteri Karesto c82c35b5a1 Use new env variables 2022-03-16 18:59:37 +02:00
Valtteri Karesto f849be1f80 export variables 2022-03-16 18:59:21 +02:00
Valtteri Karesto 694d07fa0e Add react hook form and new env variables 2022-03-16 18:59:11 +02:00
Valtteri Karesto b9aa3e2adc Merge branch 'main' of github.com:eqlabs/xrpl-hooks-ide into feat/hooks-v2-preparations 2022-03-14 14:37:12 +02:00
Valtteri Karesto 5b573b2379 Merge pull request #129 from eqlabs/fix/sample-list-logic
Fix modal showing up bug, issue #99
2022-03-14 14:36:57 +02:00
Valtteri Karesto 23538b1502 Fix navigation 2022-03-11 16:08:27 +02:00
Valtteri Karesto 723602ebdc Make some v2 hooks api preparations 2022-03-11 16:08:16 +02:00
Valtteri Karesto f8fdeaf9ce Update dependencies 2022-03-11 16:07:01 +02:00
Vaclav Barta e75b971718 Merge pull request #133 from eqlabs/update-hook-doc
closes #131
2022-03-11 08:03:19 +01:00
Vaclav Barta 11a35a5932 added link 2022-03-10 10:48:56 +01:00
Vaclav Barta 611f875761 upgraded links to https://xrpl-hooks.readme.io/v2.0/, added more 2022-03-10 10:15:20 +01:00
Vaclav Barta a7df50c194 updated docs 2022-03-09 16:41:28 +01:00
Valtteri Karesto 0c6c60ed29 Removed unused variable 2022-03-09 16:26:10 +02:00
Vaclav Barta e82662647f started updating docs 2022-03-09 15:25:42 +01:00
Valtteri Karesto 5490e7205a Improved the filename validation 2022-03-09 16:24:23 +02:00
Valtteri Karesto d8e218392a Merge pull request #132 from eqlabs/feat/add-docs-about-hover-messages
Improve readme
2022-03-09 16:23:47 +02:00
Valtteri Karesto 723722df58 Merge pull request #123 from eqlabs/fix/increment-test-sequence-number
Increment sequence on every transaction
2022-03-09 15:21:53 +02:00
Valtteri Karesto 2ff85ede06 Improve readme 2022-03-09 15:19:59 +02:00
Valtteri Karesto 052a1e5b60 Prevent special characters on filename 2022-03-09 15:01:46 +02:00
Valtteri Karesto 7f8f47cb14 Fix modal showing up bug, issue #99 2022-03-09 13:27:42 +02:00
Valtteri Karesto ddb043c104 Merge pull request #128 from eqlabs/feat/fix-account-button
Fixes issue #101
2022-03-09 12:50:50 +02:00
Valtteri Karesto d2ad6537d7 Remove console logs 2022-03-09 12:48:53 +02:00
Valtteri Karesto 8f004ee4da Fixes issue #101 2022-03-09 12:29:20 +02:00
Valtteri Karesto b90bf67c20 Merge pull request #120 from eqlabs/feat/add-hooks-docs
Feat/add hooks docs
2022-03-09 12:06:06 +02:00
Valtteri Karesto 746112e637 fixes issue #115 2022-03-09 11:57:06 +02:00
Valtteri Karesto 13bfd42093 Decorations should now work correctly 2022-03-09 11:14:43 +02:00
Valtteri Karesto c1f7d7d51c Make sure not all files are defined as C files 2022-03-09 11:14:14 +02:00
muzamil 38a097a8f9 Merge pull request #108 from eqlabs/feat/debug-prettify
Debug stream improvements.
2022-03-08 21:07:13 +05:30
muzam1l db0ffe999e fix line wrap and timestamp font 2022-03-08 19:18:51 +05:30
Jani Anttonen 6d88c4e546 Merge pull request #100 from eqlabs/feat/persist-splits
Persist splits
2022-03-08 14:15:49 +02:00
Valtteri Karesto 09f58f18ae Increment sequence on every transaction 2022-03-08 13:16:45 +02:00
Valtteri Karesto e11ddaffb0 Remove html from md now that the paragraph styling works 2022-03-08 11:36:43 +02:00
Valtteri Karesto 2e88f568b8 Add imports 2022-03-08 11:24:14 +02:00
Valtteri Karesto 237d504f17 Add better styling for markdown 2022-03-08 11:24:02 +02:00
Valtteri Karesto 197fc09e1d delete unused files 2022-03-08 11:23:51 +02:00
Valtteri Karesto 2c74a93aee Add better logic for markdown files 2022-03-08 11:22:58 +02:00
Valtteri Karesto 5209644780 Add raw-loader 2022-03-08 11:22:39 +02:00
Valtteri Karesto ed37427da8 Add new markdown files 2022-03-08 09:56:07 +02:00
Valtteri Karesto daee9de96c Update md script and readme 2022-03-08 09:55:40 +02:00
Valtteri Karesto e4b10d12c2 Remove unused script 2022-03-08 09:54:47 +02:00
Valtteri Karesto 64eabb4502 Remove unused dependencies 2022-03-08 09:54:21 +02:00
Valtteri Karesto 12a24d3d86 Remove rst files 2022-03-08 09:54:11 +02:00
muzam1l ce91182c7b fix and segrregate debug stream state. 2022-03-07 17:14:53 +05:30
muzam1l 2e3a0e557e Some vertical margin in enhanced logs 2022-03-04 20:37:07 +05:30
Valtteri Karesto 395e02343b Add logic to enrich the hover messages with rst file contents 2022-03-04 12:48:03 +02:00
Valtteri Karesto 3682dd4946 Add readme how to use the script 2022-03-04 12:38:39 +02:00
Valtteri Karesto 3070ed706e Add script that converts rst to json which contains md 2022-03-04 12:38:24 +02:00
Valtteri Karesto 4b73687779 Add lodash.uniqby 2022-03-04 12:37:51 +02:00
muzam1l 6b9a9ef978 better socket error mesage 2022-03-04 15:36:48 +05:30
muzam1l bc5bb5be39 Fix timesatmp in logs 2022-03-04 15:22:05 +05:30
muzam1l 0fe83811b9 Clickable accounts in logs and formatting fixes 2022-03-04 15:13:56 +05:30
muzam1l c6359aa853 Add error code to close event message 2022-03-04 14:19:23 +05:30
muzam1l c9c818c8f3 json data is now collapsible! 2022-03-03 21:03:28 +05:30
muzam1l c521246393 debug stream state as full global 2022-03-03 16:36:42 +05:30
muzam1l 8936b34361 separate messages for debug stream error and close evensts 2022-03-03 16:16:00 +05:30
muzam1l 5993d2762f Separately format time, json and message of debug stream log. 2022-03-01 21:36:24 +05:30
muzamil 0a44b5b5d1 Merge pull request #94 from eqlabs/feat/wasm-stats
Compiled wasm file stats
2022-03-01 15:14:14 +05:30
JaniAnttonen 810d3b2524 Add a todo 2022-02-25 16:34:09 +02:00
JaniAnttonen a3393ded1e Fix build 2022-02-25 15:22:09 +02:00
JaniAnttonen 17ede265b1 Remove debug logging 2022-02-25 14:39:06 +02:00
JaniAnttonen 629070edad Save split state 2022-02-25 13:50:56 +02:00
Vaclav Barta cc83924c27 Merge pull request #98 from eqlabs/bugfix/typos
fix for #97
2022-02-15 08:49:38 +01:00
Vaclav Barta e3e964f72a fix for #97 2022-02-11 13:54:52 +01:00
muzam 0def1d30a6 compiled wasm file stats 2022-02-09 19:06:29 +05:30
Joni Juup bdb2c0cf8f Merge pull request #93 from eqlabs/bugfix/monaco-popups
Fix monaco popup issues
2022-02-09 15:10:28 +02:00
muzamil 3e8dbc9793 Merge pull request #91 from eqlabs/fixes
Clearing some issues
2022-02-09 18:32:47 +05:30
muzam d735cd3833 Merge branch 'main' into fixes 2022-02-09 18:31:35 +05:30
Joni Juup eddf228283 more specific rule for the box itself 2022-02-09 13:14:17 +02:00
Joni Juup f9d617efdc more specific css rule for hr 2022-02-09 13:09:36 +02:00
Joni Juup 43796021da fix monaco editor issues with popups 2022-02-09 13:04:41 +02:00
Joni Juup 241c21782d Merge pull request #89 from eqlabs/enhancement/small-ui-fixes
new XRPL Hooks logo
clicking on the logo won't open the template dialog (retain gist path)
clicking gist under the Hook name opens the gist in a new window
documentation button open documentation in a new window
added meta tags & favicons
adjusted color schemes, removed pink (now matches design)
2022-02-09 12:42:18 +02:00
muzam 1a3f5d144c change accept template to starter 2022-02-09 14:26:19 +05:30
Joni Juup 66fb68d52e update share image 2022-02-08 18:58:59 +02:00
Joni Juup aaeb32a576 update favicons 2022-02-08 18:29:12 +02:00
Joni Juup 78b5dcceb6 adjusting colors and themes 2022-02-08 18:07:37 +02:00
muzam ce81c11c29 Update hooks installed info in card 2022-02-08 19:56:30 +05:30
muzam 3a98b95e3d fix firewall template link 2022-02-08 18:59:40 +05:30
muzam 8bc36655e2 disallow illegal characters in filename 2022-02-08 15:26:22 +05:30
muzam b6ab536a60 Clear log on compile 2022-02-08 15:08:38 +05:30
Joni Juup 37a3d2b207 add share image 2022-02-07 17:10:00 +02:00
Joni Juup cd0c5f8a0d meta tag changes 2022-02-07 16:54:31 +02:00
Vaclav Barta 8dde89fa9a Merge pull request #88 from eqlabs/feature/optimization
remove hardcoded file compilation options
2022-02-07 15:35:33 +01:00
Joni Juup ca3d60cfb8 small ui fixes 2022-02-07 16:23:39 +02:00
Vaclav Barta 1a4d53cfbc removed hardcoded file compilation options 2022-02-07 15:03:47 +01:00
Joni Juup 94e126782b Merge pull request #65 from eqlabs/feature/panel-resize
Added panel resizing to all views.
2022-02-02 15:41:07 +02:00
Joni Juup cc03c64f0a added a fixed height for logbox header to logbox content box height can be calculated easily 2022-02-02 15:03:31 +02:00
Joni Juup 3647aa6274 adjusted gutter sizes and highlight style 2022-02-02 12:39:08 +02:00
Joni Juup a2a58f0ba9 light mode support 2022-02-02 12:15:58 +02:00
Joni Juup c544a03be4 fixed log overflow, resize sizing 2022-02-02 12:12:07 +02:00
Joni Juup 9a09da88ec add panel resizing to views 2022-02-01 16:44:51 +02:00
Joni Juup 5850551906 fixed merge conflicts 2022-02-01 15:47:24 +02:00
muzam e35e520d24 minor fix 2022-02-01 19:07:29 +05:30
muzamil 8077fc5865 Merge pull request #66 from eqlabs/feat/tabs
Transaction tabs
2022-02-01 19:02:26 +05:30
muzam bff01b4a9f Merge branch 'main' into feat/tabs 2022-02-01 19:00:46 +05:30
muzamil de5380d6f3 Merge pull request #67 from eqlabs/feat/debug-stream
Per account debug stream
2022-02-01 18:51:20 +05:30
muzamil eda2a9596a Merge pull request #52 from eqlabs/transactions
Implemented Transactions!
2022-02-01 18:49:33 +05:30
muzam 195d33b1db Merge branch 'test-page' into transactions 2022-02-01 18:46:10 +05:30
muzam 4f042ef3b7 File prefixed logs 2022-02-01 18:42:19 +05:30
muzam 17c67822a9 First draft of debug stream 2022-02-01 17:05:53 +05:30
muzam e6f613ae0b Fix tabs overflow 2022-02-01 13:53:18 +05:30
muzam 9b822cfda4 Resuable tabs component and transaction tabs 2022-01-31 20:27:49 +05:30
muzam b5b918d877 minor changes 2022-01-31 18:55:15 +05:30
Vaclav Barta 739918647d fixed tail match 2022-01-20 10:37:24 +01:00
Vaclav Barta 1f334d6253 proposed fix for #59 2022-01-20 10:18:44 +01:00
muzam 0f15a85c45 Added additional tx type 2022-01-18 14:41:11 +05:30
muzam 0c4330e329 Support json fields and better error handling 2022-01-12 14:51:02 +05:30
muzam a9676288ea implement reset transaction state 2022-01-11 20:20:39 +05:30
muzam 7354474c70 Implemented transactions ❤️‍🔥 2022-01-11 20:16:44 +05:30
muzam ce5b307a8b Implement send transaction, payment works, yaay. 2022-01-10 15:21:59 +05:30
muzam b28bcfdd0a Merge branch 'main' into test-page 2022-01-05 16:32:30 +05:30
muzam 7f06876e3e Test page UI layout 2022-01-05 16:32:07 +05:30
muzamil fd479d8671 Merge pull request #41 from eqlabs/feat/templates
Fetch templates and header files according to selection.
2022-01-04 15:32:10 +05:30
Valtteri Karesto 938b567256 Merge pull request #46 from eqlabs/feat/patch-ripple-binary-codec
Feat/patch ripple binary codec
2021-12-23 09:08:38 +02:00
Valtteri Karesto 779f5aab0a Fixed typos 2021-12-22 16:14:35 +02:00
Valtteri Karesto 02194d8a98 Remove logs 2021-12-22 16:08:02 +02:00
Valtteri Karesto 5677fe34dc Add comments to state 2021-12-22 16:07:45 +02:00
Valtteri Karesto 895da89325 Add new version of ripple-binary-codec patch 2021-12-22 16:07:21 +02:00
Valtteri Karesto b138cc8d5b Update readme 2021-12-22 16:06:52 +02:00
muzam 027b2c8ed4 remove console log 2021-12-21 17:07:45 +05:30
Valtteri Karesto d85cc71817 Merge pull request #43 from eqlabs/feat/temporary-fix-for-editor
Roll back file paths for now
2021-12-21 11:18:58 +02:00
Valtteri Karesto bac3522078 Roll back file paths for now 2021-12-21 11:07:52 +02:00
Vaclav Barta b2c6aa7871 Merge pull request #42 from eqlabs/feature/workspace-location
fix for issue #39
2021-12-20 13:38:46 +01:00
Vaclav Barta 81e2a3673d fix for issue #39 2021-12-20 13:02:42 +01:00
muzam b4ca360661 Implement read-only editors for some headers. 2021-12-16 22:30:03 +05:30
muzam ad947be0bc Only fetch extra headers on template files. 2021-12-16 18:52:16 +05:30
muzam f739d4da34 Fetch templates and header files according to selection. 2021-12-16 18:26:58 +05:30
Valtteri Karesto fdb1eb01a4 Merge pull request #34 from eqlabs/feat/zip
Implemented download as zip.
2021-12-15 15:02:23 +02:00
Valtteri Karesto 920d359966 Merge pull request #38 from eqlabs/feat/controlled-dialog
Filename Dialog fixes and improvements.
2021-12-15 13:16:49 +02:00
muzam 9e1dbc8765 minor fixes 2021-12-15 15:49:55 +05:30
muzam 10ea77fd8d Error and loading states in download as zip. 2021-12-15 15:38:19 +05:30
muzam 50de7ebf15 Merge branch 'feat/controlled-dialog' into feat/zip 2021-12-14 22:19:05 +05:30
muzam 7db07e3f92 Merge branch 'main' into feat/controlled-dialog 2021-12-14 21:29:31 +05:30
Valtteri Karesto 6ad7c67672 hotfix: fixes faucet url 2021-12-14 16:36:37 +02:00
Valtteri Karesto 10f279a6b4 Merge pull request #33 from eqlabs/feat/add-language-client
Add example of language server
2021-12-14 16:17:16 +02:00
muzam 792c093cfd Input filename validation and default extension. 2021-12-14 16:43:43 +05:30
muzam a11a641608 New file dialog confirms on pressing Enter. 2021-12-14 15:33:39 +05:30
Valtteri Karesto c3bf31d993 Fetch accounts only on client side 2021-12-13 23:06:13 +02:00
Valtteri Karesto 67d1b72331 fix eslintrc.json 2021-12-13 23:04:11 +02:00
Valtteri Karesto 35bc89cf99 Few cleanups to code 2021-12-13 23:02:03 +02:00
Valtteri Karesto 380e196db2 Added quick comments about code 2021-12-13 22:54:57 +02:00
Valtteri Karesto d67613c0cf Add disabled state to button if no compiled code 2021-12-13 22:27:21 +02:00
Valtteri Karesto 4d4b96bede Extract actions to separate files 2021-12-13 22:23:37 +02:00
Valtteri Karesto 59637e32fe Add a lot of functionality to state 2021-12-13 17:26:37 +02:00
Valtteri Karesto 82d0c8c5ff Update navigation logic 2021-12-13 17:26:25 +02:00
Valtteri Karesto b41ee2198b Separate editors for deploy and develop 2021-12-13 17:26:13 +02:00
Valtteri Karesto 09c5aff1da Add deploy footer 2021-12-13 17:25:58 +02:00
Valtteri Karesto d806a46f13 Add account component 2021-12-13 17:25:49 +02:00
Valtteri Karesto 6bcbb5d6df Add patterns 2021-12-13 17:23:45 +02:00
Valtteri Karesto 0d7a4aae10 Remove footer 2021-12-13 17:23:36 +02:00
Valtteri Karesto 276dfff2ba Add link and log components 2021-12-13 17:23:09 +02:00
Valtteri Karesto eddb870f85 Update deploy and develope pages 2021-12-13 17:22:51 +02:00
Valtteri Karesto 3707a215bb Add custom faucet api page to prevent cors problems 2021-12-13 17:22:36 +02:00
Valtteri Karesto fad5e13430 Do not show modal on sign-in page 2021-12-13 17:22:18 +02:00
Valtteri Karesto df47158f29 Some fixes to button 2021-12-13 17:21:59 +02:00
Valtteri Karesto 51e4fed345 Forgot to add yarn lock 2021-12-13 17:21:47 +02:00
Valtteri Karesto e471e8d7ef Add colors to theme 2021-12-13 17:21:40 +02:00
Valtteri Karesto adc268c3cd Added some env info to readme 2021-12-13 17:21:28 +02:00
Valtteri Karesto 69e08abbc9 Add postinstall patch script 2021-12-13 17:21:09 +02:00
Valtteri Karesto b8596ec7ce Add some lint settings 2021-12-13 17:20:35 +02:00
Valtteri Karesto 4fc7098e78 Fix styles 2021-12-13 17:16:43 +02:00
Valtteri Karesto 69c7865491 Added some helper utils 2021-12-13 17:16:34 +02:00
muzam 8ac7e82221 Implemented download as zip 2021-12-13 16:21:28 +05:30
Valtteri Karesto 5eea51744e Monkeypatch ripple-binary-code to support SetHook 2021-12-07 15:31:10 +02:00
Valtteri Karesto dcf0598852 Update file uris 2021-11-29 10:58:04 +02:00
Valtteri Karesto a7d04a28e4 Add example of language server 2021-11-27 00:06:26 +02:00
Valtteri Karesto a0303ecfa4 Merge pull request #32 from eqlabs/feat/fix-scrollbars
Fix scrollbars
2021-11-26 15:23:13 +02:00
Valtteri Karesto 5a79e07c2d Fix scrollbars 2021-11-26 15:20:30 +02:00
Valtteri Karesto 1107bb8196 Merge pull request #31 from eqlabs/feat/add-gist-integration
Feat/add gist integration
2021-11-26 00:11:24 +02:00
Valtteri Karesto 405aafed7e Removed unused code 2021-11-26 00:02:39 +02:00
Valtteri Karesto 03156474f3 Move build button and add loading states 2021-11-25 23:55:45 +02:00
Valtteri Karesto 7982209732 Move deploy button to footer 2021-11-25 23:55:21 +02:00
Valtteri Karesto 0f9963b972 Refactor global navigation and editor navigation 2021-11-25 23:55:12 +02:00
Valtteri Karesto 650243279a Add sync to gist issue #19 feature and other fixes to global state 2021-11-25 23:54:47 +02:00
Valtteri Karesto 6f183049d5 Add some safeguards to data and new button size 2021-11-25 23:54:03 +02:00
Valtteri Karesto 48706effc1 Style tweaks to button component styles 2021-11-25 23:32:09 +02:00
Valtteri Karesto c9740b1e8a Minor tweaks to dropdown menu 2021-11-25 23:31:49 +02:00
Valtteri Karesto 166300b8d5 Custom alert dialog page 2021-11-25 23:31:33 +02:00
Valtteri Karesto 99968855e0 Add alert dialog from radix 2021-11-25 23:31:24 +02:00
Valtteri Karesto a62a9c3700 Separate login page so we maintain the app state, related to issue #17 2021-11-25 23:31:10 +02:00
Valtteri Karesto 4e971ce119 Conditional chaining to map 2021-11-25 22:02:02 +02:00
Valtteri Karesto 72ba2072ec Uppercase variant for Heading 2021-11-25 22:01:27 +02:00
Valtteri Karesto bd8d3c39c2 New size for input component 2021-11-25 22:01:13 +02:00
Valtteri Karesto 7142f5b5e2 Added buttongroup component 2021-11-25 22:00:56 +02:00
Valtteri Karesto 37516c602d Add some dependencies and theme style change 2021-11-25 17:30:11 +02:00
Valtteri Karesto cfa7a3bd30 update dialog styles 2021-11-25 17:29:52 +02:00
Valtteri Karesto 266e4c3e6c Create panel box component 2021-11-25 17:29:40 +02:00
Valtteri Karesto 4cf6d376f0 Update themechanger styles 2021-11-25 17:29:31 +02:00
Valtteri Karesto b2f49625db Add truncate string util 2021-11-25 17:29:20 +02:00
Valtteri Karesto 0b9fd172ce Add patterns to modal 2021-11-25 17:29:08 +02:00
Valtteri Karesto 2582981d85 Open modal if no gist id or files 2021-11-25 17:28:41 +02:00
Valtteri Karesto a0eda59982 Added some tsconfigs 2021-11-25 17:28:26 +02:00
Valtteri Karesto 9ae9db984d Renamed pages 2021-11-25 17:28:15 +02:00
Valtteri Karesto 8f2c78b08b Merge pull request #30 from eqlabs/feat/button-loading-state
Feat/button loading state
2021-11-20 01:37:26 +02:00
193 changed files with 18428 additions and 3104 deletions

No files matched your search

+10 -1
View File
@@ -1,3 +1,12 @@
NEXTAUTH_URL=https://example.com
NEXTAUTH_SECRET="1234"
GITHUB_SECRET=""
GITHUB_ID=""
GITHUB_ID=""
NEXT_PUBLIC_COMPILE_API_ENDPOINT="http://localhost:9000/api/build"
NEXT_PUBLIC_COMPILE_API_BASE_URL="http://localhost:9000"
NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT="ws://localhost:9000/language-server/c"
NEXT_PUBLIC_TESTNET_URL="hooks-testnet-v3.xrpl-labs.com"
NEXT_PUBLIC_DEBUG_STREAM_URL="hooks-testnet-v3-debugstream.xrpl-labs.com"
NEXT_PUBLIC_EXPLORER_URL="hooks-testnet-v3-explorer.xrpl-labs.com"
NEXT_PUBLIC_NETWORK_ID="21338"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
+5
View File
@@ -32,3 +32,8 @@ yarn-error.log*
# vercel
.vercel
.vscode
# yarn
.yarnrc.yml
.yarn/
+38
View File
@@ -0,0 +1,38 @@
See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
.vscode
*.md
utils/libwabt.js
+8
View File
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"arrowParens": "avoid",
"semi": false,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none"
}
+79 -7
View File
@@ -1,6 +1,8 @@
# XRPL Hooks IDE
# XRPL Hooks Builder
This is the repository for XRPL Hooks IDE. This project is built with Next.JS
https://hooks-builder.xrpl.org/
This is the repository for XRPL Hooks Builder. This project is built with Next.JS
## General
@@ -8,7 +10,9 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
## Getting Started
First, run the development server:
First, copy the `.env.example` to `.env.local` file, someone from the team can provide you your enviroment variables.
Then, run the development server:
```bash
npm run dev
@@ -24,6 +28,78 @@ You can start editing the page by modifying `pages/index.tsx`. The page auto-upd
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Github Login
If you want to use your Github app to provide login, here's the guide to do that.
- First go to https://github.com/settings/profile -> Developer Settings -> OAuth Apps
- Click "New OAuth App" button
- Give application some name eg. Xrpl Hooks Development
- Give some homepage url eg. localhost:3000
- Give some optional description (these values will show up on the popup when you login)
- Authorization callback URL should be http://localhost:3000/api/auth/callback (if you're creating the app for local development)
- Click register application
- Then a page should open up where you can get client id and client secret values. Copy paste those to .env.local to use them:
```
GITHUB_SECRET="client-secret-here"
GITHUB_ID="client-id-here"
```
Login should now work through your own Github OAuth app.
## Styling and Theming
This project uses Stitches (https://stitches.dev) for theming and styling the components. You should be quite familiar with the API if you have used for example styled-components earlier. Stitches should provide better performance, near zero runtime.
For components we try to use Radix-UI (https://www.radix-ui.com/) as much as possible. It may not provide all the necessary components so you're free to use other components/libraries if those makes sense. For colors we're using Radix-UI Colors (https://radix-ui.com/colors).
Theme file can be found under `./stitches.config.ts` file. When you're creating new components remeber to import `styled` from that file and not `@stitches` directly. That way it will provide the correct theme for you automatically.
Example:
```tsx
// Use our stitches.config instead of @stitches/react
import { styled } from "../stitches.config";
const Box = styled("div", {
boxSizing: "border-box",
});
export default Box;
```
Custom components can be found from `./components` folder.
## Monaco Editor
Project is relying on Monaco editor heavily. Instead of using Monaco editor directly we're using `@monaco-editor/react` which provides little helpers to use Monaco editor.
On the Develop page we're using following loader for Monaco editor:
```js
loader.config({
paths: {
vs: "https://cdn.jsdelivr.net/npm/[email protected]/min/vs",
},
});
```
By default `@monaco-editor/react` was using 0.29.? version of Monaco editor. @codingame/monaco-languageclient library (connects to clangd language server) was using API methods that were introduced in Monaco Editor 0.30 so that's why we're loading certain version of it.
Monaco Languageclient related stuff is found from `./utils/languageClient.ts`. Basically we're connecting the editor to clangd language server which lives on separate backend. That project can be found from https://github.com/eqlabs/xrpl-hooks-compiler/. If you need access to that project ask permissions from @vbar (Vaclav Barta) on GitHub.
### Language server hover messages
If you want to extend hover messages provided by language-server you can add extra docs to `xrpl-hooks-docs/md/` folder. Just make sure the filename is matching with the error code that comes from language server. So lets say you want to add extra documentation for `hooks-func-addr-taken` check create new file called `hooks-func-addr-taken.md` and then remember to import and export it on `docs.ts` file with same logic as the other files.
## Global state management
Global state management is handled with library called Valtio (https://github.com/pmndrs/valtio). Initial state can be found from `./state/index.ts` file. All the actions which updates the state is found under `./state/actions/` folder.
## Special notes
Since we are dealing with greenfield tech and one of the dependencies (ripple-binary-codec) doesn't yet support signing `SetHook` transactions we had to monkey patch the library with patch-package (https://www.npmjs.com/package/patch-package). We modified the definitions.json file of the ripple-binary-codec library and then ran `yarn patch-package ripple-binary-codec` which created `patches/ripple-binary-codec+1.2.0.patch` file to this project. This file contains the modifications to `ripple-binary-codec` library. package.json contains postinstall hook which runs patch-package, and it will add the patch on the file mentioned earlier. This happens automatically after running patch package.
## Learn More
To learn more about Next.js, take a look at the following resources:
@@ -33,8 +109,4 @@ To learn more about Next.js, take a look at the following resources:
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+589
View File
@@ -0,0 +1,589 @@
import toast from 'react-hot-toast'
import { useSnapshot } from 'valtio'
import { ArrowSquareOut, Copy, Trash, Wallet, X } from 'phosphor-react'
import React, { useEffect, useState, FC } from 'react'
import Dinero from 'dinero.js'
import Button from './Button'
import { addFaucetAccount, importAccount } from '../state/actions'
import state from '../state'
import Box from './Box'
import { Container, Heading, Stack, Text, Flex } from '.'
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
DialogTrigger
} from './Dialog'
import { css } from '../stitches.config'
import { Input, Label } from './Input'
import truncate from '../utils/truncate'
const labelStyle = css({
color: '$mauve10',
textTransform: 'uppercase',
fontSize: '10px',
mb: '$0.5'
})
import transactionsData from '../content/transactions.json'
import { SetHookDialog } from './SetHookDialog'
import { addFunds } from '../state/actions/addFaucetAccount'
import { deleteHook } from '../state/actions/deployHook'
import { capitalize } from '../utils/helpers'
import { deleteAccount } from '../state/actions/deleteAccount'
import { xrplSend } from '../state/actions/xrpl-client'
export const AccountDialog = ({
activeAccountAddress,
setActiveAccountAddress
}: {
activeAccountAddress: string | null
setActiveAccountAddress: React.Dispatch<React.SetStateAction<string | null>>
}) => {
const snap = useSnapshot(state)
const [showSecret, setShowSecret] = useState(false)
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress)
return (
<Dialog
open={Boolean(activeAccountAddress)}
onOpenChange={open => {
setShowSecret(false)
!open && setActiveAccountAddress(null)
}}
>
<DialogContent
css={{
backgroundColor: '$mauve1 !important',
border: '1px solid $mauve2',
'.dark &': {
// backgroundColor: "$black !important",
},
p: '$3',
'&:before': {
content: ' ',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
opacity: 0.2,
'.dark &': {
opacity: 1
},
zIndex: 0,
pointerEvents: 'none',
backgroundImage: `url('/pattern-dark.svg'), url('/pattern-dark-2.svg')`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom left, top right'
}
}}
>
<DialogTitle
css={{
display: 'flex',
width: '100%',
alignItems: 'center',
borderBottom: '1px solid $mauve6',
pb: '$3',
gap: '$3',
fontSize: '$md'
}}
>
<Wallet size="15px" /> {activeAccount?.name}
<DialogClose asChild>
<Button
size="xs"
outline
css={{ ml: 'auto', mr: '$9' }}
tabIndex={-1}
onClick={() => {
deleteAccount(activeAccount?.address)
}}
>
Delete Account <Trash size="15px" />
</Button>
</DialogClose>
</DialogTitle>
<DialogDescription as="div" css={{ fontFamily: '$monospace' }}>
<Stack css={{ display: 'flex', flexDirection: 'column', gap: '$3' }}>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Account Address</Text>
<Text
css={{
fontFamily: '$monospace',
a: { '&:hover': { textDecoration: 'underline' } }
}}
>
<a
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${activeAccount?.address}`}
target="_blank"
rel="noopener noreferrer"
>
{activeAccount?.address}
</a>
</Text>
</Flex>
<Flex css={{ marginLeft: 'auto', color: '$mauve12' }}>
<Button
size="sm"
ghost
css={{ mt: '$3' }}
onClick={() => {
navigator.clipboard.writeText(activeAccount?.address || '')
toast.success('Copied address to clipboard')
}}
>
<Copy size="15px" />
</Button>
</Flex>
</Flex>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Secret</Text>
<Text
as="div"
css={{
fontFamily: '$monospace',
display: 'flex',
alignItems: 'center'
}}
>
{showSecret
? activeAccount?.secret
: '•'.repeat(activeAccount?.secret.length || 16)}{' '}
<Button
css={{
fontFamily: '$monospace',
lineHeight: 2,
mt: '2px',
ml: '$3'
}}
ghost
size="xs"
onClick={() => setShowSecret(curr => !curr)}
>
{showSecret ? 'Hide' : 'Show'}
</Button>
</Text>
</Flex>
<Flex css={{ marginLeft: 'auto', color: '$mauve12' }}>
<Button
size="sm"
ghost
onClick={() => {
navigator.clipboard.writeText(activeAccount?.secret || '')
toast.success('Copied secret to clipboard')
}}
css={{ mt: '$3' }}
>
<Copy size="15px" />
</Button>
</Flex>
</Flex>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Balances & Objects</Text>
<Text
css={{
fontFamily: '$monospace',
display: 'flex',
alignItems: 'center'
}}
>
{Dinero({
amount: Number(activeAccount?.xrp || '0'),
precision: 6
})
.toUnit()
.toLocaleString(undefined, {
style: 'currency',
currency: 'XRP',
currencyDisplay: 'name'
})}
<Button
css={{
fontFamily: '$monospace',
lineHeight: 2,
mt: '2px',
ml: '$3'
}}
ghost
size="xs"
onClick={() => {
addFunds(activeAccount?.address || '')
}}
>
Add Funds
</Button>
</Text>
</Flex>
<Flex
css={{
marginLeft: 'auto'
}}
>
<a
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${activeAccount?.address}`}
target="_blank"
rel="noreferrer noopener"
>
<Button size="sm" ghost css={{ color: '$grass11 !important', mt: '$3' }}>
<ArrowSquareOut size="15px" />
</Button>
</a>
</Flex>
</Flex>
<Flex css={{ alignItems: 'center' }}>
<Flex css={{ flexDirection: 'column' }}>
<Text className={labelStyle()}>Installed Hooks</Text>
<Text
css={{
fontFamily: '$monospace',
a: { '&:hover': { textDecoration: 'underline' } }
}}
>
{activeAccount && activeAccount.hooks.length > 0
? activeAccount.hooks.map(i => {
return (
<a
key={i}
href={`https://${process.env.NEXT_PUBLIC_EXPLORER_URL}/${i}`}
target="_blank"
rel="noopener noreferrer"
>
{truncate(i, 12)}
</a>
)
})
: '–'}
</Text>
</Flex>
{activeAccount && activeAccount?.hooks?.length > 0 && (
<Flex css={{ marginLeft: 'auto' }}>
<Button
size="xs"
outline
disabled={activeAccount.isLoading}
css={{ mt: '$3', mr: '$1', ml: 'auto' }}
onClick={() => {
deleteHook(activeAccount)
}}
>
Delete Hook <Trash size="15px" />
</Button>
</Flex>
)}
</Flex>
</Stack>
</DialogDescription>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
)
}
interface AccountProps {
card?: boolean
hideDeployBtn?: boolean
showHookStats?: boolean
}
const Accounts: FC<AccountProps> = props => {
const snap = useSnapshot(state)
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null)
useEffect(() => {
const fetchAccInfo = async () => {
if (snap.clientStatus === 'online') {
const requests = snap.accounts.map(acc =>
xrplSend({
id: `hooks-builder-req-info-${acc.address}`,
command: 'account_info',
account: acc.address
})
)
const responses = await Promise.all(requests)
responses.forEach((res: any) => {
const address = res?.account_data?.Account as string
const balance = res?.account_data?.Balance as string
const sequence = res?.account_data?.Sequence as number
const accountToUpdate = state.accounts.find(acc => acc.address === address)
if (accountToUpdate) {
accountToUpdate.xrp = balance
accountToUpdate.sequence = sequence
accountToUpdate.error = null
} else {
const oldAccount = state.accounts.find(acc => acc.address === res?.account)
if (oldAccount) {
oldAccount.xrp = '0'
oldAccount.error = {
code: res?.error,
message: res?.error_message
}
}
}
})
const objectRequests = snap.accounts.map(acc => {
return xrplSend({
id: `hooks-builder-req-objects-${acc.address}`,
command: 'account_objects',
account: acc.address
})
})
const objectResponses = await Promise.all(objectRequests)
objectResponses.forEach((res: any) => {
const address = res?.account as string
const accountToUpdate = state.accounts.find(acc => acc.address === address)
if (accountToUpdate) {
accountToUpdate.hooks =
res.account_objects
.find((ac: any) => ac?.LedgerEntryType === 'Hook')
?.Hooks?.map((oo: any) => oo.Hook.HookHash) || []
}
})
}
}
let fetchAccountInfoInterval: NodeJS.Timer
if (snap.clientStatus === 'online') {
fetchAccInfo()
fetchAccountInfoInterval = setInterval(() => fetchAccInfo(), 10000)
}
return () => {
if (snap.accounts.length > 0) {
if (fetchAccountInfoInterval) {
clearInterval(fetchAccountInfoInterval)
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [snap.accounts.length, snap.clientStatus])
return (
<Box
as="div"
css={{
display: 'flex',
backgroundColor: props.card ? '$deep' : '$mauve1',
position: 'relative',
flex: '1',
height: '100%',
border: '1px solid $mauve6',
borderRadius: props.card ? '$md' : undefined
}}
>
<Container css={{ p: 0, flexShrink: 1, height: '100%' }}>
<Flex
css={{
py: '$3',
borderBottom: props.card ? '1px solid $mauve6' : undefined
}}
>
<Heading
as="h3"
css={{
fontWeight: 300,
m: 0,
fontSize: '11px',
color: '$mauve12',
px: '$3',
textTransform: 'uppercase',
alignItems: 'center',
display: 'inline-flex',
gap: '$3'
}}
>
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
</Heading>
<Flex css={{ ml: 'auto', gap: '$3', marginRight: '$3' }}>
<ImportAccountDialog type="create" />
<ImportAccountDialog />
</Flex>
</Flex>
<Stack
css={{
flexDirection: 'column',
width: '100%',
fontSize: '13px',
wordWrap: 'break-word',
fontWeight: '$body',
gap: 0,
height: 'calc(100% - 52px)',
flexWrap: 'nowrap',
overflowY: 'auto'
}}
>
{snap.accounts.map(account => (
<Flex
column
key={account.address + account.name}
onClick={() => setActiveAccountAddress(account.address)}
css={{
px: '$3',
py: props.card ? '$3' : '$2',
cursor: 'pointer',
borderBottom: props.card ? '1px solid $mauve6' : undefined,
'@hover': {
'&:hover': {
background: '$backgroundAlt'
}
}
}}
>
<Flex
row
css={{
justifyContent: 'space-between'
}}
>
<Box>
<Text>{account.name} </Text>
<Text
css={{
color: '$textMuted',
wordBreak: 'break-word'
}}
>
{account.address}{' '}
{!account?.error ? (
`(${Dinero({
amount: Number(account?.xrp || '0'),
precision: 6
})
.toUnit()
.toLocaleString(undefined, {
style: 'currency',
currency: 'XRP',
currencyDisplay: 'name'
})})`
) : (
<Box css={{ color: '$red11' }}>
(Account not found, request funds to activate account)
</Box>
)}
</Text>
</Box>
{!props.hideDeployBtn && (
<div
className="hook-deploy-button"
onClick={e => {
e.stopPropagation()
}}
>
<SetHookDialog accountAddress={account.address} />
</div>
)}
</Flex>
{props.showHookStats && (
<Text muted small css={{ mt: '$2' }}>
{account.hooks.length} hook
{account.hooks.length === 1 ? '' : 's'} installed
</Text>
)}
</Flex>
))}
</Stack>
</Container>
<AccountDialog
activeAccountAddress={activeAccountAddress}
setActiveAccountAddress={setActiveAccountAddress}
/>
</Box>
)
}
export const transactionsOptions = transactionsData.map(tx => ({
value: tx.TransactionType,
label: tx.TransactionType
}))
const ImportAccountDialog = ({ type = 'import' }: { type?: 'import' | 'create' }) => {
const [secret, setSecret] = useState('')
const [name, setName] = useState('')
const btnText = type === 'import' ? 'Import' : 'Create'
const title = type === 'import' ? 'Import Account' : 'Create Account'
const handleSubmit = async () => {
if (type === 'create') {
const value = capitalize(name)
await addFaucetAccount(value, true)
setName('')
setSecret('')
return
}
importAccount(secret, name)
setName('')
setSecret('')
}
return (
<Dialog>
<DialogTrigger asChild>
<Button ghost size="sm">
{btnText}
</Button>
</DialogTrigger>
<DialogContent aria-describedby={undefined}>
<DialogTitle css={{ mb: '$4' }}>{title}</DialogTitle>
<Flex column>
<Box css={{ mb: '$2' }}>
<Label>
Account name <Text muted>(optional)</Text>
</Label>
<Input
name="name"
type="text"
autoComplete="off"
autoCapitalize="on"
value={name}
onChange={e => setName(e.target.value)}
/>
</Box>
{type === 'import' && (
<Box>
<Label>Account secret</Label>
<Input
required
name="secret"
type="password"
autoComplete="new-password"
value={secret}
onChange={e => setSecret(e.target.value)}
/>
</Box>
)}
</Flex>
<Flex
css={{
marginTop: 25,
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button type="submit" variant="primary" onClick={handleSubmit}>
{title}
</Button>
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
)
}
export default Accounts
+72
View File
@@ -0,0 +1,72 @@
import { FC, ReactNode } from 'react'
import { proxy, useSnapshot } from 'valtio'
import Button from '../Button'
import Flex from '../Flex'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogTitle
} from './primitive'
export interface AlertState {
isOpen: boolean
title?: string
body?: ReactNode
cancelText?: string
confirmText?: string
confirmPrefix?: ReactNode
onConfirm?: () => any
onCancel?: () => any
}
export const alertState = proxy<AlertState>({
isOpen: false
})
const Alert: FC = () => {
const {
title = 'Are you sure?',
isOpen,
body,
cancelText,
confirmText = 'Ok',
confirmPrefix,
onCancel,
onConfirm
} = useSnapshot(alertState)
return (
<AlertDialog open={isOpen} onOpenChange={value => (alertState.isOpen = value)}>
<AlertDialogContent>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{body}</AlertDialogDescription>
<Flex css={{ justifyContent: 'flex-end', gap: '$3' }}>
{(cancelText || onCancel) && (
<AlertDialogCancel asChild>
<Button css={{ minWidth: '$16' }} outline onClick={onCancel}>
{cancelText || 'Cancel'}
</Button>
</AlertDialogCancel>
)}
<AlertDialogAction asChild>
<Button
css={{ minWidth: '$16' }}
variant="primary"
onClick={async () => {
await onConfirm?.()
alertState.isOpen = false
}}
>
{confirmPrefix}
{confirmText}
</Button>
</AlertDialogAction>
</Flex>
</AlertDialogContent>
</AlertDialog>
)
}
export default Alert
+83
View File
@@ -0,0 +1,83 @@
import React from 'react'
import { blackA } from '@radix-ui/colors'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { styled, keyframes } from '../../stitches.config'
const overlayShow = keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 }
})
const contentShow = keyframes({
'0%': { opacity: 0, transform: 'translate(-50%, -48%) scale(.96)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' }
})
const StyledOverlay = styled(AlertDialogPrimitive.Overlay, {
zIndex: 1000,
backgroundColor: blackA.blackA9,
position: 'fixed',
inset: 0,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
'.dark &': {
backgroundColor: blackA.blackA11
}
})
const Root: React.FC<AlertDialogPrimitive.AlertDialogProps> = ({ children, ...rest }) => {
return (
<AlertDialogPrimitive.Root {...rest}>
<StyledOverlay />
{children}
</AlertDialogPrimitive.Root>
)
}
const StyledContent = styled(AlertDialogPrimitive.Content, {
zIndex: 1000,
backgroundColor: '$mauve2',
color: '$mauve12',
borderRadius: '$md',
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '90vw',
maxWidth: '450px',
maxHeight: '85vh',
padding: 25,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
'&:focus': { outline: 'none' },
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
}
})
const StyledTitle = styled(AlertDialogPrimitive.Title, {
margin: 0,
color: '$mauve12',
fontWeight: 500,
fontSize: '$lg'
})
const StyledDescription = styled(AlertDialogPrimitive.Description, {
marginBottom: 20,
color: '$mauve11',
lineHeight: 1.5,
fontSize: '$md'
})
// Exports
export const AlertDialog = Root
export const AlertDialogTrigger = AlertDialogPrimitive.Trigger
export const AlertDialogContent = StyledContent
export const AlertDialogTitle = StyledTitle
export const AlertDialogDescription = StyledDescription
export const AlertDialogAction = AlertDialogPrimitive.Action
export const AlertDialogCancel = AlertDialogPrimitive.Cancel
+5 -5
View File
@@ -1,8 +1,8 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Box = styled("div", {
const Box = styled('div', {
// all: "unset",
boxSizing: "border-box",
});
boxSizing: 'border-box'
})
export default Box;
export default Box
+222 -139
View File
@@ -1,202 +1,285 @@
import { CSS } from "@stitches/react/types/css-util";
import { StyledComponent } from "@stitches/react/types/styled-component";
import React from "react";
import { styled } from "../stitches.config";
import Flex from "./Flex";
import Spinner from "./Spinner";
import React from 'react'
import { styled } from '../stitches.config'
import Flex from './Flex'
import Spinner from './Spinner'
const Button = styled("button", {
export const StyledButton = styled('button', {
// Reset
all: "unset",
appereance: "none",
fontFamily: "$body",
alignItems: "center",
boxSizing: "border-box",
userSelect: "none",
"&::before": {
boxSizing: "border-box",
all: 'unset',
position: 'relative',
appereance: 'none',
fontFamily: '$body',
alignItems: 'center',
boxSizing: 'border-box',
userSelect: 'none',
'&::before': {
boxSizing: 'border-box'
},
"&::after": {
boxSizing: "border-box",
'&::after': {
boxSizing: 'border-box'
},
// Custom reset?
display: "inline-flex",
display: 'inline-flex',
flexShrink: 0,
justifyContent: "center",
lineHeight: "1",
gap: "5px",
WebkitTapHighlightColor: "rgba(0,0,0,0)",
justifyContent: 'center',
lineHeight: '1',
gap: '5px',
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
// Custom
height: "$6",
px: "$2",
fontSize: "$2",
height: '$6',
px: '$2',
fontSize: '$2',
fontWeight: 500,
fontVariantNumeric: "tabular-nums",
backgroundColor: "red",
cursor: "pointer",
"&:disabled": {
fontVariantNumeric: 'tabular-nums',
cursor: 'pointer',
width: 'max-content',
'&:disabled': {
opacity: 0.6,
pointerEvents: "none",
cursor: "not-allowed",
pointerEvents: 'none',
cursor: 'not-allowed'
},
variants: {
size: {
xs: {
borderRadius: '$sm',
height: '$5',
px: '$2',
fontSize: '$xs'
},
sm: {
borderRadius: "$sm",
height: "$7",
px: "$3",
fontSize: "$xs",
borderRadius: '$sm',
height: '$7',
px: '$3',
fontSize: '$xs'
},
md: {
borderRadius: "$sm",
height: "$8",
px: "$3",
fontSize: "$xs",
borderRadius: '$sm',
height: '$8',
px: '$3',
fontSize: '$xs'
},
lg: {
borderRadius: "$sm",
height: "$10",
px: "$4",
fontSize: "$xs",
},
borderRadius: '$sm',
height: '$10',
px: '$4',
fontSize: '$xs'
}
},
variant: {
link: {
textDecoration: 'underline',
fontSize: 'inherit',
color: '$textMuted',
textUnderlineOffset: '2px'
},
default: {
backgroundColor: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12",
color: "$mauve1",
"@hover": {
"&:hover": {
backgroundColor: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve12",
},
backgroundColor: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve12',
color: '$mauve1',
'@hover': {
'&:hover': {
backgroundColor: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve12'
}
},
"&:active": {
backgroundColor: "$mauve10",
boxShadow: "inset 0 0 0 1px $colors$mauve11",
'&:active': {
backgroundColor: '$mauve10',
boxShadow: 'inset 0 0 0 1px $colors$mauve11'
},
"&:focus": {
boxShadow:
"inset 0 0 0 1px $colors$mauve12, 0 0 0 1px $colors$mauve12",
'&:focus': {
boxShadow: 'inset 0 0 0 1px $colors$mauve12, inset 0 0 0 2px $colors$mauve12'
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$mauve8",
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$mauve8'
}
},
primary: {
backgroundColor: `$pink9`,
boxShadow: "inset 0 0 0 1px $colors$pink9",
color: "$white",
"@hover": {
"&:hover": {
backgroundColor: "$pink10",
boxShadow: "inset 0 0 0 1px $colors$pink11",
},
backgroundColor: `$accent`,
boxShadow: 'inset 0 0 0 1px $colors$purple9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$purple10',
boxShadow: 'inset 0 0 0 1px $colors$purple11'
}
},
"&:active": {
backgroundColor: "$pink8",
boxShadow: "inset 0 0 0 1px $colors$pink8",
'&:active': {
backgroundColor: '$purple8',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
},
"&:focus": {
boxShadow: "inset 0 0 0 1px $colors$pink8",
'&:focus': {
boxShadow: 'inset 0 0 0 2px $colors$purple12'
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: "$mauve4",
boxShadow: "inset 0 0 0 1px $colors$pink8",
},
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
}
},
secondary: {
backgroundColor: `$purple9`,
boxShadow: 'inset 0 0 0 1px $colors$purple9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$purple10',
boxShadow: 'inset 0 0 0 1px $colors$purple11'
}
},
'&:active': {
backgroundColor: '$purple8',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
},
'&:focus': {
boxShadow: 'inset 0 0 0 2px $colors$purple12'
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$purple8'
}
},
destroy: {
backgroundColor: `$red9`,
boxShadow: 'inset 0 0 0 1px $colors$red9',
color: '$white',
'@hover': {
'&:hover': {
backgroundColor: '$red10',
boxShadow: 'inset 0 0 0 1px $colors$red11'
}
},
'&:active': {
backgroundColor: '$red8',
boxShadow: 'inset 0 0 0 1px $colors$red8'
},
'&:focus': {
boxShadow: 'inset 0 0 0 2px $colors$red12'
},
'&[data-radix-popover-trigger][data-state="open"], &[data-radix-dropdown-menu-trigger][data-state="open"]':
{
backgroundColor: '$mauve4',
boxShadow: 'inset 0 0 0 1px $colors$red8'
}
}
},
muted: {
true: {
color: '$textMuted'
}
},
isDisabled: {
true: {
opacity: 0.6,
// pointerEvents: "none",
cursor: 'auto',
'&:hover': {
boxShadow: 'inherit'
}
}
},
outline: {
true: {
backgroundColor: "transparent",
},
backgroundColor: 'transparent'
}
},
uppercase: {
true: {
textTransform: "uppercase",
},
textTransform: 'uppercase'
}
},
fullWidth: {
true: {
width: '100%'
}
},
ghost: {
true: {
boxShadow: "none",
background: "transparent",
color: "$mauve12",
"@hover": {
"&:hover": {
backgroundColor: "$mauve6",
boxShadow: "none",
},
boxShadow: 'none',
background: 'transparent',
color: '$mauve12',
'@hover': {
'&:hover': {
backgroundColor: '$mauve6',
boxShadow: 'none'
}
},
"&:active": {
backgroundColor: "$mauve8",
boxShadow: "none",
'&:active': {
backgroundColor: '$mauve8',
boxShadow: 'none'
},
"&:focus": {
boxShadow: "none",
},
},
'&:focus': {
boxShadow: 'none'
}
}
},
isLoading: {
true: {
"& .button-content": {
visibility: "hidden",
'& .button-content': {
visibility: 'hidden'
},
pointerEvents: "none",
},
},
pointerEvents: 'none'
}
}
},
compoundVariants: [
{
outline: true,
variant: "default",
variant: 'default',
css: {
background: "transparent",
color: "$mauve12",
boxShadow: "inset 0 0 0 1px $colors$mauve10",
"&:hover": {
color: "$mauve12",
background: "$mauve5",
},
},
background: 'transparent',
color: '$mauve12',
boxShadow: 'inset 0 0 0 1px $colors$mauve10',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
},
{
outline: true,
variant: "primary",
variant: 'primary',
css: {
background: "transparent",
color: "$mauve12",
"&:hover": {
color: "$mauve12",
background: "$mauve5",
},
},
background: 'transparent',
color: '$mauve12',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
},
{
outline: true,
variant: 'secondary',
css: {
background: 'transparent',
color: '$mauve12',
'&:hover': {
color: '$mauve12',
background: '$mauve5'
}
}
}
],
defaultVariants: {
size: "md",
variant: "default",
},
});
size: 'md',
variant: 'default'
}
})
const CustomButton: React.FC<
React.ComponentProps<typeof Button> & { as?: string }
> = React.forwardRef(({ children, as = "button", ...rest }, ref) => (
// @ts-expect-error
<Button {...rest} ref={ref} as={as}>
<Flex
as="span"
css={{ gap: "$2", alignItems: "center" }}
className="button-content"
>
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: "absolute" }} />}
</Button>
));
const CustomButton: React.FC<React.ComponentProps<typeof StyledButton> & { as?: string }> =
React.forwardRef(({ children, as = 'button', ...rest }, ref) => (
// @ts-expect-error
<StyledButton {...rest} ref={ref} as={as}>
<Flex as="span" css={{ gap: '$2', alignItems: 'center' }} className="button-content">
{children}
</Flex>
{rest.isLoading && <Spinner css={{ position: 'absolute' }} />}
</StyledButton>
))
CustomButton.displayName = "CustomButton";
CustomButton.displayName = 'CustomButton'
export default CustomButton;
export default CustomButton
+29
View File
@@ -0,0 +1,29 @@
import { styled } from '../stitches.config'
import { StyledButton } from './Button'
const ButtonGroup = styled('div', {
display: 'flex',
marginLeft: '1px',
[`& ${StyledButton}`]: {
marginLeft: '-1px',
px: '$4',
zIndex: 2,
position: 'relative',
'&:hover, &:focus': {
zIndex: 200
}
},
[`& ${StyledButton}:not(:only-of-type):not(:first-child):not(:last-child)`]: {
borderRadius: 0
},
[`& ${StyledButton}:first-child:not(:only-of-type)`]: {
borderBottomRightRadius: 0,
borderTopRightRadius: 0
},
[`& ${StyledButton}:last-child:not(:only-of-type)`]: {
borderBottomLeftRadius: 0,
borderTopLeftRadius: 0
}
})
export default ButtonGroup
+9 -9
View File
@@ -1,12 +1,12 @@
import { styled } from "../stitches.config";
import Box from "./Box";
import { styled } from '../stitches.config'
import Box from './Box'
const Container = styled(Box, {
width: "100%",
marginLeft: "auto",
marginRight: "auto",
px: "$4",
maxWidth: "100%",
});
width: '100%',
marginLeft: 'auto',
marginRight: 'auto',
px: '$4',
maxWidth: '100%'
})
export default Container;
export default Container
+121
View File
@@ -0,0 +1,121 @@
import { CaretRight, Check, Circle } from 'phosphor-react'
import { FC, Fragment, ReactNode } from 'react'
import { Flex, Text } from '../'
import {
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuItemIndicator,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger,
ContextMenuTriggerItem
} from './primitive'
export type TextOption = {
type: 'text'
label: ReactNode
onSelect?: () => any
children?: ContentMenuOption[]
}
export type SeparatorOption = { type: 'separator' }
export type CheckboxOption = {
type: 'checkbox'
label: ReactNode
checked?: boolean
onCheckedChange?: (isChecked: boolean) => any
}
export type RadioOption<T extends string = string> = {
type: 'radio'
label: ReactNode
onValueChange?: (value: string) => any
value: T
options?: { value: T; label?: ReactNode }[]
}
type WithCommons = { key: string; disabled?: boolean }
export type ContentMenuOption = (TextOption | SeparatorOption | CheckboxOption | RadioOption) &
WithCommons
export interface IContextMenu {
options?: ContentMenuOption[]
isNested?: boolean
}
export const ContextMenu: FC<IContextMenu> = ({ children, options, isNested }) => {
return (
<ContextMenuRoot>
{isNested ? (
<ContextMenuTriggerItem>{children}</ContextMenuTriggerItem>
) : (
<ContextMenuTrigger>{children}</ContextMenuTrigger>
)}
{options && !!options.length && (
<ContextMenuContent sideOffset={isNested ? 2 : 5}>
{options.map(({ key, ...option }) => {
if (option.type === 'text') {
const { children, label, onSelect } = option
if (children)
return (
<ContextMenu isNested key={key} options={children}>
<Flex fluid row justify="space-between" align="center">
<Text>{label}</Text>
<CaretRight />
</Flex>
</ContextMenu>
)
return (
<ContextMenuItem key={key} onSelect={onSelect}>
{label}
</ContextMenuItem>
)
}
if (option.type === 'checkbox') {
const { label, checked, onCheckedChange } = option
return (
<ContextMenuCheckboxItem
key={key}
checked={checked}
onCheckedChange={onCheckedChange}
>
<Flex row align="center">
<ContextMenuItemIndicator>
<Check />
</ContextMenuItemIndicator>
<Text css={{ ml: checked ? '$4' : undefined }}>{label}</Text>
</Flex>
</ContextMenuCheckboxItem>
)
}
if (option.type === 'radio') {
const { label, options, onValueChange, value } = option
return (
<Fragment key={key}>
<ContextMenuLabel>{label}</ContextMenuLabel>
<ContextMenuRadioGroup value={value} onValueChange={onValueChange}>
{options?.map(({ value: v, label }) => {
return (
<ContextMenuRadioItem key={v} value={v}>
<ContextMenuItemIndicator>
<Circle weight="fill" />
</ContextMenuItemIndicator>
<Text css={{ ml: '$4' }}>{label}</Text>
</ContextMenuRadioItem>
)
})}
</ContextMenuRadioGroup>
</Fragment>
)
}
return <ContextMenuSeparator key={key} />
})}
</ContextMenuContent>
)}
</ContextMenuRoot>
)
}
export default ContextMenu
+107
View File
@@ -0,0 +1,107 @@
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
import { styled } from '../../stitches.config'
import {
slideDownAndFade,
slideLeftAndFade,
slideRightAndFade,
slideUpAndFade
} from '../../styles/keyframes'
const StyledContent = styled(ContextMenuPrimitive.Content, {
minWidth: 140,
backgroundColor: '$backgroundOverlay',
borderRadius: 6,
overflow: 'hidden',
padding: '5px',
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
'.dark &': {
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)'
}
})
const itemStyles = {
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: '$text',
borderRadius: 3,
display: 'flex',
alignItems: 'center',
height: 28,
padding: '0 7px',
position: 'relative',
paddingLeft: 10,
userSelect: 'none',
'&[data-disabled]': {
color: '$textMuted',
pointerEvents: 'none'
},
'&:focus': {
backgroundColor: '$purple9',
color: '$white'
}
}
const StyledItem = styled(ContextMenuPrimitive.Item, { ...itemStyles })
const StyledCheckboxItem = styled(ContextMenuPrimitive.CheckboxItem, {
...itemStyles
})
const StyledRadioItem = styled(ContextMenuPrimitive.RadioItem, {
...itemStyles
})
const StyledTriggerItem = styled(ContextMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: '$purple9',
color: '$purple9'
},
...itemStyles
})
const StyledLabel = styled(ContextMenuPrimitive.Label, {
paddingLeft: 10,
fontSize: 12,
lineHeight: '25px',
color: '$text'
})
const StyledSeparator = styled(ContextMenuPrimitive.Separator, {
height: 1,
backgroundColor: '$backgroundAlt',
margin: 5
})
const StyledItemIndicator = styled(ContextMenuPrimitive.ItemIndicator, {
position: 'absolute',
left: 0,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
})
export const ContextMenuRoot = ContextMenuPrimitive.Root
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger
export const ContextMenuContent = StyledContent
export const ContextMenuItem = StyledItem
export const ContextMenuCheckboxItem = StyledCheckboxItem
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
export const ContextMenuRadioItem = StyledRadioItem
export const ContextMenuItemIndicator = StyledItemIndicator
export const ContextMenuTriggerItem = StyledTriggerItem
export const ContextMenuLabel = StyledLabel
export const ContextMenuSeparator = StyledSeparator
+182
View File
@@ -0,0 +1,182 @@
import { useEffect } from 'react'
import ReconnectingWebSocket, { CloseEvent } from 'reconnecting-websocket'
import { proxy, ref, useSnapshot } from 'valtio'
import { subscribeKey } from 'valtio/utils'
import { Select } from '.'
import state, { ILog, transactionsState } from '../state'
import { extractJSON } from '../utils/json'
import EnrichLog from './EnrichLog'
import LogBox from './LogBox'
interface ISelect<T = string> {
label: string
value: T
}
export interface IStreamState {
selectedAccount: ISelect | null
status: 'idle' | 'opened' | 'closed'
statusChangeTimestamp?: number
logs: ILog[]
socket?: ReconnectingWebSocket
}
export const streamState = proxy<IStreamState>({
selectedAccount: null as ISelect | null,
status: 'idle',
logs: [] as ILog[]
})
const onOpen = (account: ISelect | null) => {
if (!account) {
return
}
// streamState.logs = [];
streamState.status = 'opened'
streamState.statusChangeTimestamp = Date.now()
pushLog(`Debug stream opened for account ${account?.value}`, {
type: 'success'
})
}
const onError = () => {
pushLog('Something went wrong! Check your connection and try again.', {
type: 'error'
})
}
const onClose = (e: CloseEvent) => {
// 999 = closed websocket connection by switching account
if (e.code !== 4999) {
pushLog(`Connection was closed. [code: ${e.code}]`, {
type: 'error'
})
}
streamState.status = 'closed'
streamState.statusChangeTimestamp = Date.now()
}
const onMessage = (event: any) => {
// Ping returns just account address, if we get that
// response we don't need to log anything
if (event.data !== streamState.selectedAccount?.value) {
pushLog(event.data)
}
}
let interval: NodeJS.Timer | null = null
const addListeners = (account: ISelect | null) => {
if (account?.value && streamState.socket?.url.endsWith(account?.value)) {
return
}
streamState.logs = []
if (account?.value) {
if (interval) {
clearInterval(interval)
}
if (streamState.socket) {
streamState.socket?.removeEventListener('open', () => onOpen(account))
streamState.socket?.removeEventListener('close', onClose)
streamState.socket?.removeEventListener('error', onError)
streamState.socket?.removeEventListener('message', onMessage)
}
streamState.socket = ref(
new ReconnectingWebSocket(
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account?.value}`
)
)
if (streamState.socket) {
interval = setInterval(() => {
streamState.socket?.send('')
}, 45000)
}
streamState.socket.addEventListener('open', () => onOpen(account))
streamState.socket.addEventListener('close', onClose)
streamState.socket.addEventListener('error', onError)
streamState.socket.addEventListener('message', onMessage)
}
}
subscribeKey(streamState, 'selectedAccount', addListeners)
const clearLog = () => {
streamState.logs = []
streamState.statusChangeTimestamp = Date.now()
}
const DebugStream = () => {
const { selectedAccount, logs } = useSnapshot(streamState)
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
const { accounts } = useSnapshot(state)
const accountOptions = accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const renderNav = () => (
<>
<Select
instanceId="DSAccount"
placeholder="Select account"
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={acc => {
streamState.socket?.close(4999, 'Old connection closed because user switched account')
streamState.selectedAccount = acc as any
}}
css={{ width: '100%' }}
/>
</>
)
useEffect(() => {
const account = transactionsState.transactions.find(tx => tx.header === activeTxTab)?.state
.selectedAccount
if (account && account.value !== streamState.selectedAccount?.value)
streamState.selectedAccount = account
}, [activeTxTab])
return (
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
)
}
export default DebugStream
export const pushLog = (str: any, opts: Partial<Pick<ILog, 'type'>> = {}): ILog | undefined => {
if (!str) return
if (typeof str !== 'string') throw Error('Unrecognized debug log stream!')
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))?\ ?([\s\S]*)/m)
const [_, tm, msg] = match || []
const timestamp = Date.parse(tm || '') || undefined
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
const extracted = extractJSON(msg)
const _message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
const message = ref(<EnrichLog str={_message} />)
const _jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
const jsonData = _jsonData ? ref(<EnrichLog str={_jsonData} />) : undefined
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
return
}
const { type = 'log' } = opts
const log: ILog = {
type,
message,
timestring,
jsonData,
defaultCollapsed: true
}
if (log) streamState.logs.push(log)
return log
}
+166
View File
@@ -0,0 +1,166 @@
import React, { useState } from 'react'
import { useSnapshot } from 'valtio'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import NextLink from 'next/link'
import ReactTimeAgo from 'react-time-ago'
import filesize from 'filesize'
import Box from './Box'
import Container from './Container'
import state from '../state'
import wat from '../utils/wat-highlight'
import EditorNavigation from './EditorNavigation'
import { Button, Text, Link, Flex, Tabs, Tab } from '.'
import Monaco from './Monaco'
const FILESIZE_BREAKPOINTS: [number, number] = [2 * 1024, 5 * 1024]
const DeployEditor = () => {
const snap = useSnapshot(state)
const router = useRouter()
const { theme } = useTheme()
const [showContent, setShowContent] = useState(false)
const compiledFiles = snap.files.filter(file => file.compiledContent)
const activeFile = compiledFiles[snap.activeWat]
const renderNav = () => (
<Tabs activeIndex={snap.activeWat} onChangeActive={idx => (state.activeWat = idx)}>
{compiledFiles.map((file, index) => {
return <Tab key={file.name} header={`${file.name}.wat`} />
})}
</Tabs>
)
const compiledSize = activeFile?.compiledContent?.byteLength || 0
const color =
compiledSize > FILESIZE_BREAKPOINTS[1]
? '$error'
: compiledSize > FILESIZE_BREAKPOINTS[0]
? '$warning'
: '$success'
const isContentChanged = activeFile && activeFile.compiledValueSnapshot !== activeFile.content
// const hasDeployErrors = activeFile && activeFile.containsErrors;
const CompiledStatView = activeFile && (
<Flex
column
align="center"
css={{
fontSize: '$sm',
fontFamily: '$monospace',
textAlign: 'center'
}}
>
<Flex row align="center">
<Text css={{ mr: '$1' }}>Compiled {activeFile.name.split('.')[0] + '.wasm'}</Text>
{activeFile?.lastCompiled && <ReactTimeAgo date={activeFile.lastCompiled} locale="en-US" />}
{activeFile.compiledContent?.byteLength && (
<Text css={{ ml: '$2', color }}>({filesize(activeFile.compiledContent.byteLength)})</Text>
)}
</Flex>
{activeFile.compiledContent?.byteLength && activeFile.compiledContent?.byteLength >= 64000 && (
<Flex css={{ flexDirection: 'column', py: '$3', pb: '$1' }}>
<Text css={{ ml: '$2', color: '$error' }}>
File size is larger than 64kB, cannot set hook!
</Text>
</Flex>
)}
<Button variant="link" onClick={() => setShowContent(true)}>
View as WAT-file
</Button>
{isContentChanged && (
<Text warning>
File contents were changed after last compile, compile again to incorporate your latest
changes in the build.
</Text>
)}
</Flex>
)
const NoContentView = !snap.loading && router.isReady && (
<Text
css={{
mt: '-60px',
fontSize: '$sm',
fontFamily: '$monospace',
maxWidth: '300px',
textAlign: 'center'
}}
>
{`You haven't compiled any files yet, compile files on `}
<NextLink shallow href={`/develop/${router.query.slug}`} passHref>
<Link as="a">develop view</Link>
</NextLink>
</Text>
)
const isContent = snap.files?.filter(file => file.compiledWatContent).length > 0 && router.isReady
return (
<Box
css={{
flex: 1,
display: 'flex',
position: 'relative',
flexDirection: 'column',
backgroundColor: '$mauve2',
width: '100%'
}}
>
<EditorNavigation renderNav={renderNav} />
<Container
css={{
display: 'flex',
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}}
>
{!isContent ? (
NoContentView
) : !showContent ? (
CompiledStatView
) : (
<Monaco
className="hooks-editor"
defaultLanguage={'wat'}
language={'wat'}
path={`file://tmp/c/${activeFile?.name}.wat`}
value={activeFile?.compiledWatContent || ''}
beforeMount={monaco => {
monaco.languages.register({ id: 'wat' })
monaco.languages.setLanguageConfiguration('wat', wat.config)
monaco.languages.setMonarchTokensProvider('wat', wat.tokens)
}}
onMount={editor => {
editor.updateOptions({
glyphMargin: true,
readOnly: true
})
}}
theme={theme === 'dark' ? 'dark' : 'light'}
overlay={
<Flex
css={{
m: '$1',
ml: 'auto',
fontSize: '$sm',
color: '$textMuted'
}}
>
<Link onClick={() => setShowContent(false)}>Exit editor mode</Link>
</Flex>
}
/>
)}
</Container>
</Box>
)
}
export default DeployEditor
+62 -59
View File
@@ -1,85 +1,88 @@
import React from "react";
import * as Stiches from "@stitches/react";
import { keyframes } from "@stitches/react";
import { violet, blackA, mauve, whiteA } from "@radix-ui/colors";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { styled } from "../stitches.config";
import React from 'react'
import * as Stiches from '@stitches/react'
import { keyframes } from '@stitches/react'
import { blackA } from '@radix-ui/colors'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { styled } from '../stitches.config'
const overlayShow = keyframes({
"0%": { opacity: 0 },
"100%": { opacity: 1 },
});
'0%': { opacity: 0.01 },
'100%': { opacity: 1 }
})
const contentShow = keyframes({
"0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" },
"100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" },
});
'0%': { opacity: 0.01 },
'100%': { opacity: 1 }
})
const StyledOverlay = styled(DialogPrimitive.Overlay, {
zIndex: 1000,
zIndex: 3000,
backgroundColor: blackA.blackA9,
position: "fixed",
position: 'fixed',
inset: 0,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'grid',
placeItems: 'center',
overflowY: 'auto',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 250ms cubic-bezier(0.16, 1, 0.3, 1)`
},
".dark &": {
backgroundColor: blackA.blackA9,
},
});
'.dark &': {
backgroundColor: blackA.blackA11
}
})
const StyledContent = styled(DialogPrimitive.Content, {
zIndex: 1000,
backgroundColor: "$mauve2",
color: "$mauve12",
borderRadius: "$md",
boxShadow:
"0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "90vw",
maxWidth: "450px",
maxHeight: "85vh",
backgroundColor: '$mauve2',
color: '$mauve12',
borderRadius: '$md',
position: 'relative',
mb: '15%',
boxShadow: '0px 10px 38px -5px rgba(22, 23, 24, 0.25), 0px 10px 20px -5px rgba(22, 23, 24, 0.2)',
width: '90vw',
maxWidth: '450px',
// maxHeight: "85vh",
padding: 25,
"@media (prefers-reduced-motion: no-preference)": {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`
},
"&:focus": { outline: "none" },
".dark &": {
backgroundColor: "$mauve5",
boxShadow:
"0px 10px 38px 0px rgba(22, 23, 24, 0.85), 0px 10px 20px 0px rgba(22, 23, 24, 0.6)",
},
});
'&:focus': { outline: 'none' },
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 10px 38px 0px rgba(0, 0, 0, 0.85), 0px 10px 20px 0px rgba(0, 0, 0, 0.6)'
}
})
const Content: React.FC<{ css?: Stiches.CSS }> = ({ css, children }) => {
return (
<div>
<StyledOverlay />
<StyledOverlay>
<StyledContent css={css}>{children}</StyledContent>
</div>
);
};
</StyledOverlay>
)
}
const StyledTitle = styled(DialogPrimitive.Title, {
margin: 0,
fontWeight: 500,
color: "$mauve12",
fontSize: 17,
});
color: '$mauve12',
fontSize: 17
})
const StyledDescription = styled(DialogPrimitive.Description, {
margin: "10px 0 20px",
color: "$mauve11",
margin: '10px 0 10px',
color: '$mauve11',
fontSize: 15,
lineHeight: 1.5,
});
lineHeight: 1.5
})
// Exports
export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogContent = Content;
export const DialogTitle = StyledTitle;
export const DialogDescription = StyledDescription;
export const DialogClose = DialogPrimitive.Close;
export const Dialog = styled(DialogPrimitive.Root)
export const DialogTrigger = DialogPrimitive.Trigger
export const DialogContent = Content
export const DialogTitle = StyledTitle
export const DialogDescription = StyledDescription
export const DialogClose = DialogPrimitive.Close
export const DialogPortal = DialogPrimitive.Portal
+77 -87
View File
@@ -1,130 +1,120 @@
import { keyframes } from "@stitches/react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { styled } from "../stitches.config";
const slideUpAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
const slideRightAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(-2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
const slideDownAndFade = keyframes({
"0%": { opacity: 0, transform: "translateY(-2px)" },
"100%": { opacity: 1, transform: "translateY(0)" },
});
const slideLeftAndFade = keyframes({
"0%": { opacity: 0, transform: "translateX(2px)" },
"100%": { opacity: 1, transform: "translateX(0)" },
});
import { styled } from '../stitches.config'
import {
slideDownAndFade,
slideLeftAndFade,
slideRightAndFade,
slideUpAndFade
} from '../styles/keyframes'
const StyledContent = styled(DropdownMenuPrimitive.Content, {
minWidth: 220,
backgroundColor: "$mauve2",
backgroundColor: '$mauve2',
borderRadius: 6,
padding: 5,
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
"@media (prefers-reduced-motion: no-preference)": {
animationDuration: "400ms",
animationTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
willChange: "transform, opacity",
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
},
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
".dark &": {
backgroundColor: "$mauve5",
'.dark &': {
backgroundColor: '$mauve5',
boxShadow:
"0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)",
},
});
'0px 10px 38px -10px rgba(22, 23, 24, 0.85), 0px 10px 20px -15px rgba(22, 23, 24, 0.6)'
}
})
const itemStyles = {
all: "unset",
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: "$mauve12",
color: '$mauve12',
borderRadius: 3,
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
height: 32,
padding: "0 5px",
position: "relative",
paddingLeft: "5px",
userSelect: "none",
gap: "$2",
padding: '0 5px',
position: 'relative',
paddingLeft: '5px',
userSelect: 'none',
py: '$0.5',
pr: '$2',
gap: '$2',
"&[data-disabled]": {
color: "$mauve9",
pointerEvents: "none",
'&[data-disabled]': {
color: '$mauve9',
pointerEvents: 'none'
},
"&:focus": {
backgroundColor: "$pink9",
color: "$white",
},
};
'&:focus': {
backgroundColor: '$purple9',
color: '$white'
}
}
const StyledItem = styled(DropdownMenuPrimitive.Item, { ...itemStyles });
const StyledItem = styled(DropdownMenuPrimitive.Item, { ...itemStyles })
const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, {
...itemStyles,
});
...itemStyles
})
const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
...itemStyles,
});
...itemStyles
})
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: "$pink9",
color: "$pink9",
backgroundColor: '$purple9',
color: '$purple9'
},
...itemStyles,
});
...itemStyles
})
const StyledLabel = styled(DropdownMenuPrimitive.Label, {
paddingLeft: 25,
fontSize: 12,
lineHeight: "25px",
color: "$mauve11",
});
lineHeight: '25px',
color: '$mauve11'
})
const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
height: 1,
backgroundColor: "$mauve7",
margin: 5,
});
backgroundColor: '$mauve7',
margin: 5
})
const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
position: "absolute",
position: 'absolute',
left: 0,
width: 25,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
});
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
})
const StyledArrow = styled(DropdownMenuPrimitive.Arrow, {
fill: "$mauve2",
});
fill: '$mauve2',
'.dark &': {
fill: '$mauve5'
}
})
// Exports
export const DropdownMenu = DropdownMenuPrimitive.Root;
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuContent = StyledContent;
export const DropdownMenuItem = StyledItem;
export const DropdownMenuCheckboxItem = StyledCheckboxItem;
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export const DropdownMenuRadioItem = StyledRadioItem;
export const DropdownMenuItemIndicator = StyledItemIndicator;
export const DropdownMenuTriggerItem = StyledTriggerItem;
export const DropdownMenuLabel = StyledLabel;
export const DropdownMenuSeparator = StyledSeparator;
export const DropdownMenuArrow = StyledArrow;
export const DropdownMenu = DropdownMenuPrimitive.Root
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
export const DropdownMenuContent = StyledContent
export const DropdownMenuItem = StyledItem
export const DropdownMenuCheckboxItem = StyledCheckboxItem
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
export const DropdownMenuRadioItem = StyledRadioItem
export const DropdownMenuItemIndicator = StyledItemIndicator
export const DropdownMenuTriggerItem = StyledTriggerItem
export const DropdownMenuLabel = StyledLabel
export const DropdownMenuSeparator = StyledSeparator
export const DropdownMenuArrow = StyledArrow
+349 -220
View File
@@ -1,250 +1,379 @@
import React, { useRef, useState } from "react";
import { Plus, Share, DownloadSimple, Gear, X } from "phosphor-react";
import { useTheme } from "next-themes";
import React, { useState, useEffect, useRef, ReactNode } from 'react'
import {
Share,
DownloadSimple,
Gear,
X,
GithubLogo,
SignOut,
ArrowSquareOut,
CloudArrowUp,
CaretDown,
User,
FilePlus
} from 'phosphor-react'
import Image from 'next/image'
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuArrow,
DropdownMenuSeparator
} from './DropdownMenu'
import NewWindow from 'react-new-window'
import { signOut, useSession } from 'next-auth/react'
import { useSnapshot } from 'valtio'
import toast from 'react-hot-toast'
import { createNewFile, state, updateEditorSettings } from "../state";
import Box from "./Box";
import Button from "./Button";
import Container from "./Container";
import { syncToGist, updateEditorSettings, downloadAsZip } from '../state/actions'
import state from '../state'
import Box from './Box'
import Button from './Button'
import Container from './Container'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
} from "./Dialog";
import Flex from "./Flex";
import Stack from "./Stack";
import { useSnapshot } from "valtio";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import Input from "./Input";
DialogClose
} from './Dialog'
import Flex from './Flex'
import Stack from './Stack'
import { Input, Label } from './Input'
import Tooltip from './Tooltip'
import { showAlert } from '../state/actions/showAlert'
const EditorNavigation = () => {
const snap = useSnapshot(state);
const [filename, setFilename] = useState("");
const { theme } = useTheme();
const { data: session, status } = useSession();
const router = useRouter();
const [editorSettings, setEditorSettings] = useState(snap.editorSettings);
const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
const snap = useSnapshot(state)
const [editorSettingsOpen, setEditorSettingsOpen] = useState(false)
const { data: session, status } = useSession()
const [popup, setPopUp] = useState(false)
const [editorSettings, setEditorSettings] = useState(snap.editorSettings)
useEffect(() => {
if (session && session.user && popup) {
setPopUp(false)
}
}, [session, popup])
const showNewGistAlert = () => {
showAlert('Are you sure?', {
body: (
<>
This action will create new <strong>public</strong> Github Gist from your current saved
files. You can delete gist anytime from your GitHub Gists page.
</>
),
cancelText: 'Cancel',
confirmText: 'Create new Gist',
confirmPrefix: <FilePlus size="15px" />,
onConfirm: () => syncToGist(session, true)
})
}
const scrollRef = useRef<HTMLDivElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
return (
<Flex css={{ flexShrink: 0, gap: "$0" }}>
<Flex css={{ overflowX: "scroll", py: "$3", flex: 1 }}>
<Container css={{ flex: 1 }}>
<Stack css={{ gap: "$3", flex: 1, flexWrap: "nowrap" }}>
{state.loading && "loading"}
{snap.files &&
snap.files.length > 0 &&
snap.files?.map((file, index) => (
<Button
size="sm"
outline={snap.active !== index}
onClick={() => (state.active = index)}
key={file.name + index}
css={{
"&:hover": {
span: {
visibility: "visible",
},
},
}}
>
{file.name}
<Box
as="span"
css={{
display: "flex",
p: "2px",
borderRadius: "$full",
mr: "-4px",
"&:hover": {
// boxSizing: "0px 0px 1px",
backgroundColor: "$mauve2",
color: "$mauve12",
},
}}
onClick={(ev: React.MouseEvent<HTMLElement>) => {
ev.stopPropagation();
// Remove file from state
state.files.splice(index, 1);
// Change active file state
// If deleted file is behind active tab
// we keep the current state otherwise
// select previous file on the list
state.active =
index > snap.active ? snap.active : snap.active - 1;
}}
>
<X size="9px" weight="bold" />
</Box>
</Button>
))}
<Dialog>
<DialogTrigger asChild>
<Button
ghost
size="sm"
css={{ alignItems: "center", px: "$2", mr: "$3" }}
>
<Plus size="16px" />{" "}
{snap.files.length === 0 && "Add new file"}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Create new file</DialogTitle>
<DialogDescription>
<label>Filename</label>
<Input
value={filename}
onChange={(e) => setFilename(e.target.value)}
/>
</DialogDescription>
<Flex
css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button
variant="primary"
onClick={() => {
createNewFile(filename);
// reset
setFilename("");
}}
>
Create file
</Button>
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
</Stack>
<Flex css={{ flexShrink: 0, gap: '$0' }}>
<Flex
id="kissa"
ref={scrollRef}
css={{
overflowX: 'scroll',
overflowY: 'hidden',
py: '$3',
pb: '$0',
flex: 1,
'&::-webkit-scrollbar': {
height: '0.3em',
background: 'rgba(0,0,0,.0)'
},
'&::-webkit-scrollbar-gutter': 'stable',
'&::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(0,0,0,.2)',
outline: '0px',
borderRadius: '9999px'
},
scrollbarColor: 'rgba(0,0,0,.2) rgba(0,0,0,0)',
scrollbarGutter: 'stable',
scrollbarWidth: 'thin',
'.dark &': {
'&::-webkit-scrollbar': {
background: 'rgba(0,0,0,.0)'
},
'&::-webkit-scrollbar-gutter': 'stable',
'&::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(255,255,255,.2)',
outline: '0px',
borderRadius: '9999px'
},
scrollbarColor: 'rgba(255,255,255,.2) rgba(0,0,0,0)',
scrollbarGutter: 'stable',
scrollbarWidth: 'thin'
}
}}
onWheelCapture={e => {
if (scrollRef.current) {
scrollRef.current.scrollLeft += e.deltaY
}
}}
>
<Container css={{ flex: 1 }} ref={containerRef}>
{renderNav?.()}
</Container>
</Flex>
<Flex
css={{
py: "$3",
backgroundColor: "$mauve3",
zIndex: 1,
py: '$3',
backgroundColor: '$mauve2',
zIndex: 1
}}
>
<Container css={{ width: "unset" }}>
<Container css={{ width: 'unset', display: 'flex', alignItems: 'center' }}>
{status === 'authenticated' ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Box
css={{
display: 'flex',
borderRadius: '$full',
overflow: 'hidden',
width: '$6',
height: '$6',
boxShadow: '0px 0px 0px 1px $colors$mauve11',
position: 'relative',
mr: '$3',
'@hover': {
'&:hover': {
cursor: 'pointer',
boxShadow: '0px 0px 0px 1px $colors$mauve12'
}
}
}}
>
<Image
src={session?.user?.image || ''}
width="30px"
height="30px"
objectFit="cover"
alt="User avatar"
/>
</Box>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled onClick={() => signOut()}>
<User size="16px" /> {session?.user?.username} ({session?.user.name})
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => window.open(`http://gist.github.com/${session?.user.username}`)}
>
<ArrowSquareOut size="16px" />
Go to your Gist
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut({ callbackUrl: '/' })}>
<SignOut size="16px" /> Log out
</DropdownMenuItem>
<DropdownMenuArrow offset={10} />
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button outline size="sm" css={{ mr: '$3' }} onClick={() => setPopUp(true)}>
<GithubLogo size="16px" /> Login
</Button>
)}
<Stack
css={{
display: "inline-flex",
marginLeft: "auto",
display: 'inline-flex',
marginLeft: 'auto',
flexShrink: 0,
gap: "$0",
border: "1px solid $mauve10",
borderRadius: "$sm",
gap: '$0',
borderRadius: '$sm',
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
zIndex: 9,
position: "relative",
overflow: "hidden",
position: 'relative',
button: {
borderRadius: "$0",
px: "$2",
alignSelf: "flex-start",
borderRadius: 0,
px: '$2',
alignSelf: 'flex-start',
boxShadow: 'none'
},
'button:not(:first-child):not(:last-child)': {
borderRight: 0,
borderLeft: 0
},
'button:first-child': {
borderTopLeftRadius: '$sm',
borderBottomLeftRadius: '$sm'
},
'button:last-child': {
borderTopRightRadius: '$sm',
borderBottomRightRadius: '$sm',
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
'&:hover': {
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve12'
}
}
}}
>
<Button ghost size="sm" css={{ alignItems: "center" }}>
<DownloadSimple size="16px" />
</Button>
<Dialog>
<DialogTrigger asChild>
<Button ghost size="sm" css={{ alignItems: "center" }}>
<Tooltip content="Download as ZIP">
<Button
isLoading={snap.zipLoading}
onClick={downloadAsZip}
outline
size="sm"
css={{ alignItems: 'center' }}
>
<DownloadSimple size="16px" />
</Button>
</Tooltip>
<Tooltip content="Copy share link to clipboard">
<Button
outline
size="sm"
css={{ alignItems: 'center' }}
onClick={() => {
navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`)
toast.success('Copied share link to clipboard!')
}}
>
<Share size="16px" />
</Button>
</Tooltip>
<Tooltip
content={
session && session.user
? snap.gistOwner === session?.user.username
? 'Sync to Gist'
: 'Create as a new Gist'
: 'You need to be logged in to sync with Gist'
}
>
<Button
outline
size="sm"
isDisabled={!session || !session.user}
isLoading={snap.gistLoading}
css={{ alignItems: 'center' }}
onClick={() => {
if (!session || !session.user) {
return
}
if (snap.gistOwner === session?.user.username) {
syncToGist(session)
} else {
showNewGistAlert()
}
}}
>
{snap.gistOwner === session?.user.username ? (
<CloudArrowUp size="16px" />
) : (
<FilePlus size="16px" />
)}
</Button>
</Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button outline size="sm">
<CaretDown size="16px" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
<DownloadSimple size="16px" /> Download as ZIP
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(
`${window.location.origin}/develop/${snap.gistId}`
)
toast.success('Copied share link to clipboard!')
}}
>
<Share size="16px" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Share hook</DialogTitle>
<DialogDescription>
<span>
We will store your hook code in public GitHub Gist and
generate link to that
</span>
</DialogDescription>
<Flex
css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}
Copy share link to clipboard
</DropdownMenuItem>
<DropdownMenuItem
disabled={session?.user.username !== snap.gistOwner || !snap.gistId}
onClick={() => {
syncToGist(session)
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button ghost size="sm" css={{ alignItems: "center" }}>
<Gear size="16px" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Editor settings</DialogTitle>
<DialogDescription>
<label>Tab size</label>
<Input
type="number"
min="1"
value={editorSettings.tabSize}
onChange={(e) =>
setEditorSettings((curr) => ({
...curr,
tabSize: Number(e.target.value),
}))
}
/>
</DialogDescription>
<Flex
css={{ marginTop: 25, justifyContent: "flex-end", gap: "$3" }}
<CloudArrowUp size="16px" /> Push to Gist
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={status !== 'authenticated'}
onClick={() => {
showNewGistAlert()
}}
>
<DialogClose asChild>
<Button
outline
onClick={() => updateEditorSettings(editorSettings)}
>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button
variant="primary"
onClick={() => updateEditorSettings(editorSettings)}
>
Save changes
</Button>
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: "absolute", top: "$3", right: "$3" }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
<FilePlus size="16px" /> Create as a new Gist
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setEditorSettingsOpen(true)}>
<Gear size="16px" /> Editor Settings
</DropdownMenuItem>
<DropdownMenuArrow offset={10} />
</DropdownMenuContent>
</DropdownMenu>
</Stack>
{popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
</Container>
</Flex>
</Flex>
);
};
export default EditorNavigation;
<Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
<DialogTrigger asChild>
{/* <Button outline size="sm" css={{ alignItems: "center" }}>
<Gear size="16px" />
</Button> */}
</DialogTrigger>
<DialogContent>
<DialogTitle>Editor settings</DialogTitle>
<DialogDescription>
<Label>Tab size</Label>
<Input
type="number"
min="1"
value={editorSettings.tabSize}
onChange={e =>
setEditorSettings(curr => ({
...curr,
tabSize: Number(e.target.value)
}))
}
/>
</DialogDescription>
<Flex css={{ marginTop: 25, justifyContent: 'flex-end', gap: '$3' }}>
<DialogClose asChild>
<Button outline onClick={() => updateEditorSettings(editorSettings)}>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button variant="primary" onClick={() => updateEditorSettings(editorSettings)}>
Save changes
</Button>
</DialogClose>
</Flex>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
</Flex>
)
}
export default EditorNavigation
+73
View File
@@ -0,0 +1,73 @@
import { FC, useState } from 'react'
import regexifyString from 'regexify-string'
import { useSnapshot } from 'valtio'
import { Link } from '.'
import state from '../state'
import { AccountDialog } from './Accounts'
import Tooltip from './Tooltip'
import hookSetCodes from '../content/hook-set-codes.json'
import { capitalize } from '../utils/helpers'
interface EnrichLogProps {
str?: string
}
const EnrichLog: FC<EnrichLogProps> = ({ str }) => {
const { accounts } = useSnapshot(state)
const [dialogAccount, setDialogAccount] = useState<string | null>(null)
if (!str || !accounts.length) return <>{str}</>
const addrs = accounts.map(acc => acc.address)
const regex = `(${addrs.join('|')}|HookSet\\(\\d+\\))`
const res = regexifyString({
pattern: new RegExp(regex, 'gim'),
decorator: (match, idx) => {
if (match.startsWith('r')) {
// Account
const name = accounts.find(acc => acc.address === match)?.name
return (
<Link
key={match + idx}
as="a"
onClick={() => setDialogAccount(match)}
title={match}
highlighted
>
{name || match}
</Link>
)
}
if (match.startsWith('HookSet')) {
const code = match.match(/^HookSet\((\d+)\)/)?.[1]
const val = hookSetCodes.find(v => code && v.code === +code)
console.log({ code, val })
if (!val) return match
const content = capitalize(val.description) || 'No hint available!'
return (
<>
HookSet(
<Tooltip content={content}>
<Link>{val.identifier}</Link>
</Tooltip>
)
</>
)
}
return match
},
input: str
})
return (
<>
{res}
<AccountDialog
setActiveAccountAddress={setDialogAccount}
activeAccountAddress={dialogAccount}
/>
</>
)
}
export default EnrichLog
+50 -5
View File
@@ -1,8 +1,53 @@
import { styled } from "../stitches.config";
import Box from "./Box";
import { styled } from '../stitches.config'
import Box from './Box'
const Flex = styled(Box, {
display: "flex",
});
display: 'flex',
variants: {
row: {
true: {
flexDirection: 'row'
}
},
column: {
true: {
flexDirection: 'column'
}
},
fluid: {
true: {
width: '100%'
}
},
align: {
start: {
alignItems: 'start'
},
center: {
alignItems: 'center'
},
end: {
alignItems: 'end'
}
},
justify: {
start: {
justifyContent: 'start'
},
center: {
justifyContent: 'center'
},
end: {
justifyContent: 'end'
},
'space-between': {
justifyContent: 'space-between'
},
'space-around': {
justifyContent: 'space-around'
}
}
}
})
export default Flex;
export default Flex
-51
View File
@@ -1,51 +0,0 @@
import { useSnapshot } from "valtio";
import Container from "./Container";
import Box from "./Box";
import LogText from "./LogText";
import { state } from "../state";
const Footer = () => {
const snap = useSnapshot(state);
return (
<Box
as="footer"
css={{
display: "flex",
borderTop: "1px solid $mauve6",
background: "$mauve1",
}}
>
<Container css={{ py: "$3", flexShrink: 1 }}>
<Box
as="pre"
css={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "160px",
fontSize: "13px",
fontWeight: "$body",
fontFamily: "$monospace",
overflowY: "scroll",
wordWrap: "break-word",
py: 3,
px: 3,
m: 3,
}}
>
{snap.logs.map((log, index) => (
<Box as="span" key={log.type + index}>
<LogText capitalize variant={log.type}>
{log.type}:{" "}
</LogText>
<LogText>{log.message}</LogText>
</Box>
))}
</Box>
</Container>
</Box>
);
};
export default Footer;
+14 -8
View File
@@ -1,10 +1,16 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Heading = styled("span", {
fontFamily: "$heading",
lineHeight: "$heading",
fontWeight: "$heading",
textTransform: "uppercase",
});
const Heading = styled('span', {
fontFamily: '$heading',
lineHeight: '$heading',
fontWeight: '$heading',
variants: {
uppercase: {
true: {
textTransform: 'uppercase'
}
}
}
})
export default Heading;
export default Heading
+313 -100
View File
@@ -1,120 +1,333 @@
import React, { useEffect, useRef } from "react";
import { useSnapshot, ref } from "valtio";
import Editor from "@monaco-editor/react";
import type monaco from "monaco-editor";
import { ArrowBendLeftUp, Play } from "phosphor-react";
import { useTheme } from "next-themes";
import React, { useEffect, useRef, useState } from 'react'
import { useSnapshot, ref } from 'valtio'
import type monaco from 'monaco-editor'
import { ArrowBendLeftUp } from 'phosphor-react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import Box from "./Box";
import Button from "./Button";
import Container from "./Container";
import dark from "../theme/editor/amy.json";
import light from "../theme/editor/xcode_default.json";
import { compileCode, saveFile, state } from "../state";
import Box from './Box'
import Container from './Container'
import { createNewFile, saveFile } from '../state/actions'
import { apiHeaderFiles } from '../state/constants'
import state from '../state'
import EditorNavigation from "./EditorNavigation";
import Text from "./Text";
import EditorNavigation from './EditorNavigation'
import Text from './Text'
import { MonacoServices } from '@codingame/monaco-languageclient'
import { createLanguageClient, createWebSocket } from '../utils/languageClient'
import { listen } from '@codingame/monaco-jsonrpc'
import ReconnectingWebSocket from 'reconnecting-websocket'
import docs from '../xrpl-hooks-docs/docs'
import Monaco from './Monaco'
import { saveAllFiles } from '../state/actions/saveFile'
import { Tab, Tabs } from './Tabs'
import { renameFile } from '../state/actions/createNewFile'
import { Link } from '.'
import Markdown from './Markdown'
const checkWritable = (filename?: string): boolean => {
if (apiHeaderFiles.find(file => file === filename)) {
return false
}
return true
}
const validateWritability = (editor: monaco.editor.IStandaloneCodeEditor) => {
const filename = editor.getModel()?.uri.path.split('/').pop()
const isWritable = checkWritable(filename)
editor.updateOptions({ readOnly: !isWritable })
}
let decorations: { [key: string]: string[] } = {}
const setMarkers = (monacoE: typeof monaco) => {
// Get all the markers that are active at the moment,
// Also if same error is there twice, we can show the content
// only once (that's why we're using uniqBy)
const markers = monacoE.editor
.getModelMarkers({})
// Filter out the markers that are hooks specific
.filter(
marker =>
typeof marker?.code === 'string' &&
// Take only markers that starts with "hooks-"
marker?.code?.includes('hooks-')
)
// Get the active model (aka active file you're editing)
// const model = monacoE.editor?.getModel(
// monacoE.Uri.parse(`file:///work/c/${state.files?.[state.active]?.name}`)
// );
// console.log(state.active);
// Add decoration (aka extra hoverMessages) to markers in the
// exact same range (location) where the markers are
const models = monacoE.editor.getModels()
models.forEach(model => {
decorations[model.id] = model?.deltaDecorations(
decorations?.[model.id] || [],
markers
.filter(marker =>
marker?.resource.path.split('/').includes(`${state.files?.[state.active]?.name}`)
)
.map(marker => ({
range: new monacoE.Range(
marker.startLineNumber,
marker.startColumn,
marker.endLineNumber,
marker.endColumn
),
options: {
hoverMessage: {
value:
// Find the related hover message markdown from the
// /xrpl-hooks-docs/xrpl-hooks-docs-files.json file
// which was generated from rst files
(typeof marker.code === 'string' && docs[marker?.code]?.toString()) || '',
supportHtml: true,
isTrusted: true
}
}
}))
)
})
}
const HooksEditor = () => {
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const snap = useSnapshot(state);
const { theme } = useTheme();
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>()
const monacoRef = useRef<typeof monaco>()
const subscriptionRef = useRef<ReconnectingWebSocket | null>(null)
const snap = useSnapshot(state)
const router = useRouter()
const { theme } = useTheme()
const [isMdPreview, setIsMdPreview] = useState(true)
useEffect(() => {
if (editorRef.current) validateWritability(editorRef.current)
}, [snap.active])
useEffect(() => {
return () => {
subscriptionRef?.current?.close()
}
}, [])
useEffect(() => {
if (monacoRef.current) {
setMarkers(monacoRef.current)
}
}, [snap.active])
useEffect(() => {
return () => {
saveAllFiles()
}
}, [])
const file = snap.files[snap.active]
const renderNav = () => (
<Tabs
label="File"
activeIndex={snap.active}
onChangeActive={idx => (state.active = idx)}
extensionRequired
onCreateNewTab={createNewFile}
onCloseTab={idx => state.files.splice(idx, 1)}
onRenameTab={(idx, nwName, oldName = '') => renameFile(oldName, nwName)}
headerExtraValidation={{
regex: /^[A-Za-z0-9_-]+[.][A-Za-z0-9]{1,4}$/g,
error: 'Filename can contain only characters from a-z, A-Z, 0-9, "_" and "-"'
}}
>
{snap.files.map((file, index) => {
return <Tab key={file.name} header={file.name} renameDisabled={!checkWritable(file.name)} />
})}
</Tabs>
)
const previewToggle = (
<Link
onClick={() => {
if (!isMdPreview) {
saveFile(false)
}
setIsMdPreview(!isMdPreview)
}}
css={{
position: 'absolute',
right: 0,
bottom: 0,
zIndex: 10,
m: '$1',
fontSize: '$sm'
}}
>
{isMdPreview ? 'Exit Preview' : 'View Preview'}
</Link>
)
return (
<Box
css={{
flex: 1,
flexShrink: 1,
display: "flex",
position: "relative",
flexDirection: "column",
backgroundColor: "$mauve3",
width: "100%",
display: 'flex',
position: 'relative',
flexDirection: 'column',
backgroundColor: '$mauve2',
width: '100%'
}}
>
<EditorNavigation />
{snap.files.length > 0 ? (
<Editor
keepCurrentModel
defaultLanguage={snap.files?.[snap.active]?.language}
path={snap.files?.[snap.active]?.name}
defaultValue={snap.files?.[snap.active]?.content}
beforeMount={(monaco) => {
if (!state.editorCtx) {
state.editorCtx = ref(monaco.editor);
// @ts-expect-error
monaco.editor.defineTheme("dark", dark);
// @ts-expect-error
monaco.editor.defineTheme("light", light);
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor;
// hook editor to global state
editor.updateOptions({
minimap: {
enabled: false,
},
...snap.editorSettings,
});
editor.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S,
() => {
saveFile(editor.getValue());
}
);
}}
theme={theme === "dark" ? "dark" : "light"}
/>
) : (
<Container>
<Box
css={{
flexDirection: "row",
width: "$spaces$wide",
gap: "$3",
display: "inline-flex",
<EditorNavigation renderNav={renderNav} />
{file?.language === 'markdown' && previewToggle}
{snap.files.length > 0 && router.isReady ? (
isMdPreview && file?.language === 'markdown' ? (
<Markdown
components={{
a: ({ href, children }) => (
<Link target="_blank" rel="noopener noreferrer" href={href}>
{children}
</Link>
)
}}
>
<Box css={{ display: "inline-flex", pl: "35px" }}>
<ArrowBendLeftUp size={30} />
</Box>
{file.content}
</Markdown>
) : (
<Monaco
keepCurrentModel
defaultLanguage={file?.language}
language={file?.language}
path={`file:///work/c/${file?.name}`}
defaultValue={file?.content}
// onChange={val => (state.files[snap.active].content = val)} // Auto save?
beforeMount={monaco => {
// if (!snap.editorCtx) {
// snap.files.forEach(file =>
// monaco.editor.createModel(
// file.content,
// file.language,
// monaco.Uri.parse(`file:///work/c/${file.name}`)
// )
// )
// }
// create the web socket
if (!subscriptionRef.current) {
monaco.languages.register({
id: 'c',
extensions: ['.c', '.h'],
aliases: ['C', 'c', 'H', 'h'],
mimetypes: ['text/plain']
})
monaco.languages.register({
id: 'text',
extensions: ['.txt'],
mimetypes: ['text/plain'],
})
MonacoServices.install(monaco)
const webSocket = createWebSocket(
process.env.NEXT_PUBLIC_LANGUAGE_SERVER_API_ENDPOINT || ''
)
subscriptionRef.current = webSocket
// listen when the web socket is opened
listen({
webSocket: webSocket as WebSocket,
onConnection: connection => {
// create and start the language client
const languageClient = createLanguageClient(connection)
const disposable = languageClient.start()
connection.onClose(() => {
try {
disposable.dispose()
} catch (err) {
console.log('err', err)
}
})
}
})
}
// editor.updateOptions({
// minimap: {
// enabled: false,
// },
// ...snap.editorSettings,
// });
if (!state.editorCtx) {
state.editorCtx = ref(monaco.editor)
}
}}
onMount={(editor, monaco) => {
editorRef.current = editor
monacoRef.current = monaco
editor.updateOptions({
glyphMargin: true,
lightbulb: {
enabled: true
}
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
saveFile()
})
// When the markers (errors/warnings from clangd language server) change
// Lets improve the markers by adding extra content to them from related
// md files
monaco.editor.onDidChangeMarkers(() => {
if (monacoRef.current) {
setMarkers(monacoRef.current)
}
})
// Hacky way to hide Peek menu
editor.onContextMenu(e => {
const host = document.querySelector<HTMLElement>('.shadow-root-host')
const contextMenuItems = host?.shadowRoot?.querySelectorAll('li.action-item')
contextMenuItems?.forEach(k => {
// If menu item contains "Peek" lets hide it
if (k.querySelector('.action-label')?.textContent === 'Peek') {
// @ts-expect-error
k['style'].display = 'none'
}
})
})
validateWritability(editor)
}}
theme={theme === 'dark' ? 'dark' : 'light'}
/>
)
) : (
<Container>
{!snap.loading && router.isReady && (
<Box
css={{ pl: "0px", pt: "15px", flex: 1, display: "inline-flex" }}
css={{
flexDirection: 'row',
width: '$spaces$wide',
gap: '$3',
display: 'inline-flex'
}}
>
<Text
css={{
fontSize: "14px",
maxWidth: "220px",
fontFamily: "$monospace",
}}
>
Click the link above to create a your file (until we get the
fancy modal, where you can select example files 😊)
</Text>
<Box css={{ display: 'inline-flex', pl: '35px' }}>
<ArrowBendLeftUp size={30} />
</Box>
<Box css={{ pl: '0px', pt: '15px', flex: 1, display: 'inline-flex' }}>
<Text
css={{
fontSize: '14px',
maxWidth: '220px',
fontFamily: '$monospace'
}}
>
Click the link above to create your file
</Text>
</Box>
</Box>
</Box>
)}
</Container>
)}
<Button
variant="primary"
uppercase
disabled={!snap.files.length}
isLoading={snap.compiling}
onClick={() => compileCode(snap.active)}
css={{
position: "absolute",
bottom: "$4",
left: "$4",
alignItems: "center",
display: "flex",
cursor: "pointer",
}}
>
<Play weight="bold" size="16px" />
Compile to Wasm
</Button>
</Box>
);
};
)
}
export default HooksEditor;
export default HooksEditor
+122 -96
View File
@@ -1,139 +1,165 @@
import { styled } from "../stitches.config";
import React from 'react'
import { styled } from '../stitches.config'
import * as LabelPrim from '@radix-ui/react-label'
export const Input = styled("input", {
export const Input = styled('input', {
// Reset
appearance: "none",
borderWidth: "0",
boxSizing: "border-box",
fontFamily: "inherit",
outline: "none",
width: "100%",
flex: "1",
backgroundColor: "$mauve4",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "$sm",
px: "$2",
fontSize: "$md",
appearance: 'none',
borderWidth: '0',
boxSizing: 'border-box',
fontFamily: 'inherit',
outline: 'none',
width: '100%',
flex: '1',
backgroundColor: '$mauve4',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '$sm',
px: '$2',
fontSize: '$md',
lineHeight: 1,
color: "$mauve12",
color: '$mauve12',
boxShadow: `0 0 0 1px $colors$mauve8`,
height: 35,
WebkitTapHighlightColor: "rgba(0,0,0,0)",
"&::before": {
boxSizing: "border-box",
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
'&::before': {
boxSizing: 'border-box'
},
"&::after": {
boxSizing: "border-box",
'&::after': {
boxSizing: 'border-box'
},
fontVariantNumeric: "tabular-nums",
fontVariantNumeric: 'tabular-nums',
"&:-webkit-autofill": {
boxShadow: "inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3",
'&:-webkit-autofill': {
boxShadow: 'inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3'
},
"&:-webkit-autofill::first-line": {
fontFamily: "$untitled",
color: "$mauve12",
'&:-webkit-autofill::first-line': {
fontFamily: '$untitled',
color: '$mauve12'
},
"&:focus": {
'&:focus': {
boxShadow: `0 0 0 1px $colors$mauve10`,
"&:-webkit-autofill": {
boxShadow: `0 0 0 1px $colors$mauve10`,
},
'&:-webkit-autofill': {
boxShadow: `0 0 0 1px $colors$mauve10`
}
},
"&::placeholder": {
color: "$mauve9",
'&::placeholder': {
color: '$mauve9'
},
"&:disabled": {
pointerEvents: "none",
backgroundColor: "$mauve2",
color: "$mauve8",
cursor: "not-allowed",
"&::placeholder": {
color: "$mauve7",
},
'&:disabled': {
pointerEvents: 'none',
backgroundColor: '$mauve2',
color: '$mauve8',
cursor: 'not-allowed',
'&::placeholder': {
color: '$mauve7'
}
},
"&:read-only": {
backgroundColor: "$mauve2",
"&:focus": {
boxShadow: "inset 0px 0px 0px 1px $colors$mauve7",
},
'&:read-only': {
backgroundColor: '$mauve2',
color: '$text',
opacity: 0.8,
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$mauve7'
}
},
variants: {
size: {
sm: {
height: '$5',
fontSize: '$1',
lineHeight: '$sizes$4',
'&:-webkit-autofill::first-line': {
fontSize: '$1'
}
},
md: {
height: "$8",
fontSize: "$1",
lineHeight: "$sizes$5",
"&:-webkit-autofill::first-line": {
fontSize: "$1",
},
height: '$8',
fontSize: '$1',
lineHeight: '$sizes$5',
'&:-webkit-autofill::first-line': {
fontSize: '$1'
}
},
lg: {
height: "$12",
fontSize: "$2",
lineHeight: "$sizes$6",
"&:-webkit-autofill::first-line": {
fontSize: "$3",
},
},
height: '$12',
fontSize: '$2',
lineHeight: '$sizes$6',
'&:-webkit-autofill::first-line': {
fontSize: '$3'
}
}
},
variant: {
ghost: {
boxShadow: "none",
backgroundColor: "transparent",
"@hover": {
"&:hover": {
boxShadow: "inset 0 0 0 1px $colors$mauve7",
},
boxShadow: 'none',
backgroundColor: 'transparent',
'@hover': {
'&:hover': {
boxShadow: 'inset 0 0 0 1px $colors$mauve7'
}
},
"&:focus": {
backgroundColor: "$loContrast",
boxShadow: `0 0 0 1px $colors$mauve10`,
'&:focus': {
backgroundColor: '$loContrast',
boxShadow: `0 0 0 1px $colors$mauve10`
},
"&:disabled": {
backgroundColor: "transparent",
},
"&:read-only": {
backgroundColor: "transparent",
'&:disabled': {
backgroundColor: 'transparent'
},
'&:read-only': {
backgroundColor: 'transparent'
}
},
deep: {
backgroundColor: '$deep',
boxShadow: 'none'
}
},
state: {
invalid: {
boxShadow: "inset 0 0 0 1px $colors$red7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
},
boxShadow: 'inset 0 0 0 1px $colors$crimson7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8'
}
},
valid: {
boxShadow: "inset 0 0 0 1px $colors$green7",
"&:focus": {
boxShadow:
"inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
},
},
boxShadow: 'inset 0 0 0 1px $colors$grass7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8'
}
}
},
cursor: {
default: {
cursor: "default",
"&:focus": {
cursor: "text",
},
cursor: 'default',
'&:focus': {
cursor: 'text'
}
},
text: {
cursor: "text",
},
},
cursor: 'text'
}
}
},
defaultVariants: {
size: "md",
},
});
size: 'md'
}
})
export default Input;
// eslint-disable-next-line react/display-name
const ReffedInput = React.forwardRef<HTMLInputElement, React.ComponentProps<typeof Input>>(
(props, ref) => <Input {...props} ref={ref} />
)
export default ReffedInput
const LabelRoot = (props: LabelPrim.LabelProps) => <LabelPrim.Root {...props} />
export const Label = styled(LabelRoot, {
display: 'inline-block',
mb: '$1'
})
+16
View File
@@ -0,0 +1,16 @@
import { styled } from '../stitches.config'
const StyledLink = styled('a', {
color: 'CurrentColor',
textDecoration: 'underline',
cursor: 'pointer',
variants: {
highlighted: {
true: {
color: '$blue9'
}
}
}
})
export default StyledLink
+178
View File
@@ -0,0 +1,178 @@
import { useRef, useLayoutEffect, ReactNode, FC, useState } from 'react'
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
import useStayScrolled from 'react-stay-scrolled'
import NextLink from 'next/link'
import Container from './Container'
import LogText from './LogText'
import { ILog } from '../state'
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
interface ILogBox {
title: string
clearLog?: () => void
logs: ILog[]
renderNav?: () => ReactNode
enhanced?: boolean
Icon?: FC<IconProps>
}
const LogBox: FC<ILogBox> = ({
title,
clearLog,
logs,
children,
renderNav,
enhanced,
Icon = Notepad
}) => {
const logRef = useRef<HTMLPreElement>(null)
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef)
useLayoutEffect(() => {
stayScrolled()
}, [stayScrolled, logs])
return (
<Flex
as="div"
css={{
display: 'flex',
borderTop: '1px solid $mauve6',
background: '$mauve1',
position: 'relative',
flex: 1,
height: '100%'
}}
>
<Container
css={{
px: 0,
height: '100%'
}}
>
<Flex
fluid
css={{
height: '48px',
alignItems: 'center',
fontSize: '$sm',
fontWeight: 300
}}
>
<Heading
as="h3"
css={{
fontWeight: 300,
m: 0,
fontSize: '11px',
color: '$mauve12',
px: '$3',
textTransform: 'uppercase',
alignItems: 'center',
display: 'inline-flex',
gap: '$3'
}}
>
<Icon size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
</Heading>
<Flex
row
align="center"
// css={{
// maxWidth: "100%", // TODO make it max without breaking layout!
// }}
>
{renderNav?.()}
</Flex>
<Flex css={{ ml: 'auto', gap: '$3', marginRight: '$3' }}>
{clearLog && (
<Button ghost size="xs" onClick={clearLog}>
<Prohibit size="14px" />
</Button>
)}
</Flex>
</Flex>
<Box
as="pre"
ref={logRef}
css={{
margin: 0,
// display: "inline-block",
display: 'flex',
flexDirection: 'column',
width: '100%',
height: 'calc(100% - 48px)', // 100% minus the logbox header height
overflowY: 'auto',
fontSize: '13px',
fontWeight: '$body',
fontFamily: '$monospace',
px: '$3',
pb: '$2',
whiteSpace: 'normal'
}}
>
{logs?.map((log, index) => (
<Box
as="span"
key={log.type + index}
css={{
'@hover': {
'&:hover': {
backgroundColor: enhanced ? '$backgroundAlt' : undefined
}
},
p: enhanced ? '$1' : undefined,
my: enhanced ? '$1' : undefined
}}
>
<Log {...log} />
</Box>
))}
{children}
</Box>
</Container>
</Flex>
)
}
export const Log: FC<ILog> = ({
type,
timestring,
message,
link,
linkText,
defaultCollapsed,
jsonData
}) => {
const [expanded, setExpanded] = useState(!defaultCollapsed)
if (message === undefined) message = <Text muted>{'undefined'}</Text>
else if (message === '') message = <Text muted>{'""'}</Text>
return (
<>
<LogText variant={type}>
{timestring && (
<Text muted monospace>
{timestring}{' '}
</Text>
)}
<Pre>{message}</Pre>
{link && (
<NextLink href={link} shallow passHref>
<Link as="a">{linkText}</Link>
</NextLink>
)}
{jsonData && (
<Link onClick={() => setExpanded(!expanded)} as="a">
{expanded ? 'Collapse' : 'Expand'}
</Link>
)}
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
</LogText>
</>
)
}
export default LogBox
+18 -14
View File
@@ -1,27 +1,31 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Text = styled("span", {
fontFamily: "$monospace",
lineHeight: "$body",
color: "$text",
const Text = styled('span', {
fontFamily: '$monospace',
lineHeight: '$body',
color: '$text',
wordWrap: 'break-word',
variants: {
variant: {
log: {
color: "$text",
color: '$text'
},
warning: {
color: "$yellow11",
color: '$warning'
},
error: {
color: "$red11",
color: '$error'
},
success: {
color: '$success'
}
},
capitalize: {
true: {
textTransform: "capitalize",
},
},
},
});
textTransform: 'capitalize'
}
}
}
})
export default Text;
export default Text
+15 -26
View File
@@ -1,39 +1,28 @@
import { useTheme } from "next-themes";
import { styled } from '../stitches.config'
import { styled } from "../stitches.config";
const SVG = styled("svg", {
"& #path-one, & #path-two": {
fill: "$text",
},
});
function Logo({
width,
height,
}: {
width?: string | number;
height?: string | number;
}) {
const SVG = styled('svg', {
'& #path': {
fill: '$accent'
}
})
function Logo({ width, height }: { width?: string | number; height?: string | number }) {
return (
<SVG
width={width || "1em"}
height={height || "1em"}
viewBox="0 0 28 22"
width={width || '1.1em'}
height={height || '1.1em'}
viewBox="0 0 294 283"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="path-one"
d="M19.603 3.87h2.3l-4.786 4.747a4.466 4.466 0 01-6.276 0L6.054 3.871h2.3l3.636 3.605a2.828 2.828 0 003.975 0l3.638-3.605zM8.325 17.069h-2.3l4.816-4.776a4.466 4.466 0 016.276 0l4.816 4.776h-2.3l-3.665-3.635a2.828 2.828 0 00-3.975 0l-3.668 3.635z"
/>
<path
id="path-two"
fillRule="evenodd"
clipRule="evenodd"
d="M1.556 9.769h4.751v1.555H1.556v10.072H0V0h1.556v9.769zM26.444 9.769h-4.751v1.555h4.751v10.072H28V0h-1.556v9.769z"
id="path"
d="M265.827 235L172.416 141.589L265.005 49H226.822L147.732 128.089H53.5514L27.4824 155.089H147.732L227.643 235H265.827Z"
fill="#9D2DFF"
/>
</SVG>
);
)
}
export default Logo;
export default Logo
+14
View File
@@ -0,0 +1,14 @@
import ReactMarkdown from 'react-markdown'
import { styled } from '../stitches.config'
const Markdown = styled(ReactMarkdown, {
px: '$8',
'@md': {
px: '$20'
},
pb: '$5',
height: '100%',
overflowY: 'auto'
})
export default Markdown
+71
View File
@@ -0,0 +1,71 @@
import Editor, { loader, EditorProps, Monaco } from '@monaco-editor/react'
import { CSS } from '@stitches/react'
import type monaco from 'monaco-editor'
import { useTheme } from 'next-themes'
import { FC, MutableRefObject, ReactNode } from 'react'
import { Flex } from '.'
import dark from '../theme/editor/amy.json'
import light from '../theme/editor/xcode_default.json'
export type MonacoProps = EditorProps & {
id?: string
rootProps?: { css: CSS } & Record<string, any>
overlay?: ReactNode
editorRef?: MutableRefObject<monaco.editor.IStandaloneCodeEditor>
monacoRef?: MutableRefObject<typeof monaco>
}
loader.config({
paths: {
vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs'
}
})
const Monaco: FC<MonacoProps> = ({
id,
path = `file:///${id}`,
className = id,
language = 'json',
overlay,
editorRef,
monacoRef,
beforeMount,
rootProps,
...rest
}) => {
const { theme } = useTheme()
const setTheme = (monaco: Monaco) => {
monaco.editor.defineTheme('dark', dark as any)
monaco.editor.defineTheme('light', light as any)
}
return (
<Flex
fluid
column
{...rootProps}
css={{
position: 'relative',
height: '100%',
...rootProps?.css
}}
>
<Editor
className={className}
language={language}
path={path}
beforeMount={monaco => {
beforeMount?.(monaco)
setTheme(monaco)
}}
theme={theme === 'dark' ? 'dark' : 'light'}
{...rest}
/>
{overlay && (
<Flex css={{ position: 'absolute', bottom: 0, right: 0, width: '100%' }}>{overlay}</Flex>
)}
</Flex>
)
}
export default Monaco
+340 -165
View File
@@ -1,194 +1,369 @@
import React from "react";
import Link from "next/link";
import {
Gear,
GithubLogo,
SignOut,
User,
ArrowSquareOut,
} from "phosphor-react";
import { useSnapshot } from "valtio";
import Image from "next/image";
import { useSession, signIn, signOut } from "next-auth/react";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuArrow,
DropdownMenuSeparator,
} from "./DropdownMenu";
import React from 'react'
import Link from 'next/link'
import Stack from "./Stack";
import Logo from "./Logo";
import Button from "./Button";
import Flex from "./Flex";
import Container from "./Container";
import Box from "./Box";
import ThemeChanger from "./ThemeChanger";
import { useRouter } from "next/router";
import { useSnapshot } from 'valtio'
import { useRouter } from 'next/router'
import { FolderOpen, X, ArrowUpRight, BookOpen } from 'phosphor-react'
import Stack from './Stack'
import Logo from './Logo'
import Button from './Button'
import Flex from './Flex'
import Container from './Container'
import Box from './Box'
import ThemeChanger from './ThemeChanger'
import state from '../state'
import Heading from './Heading'
import Text from './Text'
import Spinner from './Spinner'
import truncate from '../utils/truncate'
import ButtonGroup from './ButtonGroup'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger
} from './Dialog'
import PanelBox from './PanelBox'
import { templateFileIds } from '../state/constants'
import { styled } from '../stitches.config'
const ImageWrapper = styled(Flex, {
position: 'relative',
mt: '$2',
mb: '$10',
svg: {
// fill: "red",
'.angle': {
fill: '$text'
},
':not(.angle)': {
stroke: '$text'
}
}
})
const Navigation = () => {
const { data: session, status } = useSession();
const router = useRouter();
const router = useRouter()
const snap = useSnapshot(state)
const slug = router.query?.slug
const gistId = Array.isArray(slug) ? slug[0] : null
return (
<Box
as="nav"
css={{
display: "flex",
height: "60px",
borderBottom: "1px solid $mauve6",
position: "relative",
display: 'flex',
backgroundColor: '$mauve1',
borderBottom: '1px solid $mauve6',
position: 'relative',
zIndex: 2003,
height: '60px'
}}
>
<Container
css={{
display: "flex",
alignItems: "center",
py: "$2",
display: 'flex',
alignItems: 'center'
}}
>
<Link href="/" passHref>
<Box
as="a"
css={{
display: "flex",
alignItems: "center",
color: "$textColor",
}}
>
<Logo width="30px" height="30px" />
</Box>
</Link>
<Flex
css={{
flex: 1,
flexWrap: "nowrap",
marginLeft: "$3",
overflowX: "scroll",
alignItems: 'center',
borderRight: '1px solid $colors$mauve6',
py: '$3',
pr: '$4'
}}
>
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref>
<Box
as="a"
css={{
display: 'flex',
alignItems: 'center',
color: '$textColor'
}}
>
<Logo width="32px" height="32px" />
</Box>
</Link>
<Flex
css={{
ml: '$5',
flexDirection: 'column',
gap: '1px'
}}
>
{snap.loading ? (
<Spinner />
) : (
<>
<Heading css={{ lineHeight: 1 }}>{snap.gistName || 'XRPL Hooks'}</Heading>
<Text css={{ fontSize: '$xs', color: '$mauve10', lineHeight: 1 }}>
{snap.files.length > 0 ? 'Gist: ' : 'Builder'}
{snap.files.length > 0 && (
<Link
href={`https://gist.github.com/${snap.gistOwner || ''}/${snap.gistId || ''}`}
passHref
>
<Text
as="a"
target="_blank"
rel="noreferrer noopener"
css={{ color: '$mauve12' }}
>
{`${snap.gistOwner || '-'}/${truncate(snap.gistId || '')}`}
</Text>
</Link>
)}
</Text>
</>
)}
</Flex>
{router.isReady && (
<ButtonGroup css={{ marginLeft: 'auto' }}>
<Dialog open={snap.mainModalOpen} onOpenChange={open => (state.mainModalOpen = open)}>
<DialogTrigger asChild>
<Button outline>
<FolderOpen size="15px" />
</Button>
</DialogTrigger>
<DialogContent
css={{
display: 'flex',
maxWidth: '1080px',
width: '80vw',
maxHeight: '80%',
backgroundColor: '$mauve1 !important',
overflowY: 'auto',
background: 'black',
p: 0
}}
>
<Flex
css={{
flexDirection: 'column',
height: '100%',
'@md': {
flexDirection: 'row',
height: '100%'
}
}}
>
<Flex
css={{
borderBottom: '1px solid $colors$mauve5',
width: '100%',
minWidth: '240px',
flexDirection: 'column',
p: '$7',
backgroundColor: '$mauve2',
'@md': {
width: '30%',
maxWidth: '300px',
borderBottom: '0px',
borderRight: '1px solid $colors$mauve5'
}
}}
>
<DialogTitle
css={{
textTransform: 'uppercase',
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
fontSize: '$xl',
lineHeight: '$one',
fontWeight: '$bold'
}}
>
<Logo width="48px" height="48px" /> XRPL Hooks Builder
</DialogTitle>
<DialogDescription as="div">
<Text
css={{
display: 'inline-flex',
color: 'inherit',
my: '$5',
mb: '$7'
}}
>
Hooks add smart contract functionality to the XRP Ledger.
</Text>
<Flex css={{ flexDirection: 'column', gap: '$2', mt: '$2' }}>
<Text
css={{
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
target="_blank"
href="https://github.com/XRPL-Labs/xrpld-hooks"
>
<ArrowUpRight size="15px" /> Hooks Github
</Text>
<Text
css={{
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
target="_blank"
href="https://xrpl-hooks.readme.io/v2.0/docs"
>
<ArrowUpRight size="15px" /> Hooks documentation
</Text>
<Text
css={{
display: 'inline-flex',
alignItems: 'center',
gap: '$3',
color: '$purple11',
'&:hover': {
color: '$purple12'
},
'&:focus': {
outline: 0
}
}}
as="a"
rel="noreferrer noopener"
target="_blank"
href="https://xrpl.org/docs.html"
>
<ArrowUpRight size="15px" /> XRPL documentation
</Text>
</Flex>
</DialogDescription>
</Flex>
<Flex
css={{
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplateRows: 'max-content',
flex: 1,
p: '$7',
pb: '$16',
gap: '$3',
alignItems: 'normal',
flexWrap: 'wrap',
backgroundColor: '$mauve1',
'@md': {
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: 'max-content'
},
'@lg': {
gridTemplateColumns: '1fr 1fr 1fr',
gridTemplateRows: 'max-content'
}
}}
>
{Object.values(templateFileIds).map(template => (
<PanelBox key={template.id} as="a" href={`/develop/${template.id}`}>
<ImageWrapper>{template.icon()}</ImageWrapper>
<Heading>{template.name}</Heading>
<Text>{template.description}</Text>
</PanelBox>
))}
</Flex>
</Flex>
<DialogClose asChild>
<Box
css={{
position: 'absolute',
top: '$1',
right: '$1',
cursor: 'pointer',
background: '$mauve1',
display: 'flex',
borderRadius: '$full',
p: '$1'
}}
>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
<ThemeChanger />
</ButtonGroup>
)}
</Flex>
<Flex
css={{
flexWrap: 'nowrap',
marginLeft: '$4',
overflowX: 'scroll',
'&::-webkit-scrollbar': {
height: 0,
background: 'transparent'
},
scrollbarColor: 'transparent',
scrollbarWidth: 'none'
}}
>
<Stack
css={{
ml: "$4",
gap: "$3",
flexWrap: "nowrap",
alignItems: "center",
pr: "$3",
ml: '$4',
gap: '$3',
flexWrap: 'nowrap',
alignItems: 'center',
marginLeft: 'auto'
}}
>
<Link href="/develop" passHref shallow>
<Button
as="a"
outline={!router.pathname.includes("/develop")}
uppercase
>
Develop
</Button>
<ButtonGroup>
<Link href={gistId ? `/develop/${gistId}` : '/develop'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/develop')} uppercase>
Develop
</Button>
</Link>
<Link href={gistId ? `/deploy/${gistId}` : '/deploy'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/deploy')} uppercase>
Deploy
</Button>
</Link>
<Link href={gistId ? `/test/${gistId}` : '/test'} passHref shallow>
<Button as="a" outline={!router.pathname.includes('/test')} uppercase>
Test
</Button>
</Link>
</ButtonGroup>
<Link href="https://xrpl-hooks.readme.io/v2.0" passHref>
<a target="_blank" rel="noreferrer noopener">
<Button outline>
<BookOpen size="15px" />
</Button>
</a>
</Link>
<Link href="/deploy" passHref shallow>
<Button
as="a"
outline={!router.pathname.includes("/deploy")}
uppercase
>
Deploy
</Button>
</Link>
<Link href="/test" passHref shallow>
<Button
as="a"
outline={!router.pathname.includes("/test")}
uppercase
>
Test
</Button>
</Link>
<ThemeChanger />
</Stack>
</Flex>
<Stack
css={{
color: "text",
ml: "auto",
flexWrap: "nowrap",
marginLeft: "$3",
"@sm": {
marginLeft: "auto",
},
}}
>
{status === "authenticated" && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Box
css={{
borderRadius: "$full",
overflow: "hidden",
width: "30px",
height: "30px",
position: "relative",
}}
>
<Image
src={session?.user?.image || ""}
width="30px"
height="30px"
objectFit="cover"
alt="User avatar"
/>
</Box>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled onClick={() => signOut()}>
<User size="16px" /> {session?.user?.username} (
{session?.user.name})
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
window.open(
`http://gist.github.com/${session?.user.username}`
)
}
>
<ArrowSquareOut size="16px" />
Go to your Gist
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Gear size="16px" /> Settings
</DropdownMenuItem>
<DropdownMenuItem onClick={() => signOut()}>
<SignOut size="16px" /> Log out
</DropdownMenuItem>
<DropdownMenuArrow offset={10} />
</DropdownMenuContent>
</DropdownMenu>
)}
{status !== "authenticated" && (
<Button
isLoading={status === "loading"}
outline
onClick={() => signIn("github")}
>
<GithubLogo size="16px" /> GitHub Login
</Button>
)}
{/* <Box
css={{
border: "1px solid",
borderRadius: "3px",
borderColor: "text",
p: 1,
}}
>
<BookOpen size="20px" />
</Box> */}
</Stack>
</Container>
</Box>
);
};
)
}
export default Navigation;
export default Navigation
+30
View File
@@ -0,0 +1,30 @@
import { styled } from '../stitches.config'
import Heading from './Heading'
import Text from './Text'
const PanelBox = styled('div', {
display: 'flex',
flexDirection: 'column',
border: '1px solid $colors$mauve6',
backgroundColor: '$mauve2',
padding: '$3',
borderRadius: '$sm',
fontWeight: 'lighter',
height: 'auto',
cursor: 'pointer',
flex: '1 1 0px',
'&:hover': {
border: '1px solid $colors$mauve9'
},
[`& ${Heading}`]: {
fontWeight: 'lighter',
mb: '$2'
},
[`& ${Text}`]: {
fontWeight: 'lighter',
color: '$mauve10',
fontSize: '$sm'
}
})
export default PanelBox
+102
View File
@@ -0,0 +1,102 @@
import React, { ReactNode } from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { styled, keyframes } from '../stitches.config'
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const StyledContent = styled(PopoverPrimitive.Content, {
borderRadius: 4,
padding: '$3 $3',
fontSize: 12,
lineHeight: 1,
color: '$text',
backgroundColor: '$background',
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
'.dark &': {
backgroundColor: '$mauve5',
boxShadow: '0px 5px 38px -2px rgba(22, 23, 24, 1), 0px 10px 20px 0px rgba(22, 23, 24, 1)'
}
})
const StyledArrow = styled(PopoverPrimitive.Arrow, {
fill: '$colors$mauve2',
'.dark &': {
fill: '$mauve5'
}
})
const StyledClose = styled(PopoverPrimitive.Close, {
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 25,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: '$text',
position: 'absolute',
top: 5,
right: 5
})
// Exports
export const PopoverRoot = PopoverPrimitive.Root
export const PopoverTrigger = PopoverPrimitive.Trigger
export const PopoverContent = StyledContent
export const PopoverArrow = StyledArrow
export const PopoverClose = StyledClose
interface IPopover {
content: string | ReactNode
open?: boolean
defaultOpen?: boolean
onOpenChange?: (open: boolean) => void
}
const Popover: React.FC<IPopover & React.ComponentProps<typeof PopoverContent>> = ({
children,
content,
open,
defaultOpen = false,
onOpenChange,
...rest
}) => (
<PopoverRoot open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent sideOffset={5} {...rest}>
{content} <PopoverArrow offset={5} className="arrow" />
</PopoverContent>
</PopoverRoot>
)
export default Popover
+27
View File
@@ -0,0 +1,27 @@
import { styled } from '../stitches.config'
const Pre = styled('span', {
m: 0,
wordBreak: 'break-all',
fontFamily: '$monospace',
whiteSpace: 'pre-wrap',
variants: {
fluid: {
true: {
width: '100%'
}
},
line: {
true: {
whiteSpace: 'pre-line'
}
},
block: {
true: {
display: 'block'
}
}
}
})
export default Pre
+24
View File
@@ -0,0 +1,24 @@
import { FC } from 'react'
import { Link } from '.'
interface Props {
result?: string
}
const ResultLink: FC<Props> = ({ result }) => {
if (!result) return null
let href: string
if (result === 'tesSUCCESS') {
href = 'https://xrpl.org/tes-success.html'
} else {
// Going shortcut here because of url structure, if that changes we will do it manually
href = `https://xrpl.org/${result.slice(0, 3)}-codes.html`
}
return (
<Link as="a" href={href} target="_blank" rel="noopener noreferrer">
{result}
</Link>
)
}
export default ResultLink
+333
View File
@@ -0,0 +1,333 @@
import { Play, X } from 'phosphor-react'
import { HTMLInputTypeAttribute, useCallback, useEffect, useState } from 'react'
import state, { IAccount, IFile, ILog } from '../../state'
import Button from '../Button'
import Box from '../Box'
import Input, { Label } from '../Input'
import Stack from '../Stack'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose
} from '../Dialog'
import Flex from '../Flex'
import { useSnapshot } from 'valtio'
import Select from '../Select'
import Text from '../Text'
import { saveFile } from '../../state/actions/saveFile'
import { getErrors, getTags } from '../../utils/comment-parser'
import toast from 'react-hot-toast'
const generateHtmlTemplate = async (code: string, data?: Record<string, any>) => {
let processString: string | undefined
const process = { env: { NODE_ENV: 'production' } } as any
if (data) {
Object.keys(data).forEach(key => {
process.env[key] = data[key]
})
}
processString = JSON.stringify(process)
const libs = (await import("xrpl-accountlib/dist/browser.hook-bundle.js")).default;
return `
<html>
<head>
<script>
var log = console.log;
var errorLog = console.error;
var infoLog = console.info;
var warnLog = console.warn
console.log = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'log', args: args || [] }, '*');
log.apply(console, args);
}
console.error = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'error', args: args || [] }, '*');
errorLog.apply(console, args);
}
console.info = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'info', args: args || [] }, '*');
infoLog.apply(console, args);
}
console.warn = function(){
var args = Array.from(arguments);
parent.window.postMessage({ type: 'warning', args: args || [] }, '*');
warnLog.apply(console, args);
}
var process = '${processString || '{}'}';
process = JSON.parse(process);
window.process = process
function windowErrorHandler(event) {
event.preventDefault() // to prevent automatically logging to console
console.error(event.error?.toString())
}
window.addEventListener('error', windowErrorHandler);
</script>
<script>
${libs}
</script>
<script type="module">
${code}
</script>
</head>
<body>
</body>
</html>
`
}
type Fields = Record<
string,
{
name: string
value: string
type?: 'Account' | `Account.${keyof IAccount}` | HTMLInputTypeAttribute
description?: string
required?: boolean
}
>
const RunScript: React.FC<{ file: IFile }> = ({ file: { content, name } }) => {
const snap = useSnapshot(state)
const [templateError, setTemplateError] = useState('')
const [fields, setFields] = useState<Fields>({})
const [iFrameCode, setIframeCode] = useState('')
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const getFields = useCallback(() => {
const inputTags = ['input', 'param', 'arg', 'argument']
const tags = getTags(content)
.filter(tag => inputTags.includes(tag.tag))
.filter(tag => !!tag.name)
let _fields = tags.map(tag => ({
name: tag.name,
value: tag.default || '',
type: tag.type,
description: tag.description,
required: !tag.optional
}))
const fields: Fields = _fields.reduce((acc, field) => {
acc[field.name] = field
return acc
}, {} as Fields)
const error = getErrors(content)
if (error) setTemplateError(error.message)
else setTemplateError('')
return fields
}, [content])
const runScript = useCallback(async () => {
setIsLoading(true);
try {
// Show loading toast only after 1 second, otherwise skip it.
let loaded = false
let toastId: string | undefined;
setTimeout(() => {
if (!loaded) {
toastId = toast.loading('Loading packages, may take a few seconds...', {
position: 'bottom-center',
})
}
}, 1000)
let data: any = {}
Object.keys(fields).forEach(key => {
data[key] = fields[key].value
})
const template = await generateHtmlTemplate(content, data)
setIframeCode(template)
loaded = true
if (toastId) {
toast.dismiss(toastId)
}
state.scriptLogs = [{ type: 'success', message: 'Started running...' }]
} catch (err) {
state.scriptLogs = [
...snap.scriptLogs,
// @ts-expect-error
{ type: 'error', message: err?.message || 'Could not parse template' }
]
}
setIsLoading(false);
}, [content, fields, snap.scriptLogs])
useEffect(() => {
const handleEvent = (e: any) => {
if (e.data.type === 'log' || e.data.type === 'error') {
const data: ILog[] = e.data.args.map((msg: any) => ({
type: e.data.type,
message: typeof msg === 'string' ? msg : JSON.stringify(msg, null, 2)
}))
state.scriptLogs = [...snap.scriptLogs, ...data]
}
}
window.addEventListener('message', handleEvent)
return () => window.removeEventListener('message', handleEvent)
}, [snap.scriptLogs])
useEffect(() => {
const defaultFields = getFields() || {}
setFields(defaultFields)
}, [content, setFields, getFields])
const accOptions = snap.accounts?.map(acc => ({
...acc,
label: acc.name,
value: acc.address
}))
const isDisabled = Object.values(fields).some(field => field.required && !field.value)
const handleRun = useCallback(async () => {
if (isDisabled) return toast.error('Please fill in all the required fields.')
state.scriptLogs = []
await runScript();
setIsDialogOpen(false)
}, [isDisabled, runScript])
return (
<>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button
variant="primary"
onClick={() => {
saveFile(false)
setIframeCode('')
}}
>
<Play weight="bold" size="16px" /> {name}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Run {name} script</DialogTitle>
<DialogDescription>
<Box>
You are about to run scripts provided by the developer of the hook, make sure you
trust the author before you continue.
</Box>
{templateError && (
<Box
as="span"
css={{
display: 'block',
color: '$error',
mt: '$3',
whiteSpace: 'pre'
}}
>
{templateError}
</Box>
)}
{Object.keys(fields).length > 0 && (
<Box css={{ mt: '$4', mb: 0 }}>
Fill in the following parameters to run the script.
</Box>
)}
</DialogDescription>
<Stack css={{ width: '100%' }}>
{Object.keys(fields).map(key => {
const { name, value, type, description, required } = fields[key]
const isAccount = type?.startsWith('Account')
const isAccountSecret = type === 'Account.secret'
const accountField = (isAccount && type?.split('.')[1]) || 'address'
return (
<Box key={name} css={{ width: '100%' }}>
<Label css={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
{description || name} {required && <Text error>*</Text>}
</span>
{isAccountSecret && (
<Text error small css={{ alignSelf: 'end' }}>
can access account secret key
</Text>
)}
</Label>
{isAccount ? (
<Select
css={{ mt: '$1' }}
options={accOptions}
onChange={(val: any) => {
setFields({
...fields,
[key]: {
...fields[key],
value: val[accountField]
}
})
}}
value={accOptions.find((acc: any) => acc[accountField] === value)}
/>
) : (
<Input
type={type || 'text'}
value={value}
css={{ mt: '$1' }}
onChange={e => {
setFields({
...fields,
[key]: { ...fields[key], value: e.target.value }
})
}}
/>
)}
</Box>
)
})}
<Flex css={{ justifyContent: 'flex-end', width: '100%', gap: '$3' }}>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button variant="primary" isDisabled={isDisabled || isLoading} isLoading={isLoading} onClick={handleRun}>
Run script
</Button>
</Flex>
</Stack>
<DialogClose asChild>
<Box
css={{
position: 'absolute',
top: '$1',
right: '$1',
cursor: 'pointer',
background: '$mauve1',
display: 'flex',
borderRadius: '$full',
p: '$1'
}}
>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
{iFrameCode && (
<iframe style={{ display: 'none' }} srcDoc={iFrameCode} sandbox="allow-scripts" />
)}
</>
)
}
export default RunScript
+163
View File
@@ -0,0 +1,163 @@
import { forwardRef } from 'react'
import { mauve, mauveDark, purple, purpleDark } from '@radix-ui/colors'
import { useTheme } from 'next-themes'
import { styled } from '../stitches.config'
import dynamic from 'next/dynamic'
import type { Props, StylesConfig } from 'react-select'
const SelectInput = dynamic(() => import('react-select'), { ssr: false })
const CreatableSelectInput = dynamic(() => import('react-select/creatable'), { ssr: false })
const getColors = (isDark: boolean) => {
const colors: any = {
// primary: pink.pink9,
active: isDark ? purpleDark.purple9 : purple.purple9,
activeLight: isDark ? purpleDark.purple5 : purple.purple5,
primary: isDark ? mauveDark.mauve4 : mauve.mauve4,
secondary: isDark ? mauveDark.mauve8 : mauve.mauve8,
background: isDark ? mauveDark.mauve4 : mauve.mauve4,
searchText: isDark ? mauveDark.mauve12 : mauve.mauve12,
bg: isDark ? mauveDark.mauve1 : mauve.mauve1,
dropDownBg: isDark ? mauveDark.mauve5 : mauve.mauve2,
mauve4: isDark ? mauveDark.mauve4 : mauve.mauve4,
mauve5: isDark ? mauveDark.mauve5 : mauve.mauve5,
mauve8: isDark ? mauveDark.mauve8 : mauve.mauve8,
mauve9: isDark ? mauveDark.mauve9 : mauve.mauve9,
mauve12: isDark ? mauveDark.mauve12 : mauve.mauve12,
border: isDark ? mauveDark.mauve10 : mauve.mauve10,
placeholder: isDark ? mauveDark.mauve11 : mauve.mauve11
}
colors.outline = colors.background
colors.selected = colors.secondary
return colors
}
const getStyles = (isDark: boolean) => {
const colors = getColors(isDark)
const styles: StylesConfig = {
container: provided => {
return {
...provided,
position: 'relative',
width: '100%'
}
},
singleValue: provided => ({
...provided,
color: colors.mauve12
}),
menu: provided => ({
...provided,
backgroundColor: colors.dropDownBg
}),
control: (provided, state) => {
return {
...provided,
minHeight: 0,
border: '0px',
backgroundColor: colors.mauve4,
boxShadow: `0 0 0 1px ${state.isFocused ? colors.border : colors.secondary}`
}
},
input: provided => {
return {
...provided,
color: '$text'
}
},
multiValue: provided => {
return {
...provided,
backgroundColor: colors.mauve8
}
},
multiValueLabel: provided => {
return {
...provided,
color: colors.mauve12
}
},
multiValueRemove: provided => {
return {
...provided,
':hover': {
background: colors.mauve9
}
}
},
option: (provided, state) => {
return {
...provided,
color: colors.searchText,
backgroundColor: state.isFocused ? colors.activeLight : colors.dropDownBg,
':hover': {
backgroundColor: colors.active,
color: '#ffffff'
},
':selected': {
backgroundColor: 'red'
}
}
},
indicatorSeparator: provided => {
return {
...provided,
backgroundColor: colors.secondary
}
},
dropdownIndicator: (provided, state) => {
return {
...provided,
padding: 6,
color: state.isFocused ? colors.border : colors.secondary,
':hover': {
color: colors.border
}
}
},
clearIndicator: provided => {
return {
...provided,
padding: 6,
color: colors.secondary,
':hover': {
color: colors.border
}
}
}
}
return styles
}
// eslint-disable-next-line react/display-name
const Select = forwardRef<any, Props>((props, ref) => {
const { theme } = useTheme()
const isDark = theme === 'dark'
const styles = getStyles(isDark)
return (
<SelectInput
ref={ref}
menuPosition={props.menuPosition || 'fixed'}
styles={styles}
{...props}
/>
)
})
// eslint-disable-next-line react/display-name
const Creatable = forwardRef<any, Props>((props, ref) => {
const { theme } = useTheme()
const isDark = theme === 'dark'
const styles = getStyles(isDark)
return (
<CreatableSelectInput
ref={ref}
formatCreateLabel={label => `Enter "${label}"`}
menuPosition={props.menuPosition || 'fixed'}
styles={styles}
{...props}
/>
)
})
export default styled(Select, {})
export const CreatableSelect = styled(Creatable, {})
+71
View File
@@ -0,0 +1,71 @@
import { FC, useCallback, useState } from 'react'
import state from '../state'
import { Flex, Input, Button } from '.'
import fetchAccountInfo from '../utils/accountInfo'
import { useSnapshot } from 'valtio'
interface AccountSequenceProps {
address?: string
}
const AccountSequence: FC<AccountSequenceProps> = ({ address }) => {
const { accounts } = useSnapshot(state)
const account = accounts.find(acc => acc.address === address)
const [isLoading, setIsLoading] = useState(false)
const setSequence = useCallback(
(sequence: number) => {
const acc = state.accounts.find(acc => acc.address == address)
if (!acc) return
acc.sequence = sequence
},
[address]
)
const handleUpdateSequence = useCallback(
async (silent?: boolean) => {
if (!account) return
setIsLoading(true)
const info = await fetchAccountInfo(account.address, { silent })
if (info) {
setSequence(info.Sequence)
}
setIsLoading(false)
},
[account, setSequence]
)
const disabled = !account
return (
<Flex row align="center" fluid>
<Input
placeholder="Account sequence"
value={account?.sequence || ""}
disabled={!account}
type="number"
readOnly={true}
/>
<Button
size="xs"
variant="primary"
type="button"
outline
disabled={disabled}
isDisabled={disabled}
isLoading={isLoading}
css={{
background: '$backgroundAlt',
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleUpdateSequence()}
>
Update
</Button>
</Flex>
)
}
export default AccountSequence
+425
View File
@@ -0,0 +1,425 @@
import React, { useCallback, useEffect, useState } from 'react'
import { Plus, Trash, X } from 'phosphor-react'
import { Button, Box, Text } from '.'
import { Stack, Flex, Select } from '.'
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose,
DialogTrigger
} from './Dialog'
import { Input, Label } from './Input'
import { Controller, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'
import { deployHook } from '../state/actions'
import { useSnapshot } from 'valtio'
import state, { IFile, SelectOption } from '../state'
import toast from 'react-hot-toast'
import { prepareDeployHookTx, sha256 } from '../state/actions/deployHook'
import estimateFee from '../utils/estimateFee'
import { getParameters, getInvokeOptions, transactionOptions, SetHookData } from '../utils/setHook'
import { capitalize } from '../utils/helpers'
import AccountSequence from './Sequence'
export const SetHookDialog: React.FC<{ accountAddress: string }> = React.memo(
({ accountAddress }) => {
const snap = useSnapshot(state)
const [estimateLoading, setEstimateLoading] = useState(false)
const [isSetHookDialogOpen, setIsSetHookDialogOpen] = useState(false)
const compiledFiles = snap.files.filter(file => file.compiledContent)
const activeFile = compiledFiles[snap.activeWat] as IFile | undefined
const accountOptions: SelectOption[] = snap.accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const [selectedAccount, setSelectedAccount] = useState(
accountOptions.find(acc => acc.value === accountAddress)
)
const account = snap.accounts.find(acc => acc.address === selectedAccount?.value)
const getHookNamespace = useCallback(
() =>
(activeFile && snap.deployValues[activeFile.name]?.HookNamespace) ||
activeFile?.name.split('.')[0] ||
'',
[activeFile, snap.deployValues]
)
const getDefaultValues = useCallback((): Partial<SetHookData> => {
const content = activeFile?.compiledValueSnapshot
return (
(activeFile && snap.deployValues[activeFile.name]) || {
HookNamespace: getHookNamespace(),
Invoke: getInvokeOptions(content),
HookParameters: getParameters(content)
}
)
}, [activeFile, getHookNamespace, snap.deployValues])
const {
register,
handleSubmit,
control,
watch,
setValue,
getValues,
reset,
formState: { errors }
} = useForm<SetHookData>({
defaultValues: getDefaultValues()
})
const { fields, append, remove } = useFieldArray({
control,
name: 'HookParameters' // unique name for your Field Array
})
const watchedFee = watch('Fee')
// Reset form if activeFile changes
useEffect(() => {
if (!activeFile) return
const defaultValues = getDefaultValues()
reset(defaultValues)
}, [activeFile, getDefaultValues, reset])
useEffect(() => {
if (watchedFee && (watchedFee.includes('.') || watchedFee.includes(','))) {
setValue('Fee', watchedFee.replaceAll('.', '').replaceAll(',', ''))
}
}, [watchedFee, setValue])
// const {
// fields: grantFields,
// append: grantAppend,
// remove: grantRemove,
// } = useFieldArray({
// control,
// name: "HookGrants", // unique name for your Field Array
// });
const [hashedNamespace, setHashedNamespace] = useState('')
const namespace = watch('HookNamespace', getHookNamespace())
const calculateHashedValue = useCallback(async () => {
const hashedVal = await sha256(namespace)
setHashedNamespace(hashedVal.toUpperCase())
}, [namespace])
useEffect(() => {
calculateHashedValue()
}, [namespace, calculateHashedValue])
const calculateFee = useCallback(async () => {
if (!account) return
const formValues = getValues()
const tx = await prepareDeployHookTx(account, formValues)
if (!tx) {
return
}
const res = await estimateFee(tx, account)
if (res && res.base_fee) {
setValue('Fee', Math.round(Number(res.base_fee || '')).toString())
}
}, [account, getValues, setValue])
const tooLargeFile = () => {
return Boolean(
activeFile?.compiledContent?.byteLength && activeFile?.compiledContent?.byteLength >= 64000
)
}
const onSubmit: SubmitHandler<SetHookData> = async data => {
const currAccount = state.accounts.find(acc => acc.address === account?.address)
if (!account) return
if (currAccount) currAccount.isLoading = true
data.HookParameters.forEach(param => {
delete param.$metaData
return param
})
const res = await deployHook(account, data)
if (currAccount) currAccount.isLoading = false
if (res && res.engine_result === 'tesSUCCESS') {
toast.success('Transaction succeeded!')
return setIsSetHookDialogOpen(false)
}
toast.error(`Transaction failed! (${res?.engine_result_message})`)
}
const onOpenChange = useCallback(
(open: boolean) => {
setIsSetHookDialogOpen(open)
if (open) calculateFee()
},
[calculateFee]
)
return (
<Dialog open={isSetHookDialogOpen} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
<Button
ghost
size="xs"
uppercase
variant={'secondary'}
disabled={!account || account.isLoading || !activeFile || tooLargeFile()}
>
Set Hook
</Button>
</DialogTrigger>
<DialogContent>
<form onSubmit={handleSubmit(onSubmit)}>
<DialogTitle>Deploy configuration</DialogTitle>
<DialogDescription as="div">
<Stack css={{ width: '100%', flex: 1 }}>
<Box css={{ width: '100%' }}>
<Label>Account</Label>
<Select
instanceId="deploy-account"
placeholder="Select account"
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => setSelectedAccount(acc)}
/>
</Box>
<Box css={{ width: '100%', position: 'relative' }}>
<Label>Sequence</Label>
<AccountSequence address={selectedAccount?.value} />
</Box>
<Box css={{ width: '100%' }}>
<Label>Invoke on transactions</Label>
<Controller
name="Invoke"
control={control}
render={({ field }) => (
<Select
{...field}
closeMenuOnSelect={false}
isMulti
menuPosition="fixed"
options={transactionOptions}
/>
)}
/>
</Box>
<Box css={{ width: '100%' }}>
<Label>Hook Namespace Seed</Label>
<Input {...register('HookNamespace', { required: true })} autoComplete={'off'} />
{errors.HookNamespace?.type === 'required' && (
<Box css={{ display: 'inline', color: '$red11' }}>Namespace is required</Box>
)}
<Box css={{ mt: '$3' }}>
<Label>Hook Namespace (sha256)</Label>
<Input readOnly value={hashedNamespace} />
</Box>
</Box>
<Box css={{ width: '100%' }}>
<Label style={{ marginBottom: '10px', display: 'block' }}>Hook parameters</Label>
<Stack>
{fields.map((field, index) => (
<Stack key={field.id}>
<Flex column>
<Flex row>
<Input
// important to include key with field's id
placeholder="Parameter name"
readOnly={field.$metaData?.required}
{...register(
`HookParameters.${index}.HookParameter.HookParameterName`
)}
/>
<Input
css={{ mx: '$2' }}
placeholder="Value (hex-quoted)"
{...register(
`HookParameters.${index}.HookParameter.HookParameterValue`,
{ required: field.$metaData?.required }
)}
/>
<Button onClick={() => remove(index)} variant="destroy">
<Trash weight="regular" size="16px" />
</Button>
</Flex>
{errors.HookParameters?.[index]?.HookParameter?.HookParameterValue
?.type === 'required' && <Text error>This field is required</Text>}
<Label css={{ fontSize: '$sm', mt: '$1' }}>
{capitalize(field.$metaData?.description)}
</Label>
</Flex>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
append({
HookParameter: {
HookParameterName: '',
HookParameterValue: ''
}
})
}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Stack>
</Box>
<Box css={{ width: '100%', position: 'relative' }}>
<Label>Fee</Label>
<Box css={{ display: 'flex', alignItems: 'center' }}>
<Input
type="number"
{...register('Fee', { required: true })}
autoComplete={'off'}
onKeyPress={e => {
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}}
step="1"
defaultValue={10000}
css={{
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
<Button
size="xs"
variant="primary"
outline
isLoading={estimateLoading}
css={{
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={async e => {
e.preventDefault()
if (!account) return
setEstimateLoading(true)
const formValues = getValues()
try {
const tx = await prepareDeployHookTx(account, formValues)
if (tx) {
const res = await estimateFee(tx, account)
if (res && res.base_fee) {
setValue('Fee', Math.round(Number(res.base_fee || '')).toString())
}
}
} catch (err) {}
setEstimateLoading(false)
}}
>
Suggest
</Button>
</Box>
{errors.Fee?.type === 'required' && (
<Box css={{ display: 'inline', color: '$red11' }}>Fee is required</Box>
)}
</Box>
{/* <Box css={{ width: "100%" }}>
<label style={{ marginBottom: "10px", display: "block" }}>
Hook Grants
</label>
<Stack>
{grantFields.map((field, index) => (
<Stack key={field.id}>
<Input
// important to include key with field's id
placeholder="Authorize"
{...register(
`HookGrants.${index}.HookGrant.Authorize`,
{ minLength: 5 }
)}
/>
<Input
placeholder="HookHash"
{...register(`HookGrants.${index}.HookGrant.HookHash`, {
minLength: 64,
maxLength: 64,
})}
/>
<Button
onClick={() => grantRemove(index)}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Stack>
))}
<Button
outline
fullWidth
type="button"
onClick={() =>
grantAppend({
HookGrant: {
Authorize: "",
HookHash: "",
},
})
}
>
<Plus size="16px" />
Add Hook Grant
</Button>
</Stack>
</Box> */}
</Stack>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
{/* <DialogClose asChild> */}
<Button variant="primary" type="submit" isLoading={account?.isLoading}>
Set Hook
</Button>
{/* </DialogClose> */}
</Flex>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</form>
</DialogContent>
</Dialog>
)
}
)
SetHookDialog.displayName = 'SetHookDialog'
export default SetHookDialog
+8 -8
View File
@@ -1,14 +1,14 @@
import { Spinner as SpinnerIcon } from "phosphor-react";
import { styled, keyframes } from "../stitches.config";
import { Spinner as SpinnerIcon } from 'phosphor-react'
import { styled, keyframes } from '../stitches.config'
const rotate = keyframes({
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(-360deg)" },
});
'0%': { transform: 'rotate(0deg)' },
'100%': { transform: 'rotate(-360deg)' }
})
const Spinner = styled(SpinnerIcon, {
animation: `${rotate} 150ms linear infinite`,
fontSize: "16px",
});
fontSize: '16px'
})
export default Spinner;
export default Spinner
+8 -11
View File
@@ -1,14 +1,11 @@
import { Children } from "react";
import Box from "./Box";
import { styled } from "../stitches.config";
import type * as Stitches from "@stitches/react";
import Box from './Box'
import { styled } from '../stitches.config'
const StackComponent = styled(Box, {
display: "flex",
flexWrap: "wrap",
flexDirection: "row",
gap: "$4",
});
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
gap: '$4'
})
export default StackComponent;
export default StackComponent
+32
View File
@@ -0,0 +1,32 @@
import { styled } from '../stitches.config'
import * as SwitchPrimitive from '@radix-ui/react-switch'
const StyledSwitch = styled(SwitchPrimitive.Root, {
all: 'unset',
width: 42,
height: 25,
backgroundColor: '$mauve9',
borderRadius: '9999px',
position: 'relative',
boxShadow: `0 2px 10px $colors$mauve2`,
WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',
'&:focus': { boxShadow: `0 0 0 2px $colors$mauveA2` },
'&[data-state="checked"]': { backgroundColor: '$green11' }
})
const StyledThumb = styled(SwitchPrimitive.Thumb, {
display: 'block',
width: 21,
height: 21,
backgroundColor: 'white',
borderRadius: '9999px',
boxShadow: `0 2px 2px $colors$mauveA6`,
transition: 'transform 100ms',
transform: 'translateX(2px)',
willChange: 'transform',
'&[data-state="checked"]': { transform: 'translateX(19px)' }
})
// Exports
export const Switch = StyledSwitch
export const SwitchThumb = StyledThumb
+374
View File
@@ -0,0 +1,374 @@
import React, { useEffect, useState, Fragment, isValidElement, useCallback } from 'react'
import type { ReactNode, ReactElement } from 'react'
import { Box, Button, Flex, Input, Label, Pre, Stack, Text } from '.'
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
DialogClose
} from './Dialog'
import { Plus, X } from 'phosphor-react'
import { styled } from '../stitches.config'
import { capitalize, getFileExtention } from '../utils/helpers'
import ContextMenu, { ContentMenuOption } from './ContextMenu'
const ErrorText = styled(Text, {
color: '$error',
mt: '$1',
display: 'block'
})
type Nullable<T> = T | null | undefined | false
interface TabProps {
header: string
children?: ReactNode
renameDisabled?: boolean
}
// TODO customize messages shown
interface Props {
label?: string
activeIndex?: number
activeHeader?: string
headless?: boolean
children: ReactElement<TabProps>[]
keepAllAlive?: boolean
defaultExtension?: string
extensionRequired?: boolean
allowedExtensions?: string[]
headerExtraValidation?: {
regex: string | RegExp
error: string
}
onCreateNewTab?: (name: string) => any
onRenameTab?: (index: number, nwName: string, oldName?: string) => any
onCloseTab?: (index: number, header?: string) => any
onChangeActive?: (index: number, header?: string) => any
}
export const Tab = (props: TabProps) => null
export const Tabs = ({
label = 'Tab',
children,
activeIndex,
activeHeader,
headless,
keepAllAlive = false,
onCreateNewTab,
onCloseTab,
onChangeActive,
onRenameTab,
headerExtraValidation,
extensionRequired,
defaultExtension = '',
allowedExtensions
}: Props) => {
const [active, setActive] = useState(activeIndex || 0)
const tabs: TabProps[] = children.map(elem => elem.props)
const [isNewtabDialogOpen, setIsNewtabDialogOpen] = useState(false)
const [renamingTab, setRenamingTab] = useState<number | null>(null)
const [tabname, setTabname] = useState('')
const [tabnameError, setTabnameError] = useState<string | null>(null)
useEffect(() => {
if (activeIndex) setActive(activeIndex)
}, [activeIndex])
useEffect(() => {
if (activeHeader) {
const idx = tabs.findIndex(tab => tab.header === activeHeader)
if (idx !== -1) setActive(idx)
else setActive(0)
}
}, [activeHeader, tabs])
// when filename changes, reset error
useEffect(() => {
setTabnameError(null)
}, [tabname, setTabnameError])
const validateTabname = useCallback(
(tabname: string): { error?: string; result?: string } => {
if (!tabname) {
return { error: `Please enter ${label.toLocaleLowerCase()} name.` }
}
let ext = getFileExtention(tabname)
if (!ext && defaultExtension) {
ext = defaultExtension
tabname = `${tabname}.${defaultExtension}`
}
if (tabs.find(tab => tab.header === tabname)) {
return { error: `${capitalize(label)} name already exists.` }
}
if (extensionRequired && !ext) {
return { error: 'File extension is required!' }
}
if (allowedExtensions && ext && !allowedExtensions.includes(ext)) {
return { error: 'This file extension is not allowed!' }
}
if (headerExtraValidation && !tabname.match(headerExtraValidation.regex)) {
return { error: headerExtraValidation.error }
}
return { result: tabname }
},
[allowedExtensions, defaultExtension, extensionRequired, headerExtraValidation, label, tabs]
)
const handleActiveChange = useCallback(
(idx: number, header?: string) => {
setActive(idx)
onChangeActive?.(idx, header)
},
[onChangeActive]
)
const handleRenameTab = useCallback(() => {
if (renamingTab === null) return
const res = validateTabname(tabname)
if (res.error) {
setTabnameError(`Error: ${res.error}`)
return
}
const { result: nwName = tabname } = res
setRenamingTab(null)
setTabname('')
const oldName = tabs[renamingTab]?.header
onRenameTab?.(renamingTab, nwName, oldName)
handleActiveChange(renamingTab, nwName)
}, [handleActiveChange, onRenameTab, renamingTab, tabname, tabs, validateTabname])
const handleCreateTab = useCallback(() => {
const res = validateTabname(tabname)
if (res.error) {
setTabnameError(`Error: ${res.error}`)
return
}
const { result: _tabname = tabname } = res
setIsNewtabDialogOpen(false)
setTabname('')
onCreateNewTab?.(_tabname)
handleActiveChange(tabs.length, _tabname)
}, [validateTabname, tabname, onCreateNewTab, handleActiveChange, tabs.length])
const handleCloseTab = useCallback(
(idx: number) => {
onCloseTab?.(idx, tabs[idx].header)
if (idx <= active && active !== 0) {
const nwActive = active - 1
handleActiveChange(nwActive, tabs[nwActive].header)
}
},
[active, handleActiveChange, onCloseTab, tabs]
)
const closeOption = (idx: number): Nullable<ContentMenuOption> =>
onCloseTab && {
type: 'text',
label: 'Close',
key: 'close',
onSelect: () => handleCloseTab(idx)
}
const renameOption = (idx: number, tab: TabProps): Nullable<ContentMenuOption> => {
return (
onRenameTab &&
!tab.renameDisabled && {
type: 'text',
label: 'Rename',
key: 'rename',
onSelect: () => setRenamingTab(idx)
}
)
}
return (
<>
{!headless && (
<Stack
css={{
gap: '$3',
flex: 1,
flexWrap: 'nowrap',
marginBottom: '$2',
width: '100%',
overflow: 'auto'
}}
>
{tabs.map((tab, idx) => (
<ContextMenu
key={tab.header}
options={
[closeOption(idx), renameOption(idx, tab)].filter(Boolean) as ContentMenuOption[]
}
>
<Button
role="tab"
tabIndex={idx}
onClick={() => handleActiveChange(idx, tab.header)}
onKeyPress={() => handleActiveChange(idx, tab.header)}
outline={active !== idx}
size="sm"
css={{
'&:hover': {
span: {
visibility: 'visible'
}
}
}}
>
{tab.header || idx}
{onCloseTab && (
<Box
as="span"
css={{
display: 'flex',
p: '2px',
borderRadius: '$full',
mr: '-4px',
'&:hover': {
// boxSizing: "0px 0px 1px",
backgroundColor: '$mauve2',
color: '$mauve12'
}
}}
onClick={(ev: React.MouseEvent<HTMLElement>) => {
ev.stopPropagation()
handleCloseTab(idx)
}}
>
<X size="9px" weight="bold" />
</Box>
)}
</Button>
</ContextMenu>
))}
{onCreateNewTab && (
<Dialog open={isNewtabDialogOpen} onOpenChange={setIsNewtabDialogOpen}>
<DialogTrigger asChild>
<Button ghost size="sm" css={{ alignItems: 'center', px: '$2', mr: '$3' }}>
<Plus size="16px" /> {tabs.length === 0 && `Add new ${label.toLocaleLowerCase()}`}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Create new {label.toLocaleLowerCase()}</DialogTitle>
<DialogDescription>
<Label>{label} name</Label>
<Input
value={tabname}
onChange={e => setTabname(e.target.value)}
onKeyPress={e => {
if (e.key === 'Enter') {
handleCreateTab()
}
}}
/>
<ErrorText>{tabnameError}</ErrorText>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button variant="primary" onClick={handleCreateTab}>
Create
</Button>
</Flex>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
)}
{onRenameTab && (
<Dialog open={renamingTab !== null} onOpenChange={() => setRenamingTab(null)}>
<DialogContent>
<DialogTitle>
Rename <Pre>{tabs[renamingTab || 0]?.header}</Pre>
</DialogTitle>
<DialogDescription>
<Label>Enter new name</Label>
<Input
value={tabname}
onChange={e => setTabname(e.target.value)}
onKeyPress={e => {
if (e.key === 'Enter') {
handleRenameTab()
}
}}
/>
<ErrorText>{tabnameError}</ErrorText>
</DialogDescription>
<Flex
css={{
marginTop: 25,
justifyContent: 'flex-end',
gap: '$3'
}}
>
<DialogClose asChild>
<Button outline>Cancel</Button>
</DialogClose>
<Button variant="primary" onClick={handleRenameTab}>
Confirm
</Button>
</Flex>
<DialogClose asChild>
<Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
<X size="20px" />
</Box>
</DialogClose>
</DialogContent>
</Dialog>
)}
</Stack>
)}
{keepAllAlive
? tabs.map((tab, idx) => {
// TODO Maybe rule out fragments as children
if (!isValidElement(tab.children)) {
if (active !== idx) return null
return tab.children
}
let key = tab.children.key || tab.header || idx
let { children } = tab
let { style, ...props } = children.props
return (
<children.type
key={key}
{...props}
style={{
...style,
display: active !== idx ? 'none' : undefined
}}
/>
)
})
: tabs[active] && (
<Fragment key={tabs[active].header || active}>{tabs[active].children}</Fragment>
)}
</>
)
}
+39 -7
View File
@@ -1,9 +1,41 @@
import { styled } from "../stitches.config";
import { styled } from '../stitches.config'
const Text = styled("span", {
fontFamily: "$body",
lineHeight: "$body",
color: "$text",
});
const Text = styled('span', {
fontFamily: '$body',
lineHeight: '$body',
color: '$text',
variants: {
small: {
true: {
fontSize: '$xs'
}
},
muted: {
true: {
color: '$mauve9'
}
},
error: {
true: {
color: '$error'
}
},
warning: {
true: {
color: '$warning'
}
},
monospace: {
true: {
fontFamily: '$monospace'
}
},
block: {
true: {
display: 'block'
}
}
}
})
export default Text;
export default Text
+113
View File
@@ -0,0 +1,113 @@
import { styled } from '../stitches.config'
export const Textarea = styled('textarea', {
// Reset
appearance: 'none',
borderWidth: '0',
boxSizing: 'border-box',
fontFamily: 'inherit',
outline: 'none',
width: '100%',
flex: '1',
backgroundColor: '$mauve4',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '$sm',
p: '$2',
fontSize: '$md',
lineHeight: 1,
color: '$mauve12',
boxShadow: `0 0 0 1px $colors$mauve8`,
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
'&::before': {
boxSizing: 'border-box'
},
'&::after': {
boxSizing: 'border-box'
},
fontVariantNumeric: 'tabular-nums',
'&:-webkit-autofill': {
boxShadow: 'inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3'
},
'&:-webkit-autofill::first-line': {
fontFamily: '$untitled',
color: '$mauve12'
},
'&:focus': {
boxShadow: `0 0 0 1px $colors$mauve10`,
'&:-webkit-autofill': {
boxShadow: `0 0 0 1px $colors$mauve10`
}
},
'&::placeholder': {
color: '$mauve9'
},
'&:disabled': {
pointerEvents: 'none',
backgroundColor: '$mauve2',
color: '$mauve8',
cursor: 'not-allowed',
'&::placeholder': {
color: '$mauve7'
}
},
variants: {
variant: {
ghost: {
boxShadow: 'none',
backgroundColor: 'transparent',
'@hover': {
'&:hover': {
boxShadow: 'inset 0 0 0 1px $colors$mauve7'
}
},
'&:focus': {
backgroundColor: '$loContrast',
boxShadow: `0 0 0 1px $colors$mauve10`
},
'&:disabled': {
backgroundColor: 'transparent'
},
'&:read-only': {
backgroundColor: 'transparent'
}
},
deep: {
backgroundColor: '$deep',
boxShadow: 'none'
}
},
state: {
invalid: {
boxShadow: 'inset 0 0 0 1px $colors$crimson7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$crimson8, 0px 0px 0px 1px $colors$crimson8'
}
},
valid: {
boxShadow: 'inset 0 0 0 1px $colors$grass7',
'&:focus': {
boxShadow: 'inset 0px 0px 0px 1px $colors$grass8, 0px 0px 0px 1px $colors$grass8'
}
}
},
cursor: {
default: {
cursor: 'default',
'&:focus': {
cursor: 'text'
}
},
text: {
cursor: 'text'
}
}
}
})
export default Textarea
+22 -25
View File
@@ -1,37 +1,34 @@
import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { Sun, Moon } from "phosphor-react";
import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
import { Sun, Moon } from 'phosphor-react'
import Box from "./Box";
import Button from './Button'
const ThemeChanger = () => {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null;
if (!mounted) return null
return (
<Box
<Button
outline
onClick={() => {
setTheme(theme && theme === "light" ? "dark" : "light");
setTheme(theme && theme === 'light' ? 'dark' : 'light')
}}
css={{
display: "flex",
marginLeft: "auto",
cursor: "pointer",
alignItems: "center",
justifyContent: "center",
color: "$muted",
display: 'flex',
marginLeft: 'auto',
cursor: 'pointer',
alignItems: 'center',
justifyContent: 'center',
color: '$muted'
}}
>
{theme === "dark" ? (
<Sun weight="bold" size="16px" />
) : (
<Moon weight="bold" size="16px" />
)}
</Box>
);
};
{theme === 'dark' ? <Sun size="15px" /> : <Moon size="15px" />}
</Button>
)
}
export default ThemeChanger;
export default ThemeChanger
+90
View File
@@ -0,0 +1,90 @@
import React from 'react'
import { styled, keyframes } from '../stitches.config'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' }
})
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' }
})
const StyledContent = styled(TooltipPrimitive.Content, {
borderRadius: 4,
padding: '$2 $3',
fontSize: 12,
lineHeight: 1,
color: '$text',
backgroundColor: '$background',
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
animationFillMode: 'forwards',
willChange: 'transform, opacity',
'&[data-state="delayed-open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade }
}
},
'.dark &': {
boxShadow:
'0px 0px 10px 2px rgba(0,0,0,.45), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px'
},
'.light &': {
boxShadow:
'0px 0px 10px 2px rgba(0,0,0,.25), hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px'
}
})
const StyledArrow = styled(TooltipPrimitive.Arrow, {
fill: '$background'
})
interface ITooltip {
content: string
open?: boolean
defaultOpen?: boolean
onOpenChange?: (open: boolean) => void
}
const Tooltip: React.FC<React.ComponentProps<typeof StyledContent> & ITooltip> = ({
children,
content,
open,
defaultOpen = false,
onOpenChange,
...rest
}) => {
return (
<TooltipPrimitive.Root
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
delayDuration={100}
>
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
<StyledContent side="bottom" align="center" {...rest}>
<div dangerouslySetInnerHTML={{ __html: content }} />
<StyledArrow offset={5} width={11} height={5} />
</StyledContent>
</TooltipPrimitive.Root>
)
}
export default Tooltip
+273
View File
@@ -0,0 +1,273 @@
import { Play } from 'phosphor-react'
import { FC, useCallback, useEffect } from 'react'
import { useSnapshot } from 'valtio'
import state from '../../state'
import {
defaultTransactionType,
getTxFields,
modifyTxState,
prepareState,
prepareTransaction,
SelectOption,
TransactionState
} from '../../state/transactions'
import { sendTransaction } from '../../state/actions'
import Box from '../Box'
import Button from '../Button'
import Flex from '../Flex'
import { TxJson } from './json'
import { TxUI } from './ui'
import { default as _estimateFee } from '../../utils/estimateFee'
import toast from 'react-hot-toast'
import { combineFlags, extractFlags, transactionFlags } from '../../state/constants/flags'
import { SetHookData, toHex } from '../../utils/setHook'
export interface TransactionProps {
header: string
state: TransactionState
}
const Transaction: FC<TransactionProps> = ({ header, state: txState, ...props }) => {
const { accounts, editorSettings } = useSnapshot(state)
const { selectedAccount, selectedTransaction, txIsDisabled, txIsLoading, viewType, editorValue } =
txState
const setState = useCallback(
(pTx?: Partial<TransactionState>) => {
return modifyTxState(header, pTx)
},
[header]
)
const prepareOptions = useCallback(
(state: Partial<TransactionState> = txState) => {
const {
selectedTransaction,
selectedAccount,
txFields,
selectedFlags,
hookParameters,
memos
} = state
const TransactionType = selectedTransaction?.value || null
const Account = selectedAccount?.value || null
const Flags = combineFlags(selectedFlags?.map(flag => flag.value)) || txFields?.Flags
const HookParameters = Object.entries(hookParameters || {}).reduce<
SetHookData['HookParameters']
>((acc, [_, { label, value }]) => {
return acc.concat({
HookParameter: { HookParameterName: toHex(label), HookParameterValue: toHex(value) }
})
}, [])
const Memos = memos
? Object.entries(memos).reduce<SetHookData['Memos']>((acc, [_, { format, data, type }]) => {
return acc?.concat({
Memo: { MemoData: toHex(data), MemoFormat: toHex(format), MemoType: toHex(type) }
})
}, [])
: undefined
return prepareTransaction({
...txFields,
HookParameters,
Flags,
TransactionType,
Account,
Memos
})
},
[txState]
)
useEffect(() => {
const transactionType = selectedTransaction?.value
const account = selectedAccount?.value
if (!account || !transactionType || txIsLoading) {
setState({ txIsDisabled: true })
} else {
setState({ txIsDisabled: false })
}
}, [selectedAccount?.value, selectedTransaction?.value, setState, txIsLoading])
const getJsonString = useCallback(
(state?: Partial<TransactionState>) =>
JSON.stringify(prepareOptions?.(state) || {}, null, editorSettings.tabSize),
[editorSettings.tabSize, prepareOptions]
)
const saveEditorState = useCallback(
(value: string = '', transactionType?: string) => {
const pTx = prepareState(value, transactionType)
if (pTx) {
pTx.editorValue = getJsonString(pTx)
return setState(pTx)
}
},
[getJsonString, setState]
)
const submitTest = useCallback(async () => {
let st: TransactionState | undefined
const tt = txState.selectedTransaction?.value
if (viewType === 'json') {
st = saveEditorState(editorValue, tt)
if (!st) return
}
const account = accounts.find(acc => acc.address === selectedAccount?.value)
if (txIsDisabled) return
setState({ txIsLoading: true })
const logPrefix = header ? `${header.split('.')[0]}: ` : undefined
try {
if (!account) {
throw Error('Account must be selected from imported accounts!')
}
const options = prepareOptions(st)
// delete unnecessary fields
Object.keys(options).forEach(field => {
if (!options[field]) {
delete options[field]
}
})
await sendTransaction(account, options, { logPrefix })
} catch (error) {
console.error(error)
if (error instanceof Error) {
state.transactionLogs.push({
type: 'error',
message: `${logPrefix}${error.message}`
})
}
}
setState({ txIsLoading: false })
}, [
txState.selectedTransaction?.value,
viewType,
accounts,
txIsDisabled,
setState,
header,
saveEditorState,
editorValue,
selectedAccount?.value,
prepareOptions
])
const resetState = useCallback(
(transactionType: SelectOption | undefined = defaultTransactionType) => {
const fields = getTxFields(transactionType?.value)
const nwState: Partial<TransactionState> = {
viewType,
selectedTransaction: transactionType
}
if (transactionType?.value && transactionFlags[transactionType?.value] && fields.Flags) {
nwState.selectedFlags = extractFlags(transactionType.value, fields.Flags)
fields.Flags = undefined
}
nwState.txFields = fields
const state = modifyTxState(header, nwState, { replaceState: true })
const editorValue = getJsonString(state)
return setState({ editorValue })
},
[getJsonString, header, setState, viewType]
)
const estimateFee = useCallback(
async (st?: TransactionState, opts?: { silent?: boolean }) => {
const state = st || txState
const ptx = prepareOptions(state)
const account = accounts.find(acc => acc.address === state.selectedAccount?.value)
if (!account) {
if (!opts?.silent) {
toast.error('Please select account from the list.')
}
return
}
ptx.Account = account.address
ptx.Sequence = account.sequence
const res = await _estimateFee(ptx, account, opts)
const fee = res?.base_fee
setState({ estimatedFee: fee })
return fee
},
[accounts, prepareOptions, setState, txState]
)
const switchToJson = useCallback(() => {
const editorValue = getJsonString()
setState({ viewType: 'json', editorValue })
}, [getJsonString, setState])
const switchToUI = useCallback(() => {
setState({ viewType: 'ui' })
}, [setState])
return (
<Box css={{ position: 'relative', height: 'calc(100% - 28px)' }} {...props}>
{viewType === 'json' ? (
<TxJson
getJsonString={getJsonString}
saveEditorState={saveEditorState}
header={header}
state={txState}
setState={setState}
estimateFee={estimateFee}
/>
) : (
<TxUI
switchToJson={switchToJson}
state={txState}
resetState={resetState}
setState={setState}
estimateFee={estimateFee}
/>
)}
<Flex
row
css={{
justifyContent: 'space-between',
position: 'absolute',
left: 0,
bottom: 0,
width: '100%',
mb: '$1'
}}
>
<Button
onClick={() => {
if (viewType === 'ui') {
switchToJson()
} else switchToUI()
}}
outline
>
{viewType === 'ui' ? 'EDIT AS JSON' : 'EXIT JSON MODE'}
</Button>
<Flex row>
<Button onClick={() => resetState()} outline css={{ mr: '$3' }}>
RESET
</Button>
<Button
variant="primary"
onClick={submitTest}
isLoading={txIsLoading}
disabled={txIsDisabled}
>
<Play weight="bold" size="16px" />
RUN TEST
</Button>
</Flex>
</Flex>
</Box>
)
}
export default Transaction
+197
View File
@@ -0,0 +1,197 @@
import { FC, useCallback, useEffect, useState } from 'react'
import { useSnapshot } from 'valtio'
import state, { transactionsData, TransactionState } from '../../state'
import Text from '../Text'
import { Flex, Link } from '..'
import { showAlert } from '../../state/actions/showAlert'
import { parseJSON } from '../../utils/json'
import { extractSchemaProps } from '../../utils/schema'
import amountSchema from '../../content/amount-schema.json'
import Monaco from '../Monaco'
import type monaco from 'monaco-editor'
interface JsonProps {
getJsonString: (st?: Partial<TransactionState>) => string
saveEditorState: (val?: string, tt?: string) => TransactionState | undefined
header?: string
setState: (pTx?: Partial<TransactionState> | undefined) => void
state: TransactionState
estimateFee?: () => Promise<string | undefined>
}
export const TxJson: FC<JsonProps> = ({
getJsonString,
state: txState,
header,
setState,
saveEditorState
}) => {
const { editorSettings, accounts } = useSnapshot(state)
const { editorValue, estimatedFee, editorIsSaved } = txState
const [currTxType, setCurrTxType] = useState<string | undefined>(
txState.selectedTransaction?.value
)
useEffect(() => {
const parsed = parseJSON(editorValue)
if (!parsed) return
const tt = parsed.TransactionType
const tx = transactionsData.find(t => t.TransactionType === tt)
if (tx) setCurrTxType(tx.TransactionType)
else {
setCurrTxType(undefined)
}
}, [editorValue])
const discardChanges = () => {
showAlert('Confirm', {
body: 'Are you sure to discard these changes?',
confirmText: 'Yes',
onCancel: () => {},
onConfirm: () => setState({ editorValue: getJsonString() })
})
}
const onExit = (value: string) => {
const options = parseJSON(value)
if (options) {
saveEditorState(value, currTxType)
return
}
showAlert('Error!', {
body: `Malformed Transaction in ${header}, would you like to discard these changes?`,
confirmText: 'Discard',
onConfirm: () => setState({ editorValue: getJsonString?.() }),
onCancel: () => setState({ viewType: 'json' })
})
}
const getSchemas = useCallback(async (): Promise<any[]> => {
const txObj = transactionsData.find(td => td.TransactionType === currTxType)
let genericSchemaProps: any
if (txObj) {
genericSchemaProps = extractSchemaProps(txObj)
} else {
genericSchemaProps = transactionsData.reduce(
(cumm, td) => ({
...cumm,
...extractSchemaProps(td)
}),
{}
)
}
return [
{
uri: 'file:///main-schema.json', // id of the first schema
fileMatch: ['**.json'], // associate with our model
schema: {
title: header,
type: 'object',
required: ['TransactionType', 'Account'],
properties: {
...genericSchemaProps,
TransactionType: {
title: 'Transaction Type',
enum: transactionsData.map(td => td.TransactionType)
},
Account: {
$ref: 'file:///account-schema.json'
},
Destination: {
anyOf: [
{
$ref: 'file:///account-schema.json'
},
{
type: 'string',
title: 'Destination Account'
}
]
},
Amount: {
$ref: 'file:///amount-schema.json'
},
Fee: {
$ref: 'file:///fee-schema.json'
}
}
}
},
{
uri: 'file:///account-schema.json',
schema: {
type: 'string',
title: 'Account type',
enum: accounts.map(acc => acc.address)
}
},
{
uri: 'file:///fee-schema.json',
schema: {
type: 'string',
title: 'Fee type',
const: estimatedFee,
description: estimatedFee ? 'Above mentioned value is recommended base fee' : undefined
}
},
{
...amountSchema
}
]
}, [accounts, currTxType, estimatedFee, header])
const [monacoInst, setMonacoInst] = useState<typeof monaco>()
useEffect(() => {
if (!monacoInst) return
getSchemas().then(schemas => {
monacoInst.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas
})
})
}, [getSchemas, monacoInst])
return (
<Monaco
rootProps={{
css: { height: 'calc(100% - 45px)' }
}}
language={'json'}
id={header}
height="100%"
value={editorValue}
onChange={val => setState({ editorValue: val, editorIsSaved: false })}
onMount={(editor, monaco) => {
editor.updateOptions({
minimap: { enabled: false },
glyphMargin: true,
tabSize: editorSettings.tabSize,
dragAndDrop: true,
fontSize: 14
})
setMonacoInst(monaco)
// register onExit cb
const model = editor.getModel()
model?.onWillDispose(() => onExit(model.getValue()))
}}
overlay={
!editorIsSaved ? (
<Flex row align="center" css={{ fontSize: '$xs', color: '$textMuted', ml: 'auto' }}>
<Text muted small>
This file has unsaved changes.
</Text>
<Link css={{ ml: '$1' }} onClick={() => saveEditorState(editorValue, currTxType)}>
save
</Link>
<Link css={{ ml: '$1' }} onClick={discardChanges}>
discard
</Link>
</Flex>
) : undefined
}
/>
)
}
+583
View File
@@ -0,0 +1,583 @@
import { FC, ReactNode, useCallback, useEffect, useState } from 'react'
import Container from '../Container'
import Flex from '../Flex'
import Input from '../Input'
import Select, { CreatableSelect } from '../Select'
import Text from '../Text'
import {
SelectOption,
TransactionState,
transactionsOptions,
TxFields,
defaultTransactionType
} from '../../state/transactions'
import { useSnapshot } from 'valtio'
import state from '../../state'
import { streamState } from '../DebugStream'
import { Box, Button } from '..'
import Textarea from '../Textarea'
import { getFlags } from '../../state/constants/flags'
import { Plus, Trash } from 'phosphor-react'
import AccountSequence from '../Sequence'
import { capitalize, typeIs } from '../../utils/helpers'
interface UIProps {
setState: (pTx?: Partial<TransactionState> | undefined) => TransactionState | undefined
resetState: (tt?: SelectOption) => TransactionState | undefined
state: TransactionState
estimateFee?: (...arg: any) => Promise<string | undefined>
switchToJson: () => void
}
export const TxUI: FC<UIProps> = ({
state: txState,
setState,
resetState,
estimateFee,
switchToJson
}) => {
const { accounts } = useSnapshot(state)
const { selectedAccount, selectedTransaction, txFields, selectedFlags, hookParameters, memos } =
txState
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const flagsOptions: SelectOption[] = Object.entries(
getFlags(selectedTransaction?.value) || {}
).map(([label, value]) => ({
label,
value
}))
const [feeLoading, setFeeLoading] = useState(false)
const handleSetAccount = (acc: SelectOption) => {
setState({ selectedAccount: acc })
streamState.selectedAccount = acc
}
const handleSetField = useCallback(
(field: keyof TxFields, value: string, opFields?: TxFields) => {
const fields = opFields || txFields
const obj = fields[field]
setState({
txFields: {
...fields,
[field]: typeof obj === 'object' ? { ...obj, $value: value } : value
}
})
},
[setState, txFields]
)
const setRawField = useCallback(
(field: keyof TxFields, type: string, value: any) => {
// TODO $type should be a narrowed type
setState({
txFields: {
...txFields,
[field]: {
$type: type,
$value: value
}
}
})
},
[setState, txFields]
)
const handleEstimateFee = useCallback(
async (state?: TransactionState, silent?: boolean) => {
setFeeLoading(true)
const fee = await estimateFee?.(state, { silent })
if (fee) handleSetField('Fee', fee, state?.txFields)
setFeeLoading(false)
},
[estimateFee, handleSetField]
)
const handleChangeTxType = useCallback(
(tt: SelectOption) => {
setState({ selectedTransaction: tt })
const newState = resetState(tt)
handleEstimateFee(newState, true)
},
[handleEstimateFee, resetState, setState]
)
// default tx
useEffect(() => {
if (selectedTransaction?.value) return
if (defaultTransactionType) {
handleChangeTxType(defaultTransactionType)
}
}, [handleChangeTxType, selectedTransaction?.value])
const richFields = ['TransactionType', 'Account', 'HookParameters', 'Memos']
if (flagsOptions.length) {
richFields.push('Flags')
}
const otherFields = Object.keys(txFields).filter(k => !richFields.includes(k)) as [keyof TxFields]
const amountOptions = [
{ label: 'XRP', value: 'xrp' },
{ label: 'Token', value: 'token' }
] as const
const defaultTokenAmount = {
value: '0',
currency: '',
issuer: ''
}
return (
<Container
css={{
p: '$3 01',
fontSize: '$sm',
height: 'calc(100% - 45px)'
}}
>
<Flex column fluid css={{ height: '100%', overflowY: 'auto', pr: '$1' }}>
<TxField label="Transaction type">
<Select
instanceId="transactionsType"
placeholder="Select transaction type"
options={transactionsOptions}
hideSelectedOptions
value={selectedTransaction}
onChange={(tt: any) => handleChangeTxType(tt)}
/>
</TxField>
<TxField label="Account">
<Select
instanceId="from-account"
placeholder="Select your account"
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
/>
</TxField>
<TxField label="Sequence">
<AccountSequence address={selectedAccount?.value} />
</TxField>
{richFields.includes('Flags') && (
<TxField label="Flags">
<Select
isClearable
instanceId="flags"
placeholder="Select flags to apply"
menuPosition="fixed"
value={selectedFlags}
isMulti
options={flagsOptions}
onChange={flags => setState({ selectedFlags: flags as any })}
closeMenuOnSelect={
selectedFlags ? selectedFlags.length >= flagsOptions.length - 1 : false
}
/>
</TxField>
)}
{otherFields.map(field => {
let _value = txFields[field]
let value: string | undefined
if (typeIs(_value, 'object')) {
if (_value.$type === 'json' && typeIs(_value.$value, ['object', 'array'])) {
value = JSON.stringify(_value.$value, null, 2)
} else {
value = _value.$value?.toString()
}
} else {
value = _value?.toString()
}
const isAccount = typeIs(_value, 'object') && _value.$type === 'account'
const isXrpAmount = typeIs(_value, 'object') && _value.$type === 'amount.xrp'
const isTokenAmount = typeIs(_value, 'object') && _value.$type === 'amount.token'
const isJson = typeof _value === 'object' && _value.$type === 'json'
const isFee = field === 'Fee'
let rows = isJson ? (value?.match(/\n/gm)?.length || 0) + 1 : undefined
if (rows && rows > 5) rows = 5
let tokenAmount = defaultTokenAmount
if (isTokenAmount && typeIs(_value, 'object') && typeIs(_value.$value, 'object')) {
tokenAmount = {
value: _value.$value.value,
currency: _value.$value.currency,
issuer: _value.$value.issuer
}
}
if (isXrpAmount || isTokenAmount) {
return (
<TxField key={field} label={field}>
<Flex fluid css={{ alignItems: 'center' }}>
{isTokenAmount ? (
<Flex
fluid
row
align="center"
justify="space-between"
css={{ position: 'relative' }}
>
{/* <Input
type="text"
placeholder="Issuer"
value={tokenAmount.issuer}
onChange={e =>
setRawField(field, 'amount.token', {
...tokenAmount,
issuer: e.target.value
})
}
/> */}
<Input
type="text"
value={tokenAmount.currency}
placeholder="Currency"
onChange={e => {
setRawField(field, 'amount.token', {
...tokenAmount,
currency: e.target.value
})
}}
/>
<Input
css={{ mx: '$1' }}
type="number"
value={tokenAmount.value}
placeholder="Value"
onChange={e => {
setRawField(field, 'amount.token', {
...tokenAmount,
value: e.target.value
})
}}
/>
<Box css={{ width: '50%' }}>
<CreatableAccount
value={tokenAmount.issuer}
field={'Issuer' as any}
placeholder="Issuer"
setField={(_, value = '') => {
setRawField(field, 'amount.token', {
...tokenAmount,
issuer: value
})
}}
/>
</Box>
</Flex>
) : (
<Input
css={{ flex: 'inherit' }}
type="number"
value={value}
onChange={e => handleSetField(field, e.target.value)}
/>
)}
<Box
css={{
ml: '$2',
width: '150px'
}}
>
<Select
instanceId="currency-type"
options={amountOptions}
value={isXrpAmount ? amountOptions['0'] : amountOptions['1']}
onChange={(e: any) => {
const opt = e as typeof amountOptions[number]
if (opt.value === 'xrp') {
setRawField(field, 'amount.xrp', '0')
} else {
setRawField(field, 'amount.token', defaultTokenAmount)
}
}}
/>
</Box>
</Flex>
</TxField>
)
}
if (isAccount) {
return (
<TxField key={field} label={field}>
<CreatableAccount value={value} field={field} setField={handleSetField} />
</TxField>
)
}
return (
<TxField key={field} label={field}>
{isJson ? (
<Textarea
rows={rows}
value={value}
spellCheck={false}
onChange={switchToJson}
css={{
flex: 'inherit',
resize: 'vertical'
}}
/>
) : (
<Input
type={isFee ? 'number' : 'text'}
value={value}
onChange={e => {
if (isFee) {
const val = e.target.value.replaceAll('.', '').replaceAll(',', '')
handleSetField(field, val)
} else {
handleSetField(field, e.target.value)
}
}}
onKeyPress={
isFee
? e => {
if (e.key === '.' || e.key === ',') {
e.preventDefault()
}
}
: undefined
}
css={{
flex: 'inherit',
'-moz-appearance': 'textfield',
'&::-webkit-outer-spin-button': {
'-webkit-appearance': 'none',
margin: 0
},
'&::-webkit-inner-spin-button ': {
'-webkit-appearance': 'none',
margin: 0
}
}}
/>
)}
{isFee && (
<Button
size="xs"
variant="primary"
outline
disabled={txState.txIsDisabled}
isDisabled={txState.txIsDisabled}
isLoading={feeLoading}
css={{
position: 'absolute',
right: '$2',
fontSize: '$xs',
cursor: 'pointer',
alignContent: 'center',
display: 'flex'
}}
onClick={() => handleEstimateFee()}
>
Suggest
</Button>
)}
</TxField>
)
})}
<TxField multiLine label="Hook parameters">
<Flex column fluid>
{Object.entries(hookParameters).map(([id, { label, value }]) => (
<Flex column key={id} css={{ mb: '$2' }}>
<Flex row>
<Input
placeholder="Parameter name"
value={label}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label: e.target.value, value }
}
})
}}
/>
<Input
css={{ mx: '$2' }}
placeholder="Value"
value={value}
onChange={e => {
setState({
hookParameters: {
...hookParameters,
[id]: { label, value: e.target.value }
}
})
}}
/>
<Button
onClick={() => {
const { [id]: _, ...rest } = hookParameters
setState({ hookParameters: rest })
}}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Flex>
</Flex>
))}
<Button
outline
fullWidth
type="button"
onClick={() => {
const id = Object.keys(hookParameters).length
setState({
hookParameters: { ...hookParameters, [id]: { label: '', value: '' } }
})
}}
>
<Plus size="16px" />
Add Hook Parameter
</Button>
</Flex>
</TxField>
<TxField multiLine label="Memos">
<Flex column fluid>
{Object.entries(memos).map(([id, memo]) => (
<Flex column key={id} css={{ mb: '$2' }}>
<Flex
row
css={{
flexWrap: 'wrap',
width: '100%'
}}
>
<Input
placeholder="Memo type"
value={memo.type}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, type: e.target.value }
}
})
}}
/>
<Input
placeholder="Data"
css={{ mx: '$2' }}
value={memo.data}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, data: e.target.value }
}
})
}}
/>
<Input
placeholder="Format"
value={memo.format}
onChange={e => {
setState({
memos: {
...memos,
[id]: { ...memo, format: e.target.value }
}
})
}}
/>
<Button
css={{ ml: '$2' }}
onClick={() => {
const { [id]: _, ...rest } = memos
setState({ memos: rest })
}}
variant="destroy"
>
<Trash weight="regular" size="16px" />
</Button>
</Flex>
</Flex>
))}
<Button
outline
fullWidth
type="button"
onClick={() => {
const id = Object.keys(memos).length
setState({
memos: { ...memos, [id]: { data: '', format: '', type: '' } }
})
}}
>
<Plus size="16px" />
Add Memo
</Button>
</Flex>
</TxField>
</Flex>
</Container>
)
}
export const CreatableAccount: FC<{
value: string | undefined
field: keyof TxFields
placeholder?: string
setField: (field: keyof TxFields, value: string, opFields?: TxFields) => void
}> = ({ value, field, setField, placeholder }) => {
const { accounts } = useSnapshot(state)
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const label = accountOptions.find(a => a.value === value)?.label || value
const val = {
value,
label
}
placeholder = placeholder || `${capitalize(field)} account`
return (
<CreatableSelect
isClearable
instanceId={field}
placeholder={placeholder}
options={accountOptions}
value={value ? val : undefined}
onChange={(acc: any) => setField(field, acc?.value)}
/>
)
}
export const TxField: FC<{ label: string; children: ReactNode; multiLine?: boolean }> = ({
label,
children,
multiLine = false
}) => {
return (
<Flex
row
fluid
css={{
justifyContent: 'flex-end',
alignItems: multiLine ? 'flex-start' : 'center',
position: 'relative',
mb: '$2',
mt: '1px',
pr: '1px'
}}
>
<Text muted css={{ mr: '$3', mt: multiLine ? '$2' : 0 }}>
{label}:{' '}
</Text>
<Flex css={{ width: '70%', alignItems: 'center' }}>{children}</Flex>
</Flex>
)
}
+34
View File
@@ -0,0 +1,34 @@
const Carbon = () => (
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 2L23 15H28L21 24H45L38 15H43L33 2Z"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M33 24V30"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M-1.14441e-05 4L8.94099 15.0625L4.00543e-05 26.125H2.27587L10.5015 15.9475H16.5938V14.1775H10.5015L2.27582 4H-1.14441e-05Z"
fill="#EDEDEF"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M66 4L57.059 15.0625L66 26.125H63.7241L55.4985 15.9475H49.4062V14.1775H55.4985L63.7242 4H66Z"
fill="#EDEDEF"
/>
</svg>
)
export default Carbon
+69
View File
@@ -0,0 +1,69 @@
const Firewall = () => (
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 13V7"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M27 19V13"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M39 19V13"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M33 25V19"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M21 13H45"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M21 19H45"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M45 7H21V25H45V7Z"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M-1.14441e-05 4.875L8.94099 15.9375L4.00543e-05 27H2.27587L10.5015 16.8225H16.5938V15.0525H10.5015L2.27582 4.875H-1.14441e-05Z"
fill="#EDEDEF"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M66 4.875L57.059 15.9375L66 27H63.7241L55.4985 16.8225H49.4062V15.0525H55.4985L63.7242 4.875H66Z"
fill="#EDEDEF"
/>
</svg>
)
export default Firewall
+34
View File
@@ -0,0 +1,34 @@
const Notary = () => (
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M37.5 10.5L26.5 21.5L21 16.0002"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M49 10.5L38 21.5L35.0784 18.5785"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M-1.14441e-05 5L8.94099 16.0625L4.00543e-05 27.125H2.27587L10.5015 16.9475H16.5938V15.1775H10.5015L2.27582 5H-1.14441e-05Z"
fill="#EDEDEF"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M66 5L57.059 16.0625L66 27.125H63.7241L55.4985 16.9475H49.4062V15.1775H55.4985L63.7242 5H66Z"
fill="#EDEDEF"
/>
</svg>
)
export default Notary
+55
View File
@@ -0,0 +1,55 @@
const Peggy = () => (
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M33 19C40.1797 19 46 16.3137 46 13C46 9.68629 40.1797 7 33 7C25.8203 7 20 9.68629 20 13C20 16.3137 25.8203 19 33 19Z"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M33 19V25"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M20 13V19C20 22 25 25 33 25C41 25 46 22 46 19V13"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M41 17.7633V23.7634"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M25 17.7633V23.7634"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M-1.14441e-05 4L8.94099 15.0625L4.00543e-05 26.125H2.27587L10.5015 15.9475H16.5938V14.1775H10.5015L2.27582 4H-1.14441e-05Z"
fill="#EDEDEF"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M66 4L57.059 15.0625L66 26.125H63.7241L55.4985 15.9475H49.4062V14.1775H55.4985L63.7242 4H66Z"
fill="#EDEDEF"
/>
</svg>
)
export default Peggy
+34
View File
@@ -0,0 +1,34 @@
const Starter = () => (
<svg width="66" height="32" viewBox="0 0 66 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M42 28H24C23.7347 28 23.4804 27.8946 23.2929 27.7071C23.1053 27.5196 23 27.2652 23 27V5C23 4.73479 23.1053 4.48044 23.2929 4.2929C23.4804 4.10537 23.7347 4.00001 24 4H36.0003L43 11V27C43 27.2652 42.8947 27.5196 42.7071 27.7071C42.5196 27.8946 42.2653 28 42 28V28Z"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M36 4V11H43.001"
stroke="#EDEDEF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M-1.14441e-05 4.875L8.94099 15.9375L4.00543e-05 27H2.27587L10.5015 16.8225H16.5938V15.0525H10.5015L2.27582 4.875H-1.14441e-05Z"
fill="#EDEDEF"
/>
<path
className="angle"
fillRule="evenodd"
clipRule="evenodd"
d="M66 4.875L57.059 15.9375L66 27H63.7241L55.4985 16.8225H49.4062V15.0525H55.4985L63.7242 4.875H66Z"
fill="#EDEDEF"
/>
</svg>
)
export default Starter
+16
View File
@@ -0,0 +1,16 @@
export { default as Flex } from './Flex'
export { default as Link } from './Link'
export { default as Container } from './Container'
export { default as Heading } from './Heading'
export { default as Stack } from './Stack'
export { default as Text } from './Text'
export { default as Input, Label } from './Input'
export { default as Select } from './Select'
export * from './Tabs'
export * from './AlertDialog/primitive'
export { default as Box } from './Box'
export { default as Button } from './Button'
export { default as Pre } from './Pre'
export { default as ButtonGroup } from './ButtonGroup'
export * from './Dialog'
export * from './DropdownMenu'
+44
View File
@@ -0,0 +1,44 @@
{
"uri": "file:///amount-schema.json",
"title": "Amount",
"description": "Specify xrp in drops and tokens as objects.",
"schema": {
"anyOf": [
{
"type": ["number", "string"],
"exclusiveMinimum": 0,
"maximum": "100000000000000000"
},
{
"type": "object",
"properties": {
"currency": {
"description": "Arbitrary currency code for the token. Cannot be XRP."
},
"value": {
"type": ["string", "number"],
"description": "Quoted decimal representation of the amount of the token."
},
"issuer": {
"type": "string",
"description": "Generally, the account that issues this token. In special cases, this can refer to the account that holds the token instead."
}
}
}
],
"defaultSnippets": [
{
"label": "Xrp",
"body": "1000000"
},
{
"label": "Token",
"body": {
"currency": "${1:USD}",
"value": "${2:100}",
"issuer": "${3:rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpns}"
}
}
]
}
}
+409
View File
@@ -0,0 +1,409 @@
[
{
"code": 1,
"identifier": "AMENDMENT_DISABLED",
"description": "attempt to HookSet when amendment is not yet enabled."
},
{
"code": 2,
"identifier": "API_ILLEGAL",
"description": "HookSet object contained HookApiVersion for existing HookDefinition"
},
{
"code": 3,
"identifier": "API_INVALID",
"description": "HookSet object contained HookApiVersion for unrecognised hook API "
},
{
"code": 4,
"identifier": "API_MISSING",
"description": "HookSet object lacked HookApiVersion"
},
{
"code": 5,
"identifier": "BLOCK_ILLEGAL",
"description": " a block end instruction moves execution below depth 0 {{}}`}` <= like this"
},
{
"code": 6,
"identifier": "CALL_ILLEGAL",
"description": "wasm tries to call a non-whitelisted function"
},
{
"code": 7,
"identifier": "CALL_INDIRECT",
"description": "wasm used call indirect instruction which is disallowed"
},
{
"code": 8,
"identifier": "CREATE_FLAG",
"description": "create operation requires hsoOVERRIDE"
},
{
"code": 9,
"identifier": "DELETE_FIELD",
"description": ""
},
{
"code": 10,
"identifier": "DELETE_FLAG",
"description": "delete operation requires hsoOVERRIDE"
},
{
"code": 11,
"identifier": "DELETE_NOTHING",
"description": "delete operation would delete nothing"
},
{
"code": 12,
"identifier": "EXPORTS_MISSING",
"description": "hook did not export *any* functions (should be cbak, hook)"
},
{
"code": 13,
"identifier": "EXPORT_CBAK_FUNC",
"description": "hook did not export correct func def int64_t cbak(uint32_t)"
},
{
"code": 14,
"identifier": "EXPORT_HOOK_FUNC",
"description": "hook did not export correct func def int64_t hook(uint32_t)"
},
{
"code": 15,
"identifier": "EXPORT_MISSING",
"description": "distinct from export*S*_missing, either hook or cbak is missing"
},
{
"identifier": "FLAGS_INVALID",
"code": 16,
"description": "HookSet flags were invalid for specified operation "
},
{
"identifier": "FUNCS_MISSING",
"code": 17,
"description": "hook did not include function code for any functions "
},
{
"identifier": "FUNC_PARAM_INVALID",
"code": 18,
"description": "parameter types may only be i32 i64 u32 u64 "
},
{
"identifier": "FUNC_RETURN_COUNT",
"code": 19,
"description": "a function type is defined in the wasm which returns > 1 return value "
},
{
"identifier": "FUNC_RETURN_INVALID",
"code": 20,
"description": "a function type does not return i32 i64 u32 or u64 "
},
{
"identifier": "FUNC_TYPELESS",
"code": 21,
"description": "hook defined hook/cbak but their type is not defined in wasm "
},
{
"identifier": "FUNC_TYPE_INVALID",
"code": 22,
"description": "malformed and illegal wasm in the func type section "
},
{
"identifier": "GRANTS_EMPTY",
"code": 23,
"description": "HookSet object contained an empty grants array (you should remove it) "
},
{
"identifier": "GRANTS_EXCESS",
"code": 24,
"description": "HookSet object cotnained a grants array with too many grants "
},
{
"identifier": "GRANTS_FIELD",
"code": 25,
"description": "HookSet object contained a grant without Authorize or HookHash "
},
{
"identifier": "GRANTS_ILLEGAL",
"code": 26,
"description": "Hookset object contained grants array which contained a non Grant object "
},
{
"identifier": "GUARD_IMPORT",
"code": 27,
"description": "guard import is missing "
},
{
"identifier": "GUARD_MISSING",
"code": 28,
"description": "guard call missing at top of loop "
},
{
"identifier": "GUARD_PARAMETERS",
"code": 29,
"description": "guard called but did not use constant expressions for params "
},
{
"identifier": "HASH_OR_CODE",
"code": 30,
"description": "HookSet object can contain only one of CreateCode and HookHash "
},
{
"identifier": "HOOKON_MISSING",
"code": 31,
"description": "HookSet object did not contain HookOn but should have "
},
{
"identifier": "HOOKS_ARRAY_BAD",
"code": 32,
"description": "attempt to HookSet with a Hooks array containing a non-Hook obj "
},
{
"identifier": "HOOKS_ARRAY_BLANK",
"code": 33,
"description": "all hook set objs were blank "
},
{
"identifier": "HOOKS_ARRAY_EMPTY",
"code": 34,
"description": "attempt to HookSet with an empty Hooks array "
},
{
"identifier": "HOOKS_ARRAY_MISSING",
"code": 35,
"description": "attempt to HookSet without a Hooks array "
},
{
"identifier": "HOOKS_ARRAY_TOO_BIG",
"code": 36,
"description": "attempt to HookSet with a Hooks array beyond the chain size limit "
},
{
"identifier": "HOOK_ADD",
"code": 37,
"description": "Informational: adding ltHook to directory "
},
{
"identifier": "HOOK_DEF_MISSING",
"code": 38,
"description": "attempt to reference a hook definition (by hash) that is not on ledger "
},
{
"identifier": "HOOK_DELETE",
"code": 39,
"description": "unable to delete ltHook from owner "
},
{
"identifier": "HOOK_INVALID_FIELD",
"code": 40,
"description": "HookSetObj contained an illegal/unexpected field "
},
{
"identifier": "HOOK_PARAMS_COUNT",
"code": 41,
"description": "hookset obj would create too many hook parameters "
},
{
"identifier": "HOOK_PARAM_SIZE",
"code": 42,
"description": "hookset obj sets a parameter or value that exceeds max allowable size "
},
{
"identifier": "IMPORTS_MISSING",
"code": 43,
"description": "hook must import guard, and accept/rollback "
},
{
"identifier": "IMPORT_ILLEGAL",
"code": 44,
"description": "attempted import of a non-whitelisted function "
},
{
"identifier": "IMPORT_MODULE_BAD",
"code": 45,
"description": "hook attempted to specify no or a bad import module "
},
{
"identifier": "IMPORT_MODULE_ENV",
"code": 46,
"description": "hook attempted to specify import module not named env "
},
{
"identifier": "IMPORT_NAME_BAD",
"code": 47,
"description": "import name was too short or too long "
},
{
"identifier": "INSTALL_FLAG",
"code": 48,
"description": "install operation requires hsoOVERRIDE "
},
{
"identifier": "INSTALL_MISSING",
"code": 49,
"description": "install operation specifies hookhash which doesn't exist on the ledger "
},
{
"identifier": "INSTRUCTION_COUNT",
"code": 50,
"description": "worst case execution instruction count as computed by HookSet "
},
{
"identifier": "INSTRUCTION_EXCESS",
"code": 51,
"description": "worst case execution instruction count was too large "
},
{
"identifier": "MEMORY_GROW",
"code": 52,
"description": "memory.grow instruction is present but disallowed "
},
{
"identifier": "NAMESPACE_MISSING",
"code": 53,
"description": "HookSet object lacked HookNamespace "
},
{
"identifier": "NSDELETE",
"code": 54,
"description": "Informational: a namespace is being deleted "
},
{
"identifier": "NSDELETE_ACCOUNT",
"code": 55,
"description": "nsdelete tried to delete ns from a non-existing account "
},
{
"identifier": "NSDELETE_COUNT",
"code": 56,
"description": "namespace state count less than 0 / overflow "
},
{
"identifier": "NSDELETE_DIR",
"code": 57,
"description": "could not delete directory node in ledger "
},
{
"identifier": "NSDELETE_DIRECTORY",
"code": 58,
"description": "nsdelete operation failed to delete ns directory "
},
{
"identifier": "NSDELETE_DIR_ENTRY",
"code": 59,
"description": "nsdelete operation failed due to bad entry in ns directory "
},
{
"identifier": "NSDELETE_ENTRY",
"code": 60,
"description": "nsdelete operation failed due to missing hook state entry "
},
{
"identifier": "NSDELETE_FIELD",
"code": 61
},
{
"identifier": "NSDELETE_FLAGS",
"code": 62
},
{
"identifier": "NSDELETE_NONSTATE",
"code": 63,
"description": "nsdelete operation failed due to the presence of a non-hookstate obj "
},
{
"identifier": "NSDELETE_NOTHING",
"code": 64,
"description": "hsfNSDELETE provided but nothing to delete "
},
{
"identifier": "OPERATION_INVALID",
"code": 65,
"description": "could not deduce an operation from the provided hookset obj "
},
{
"identifier": "OVERRIDE_MISSING",
"code": 66,
"description": "HookSet object was trying to update or delete a hook but lacked hsfOVERRIDE "
},
{
"identifier": "PARAMETERS_FIELD",
"code": 67,
"description": "HookParameters contained a HookParameter with an invalid key in it "
},
{
"identifier": "PARAMETERS_ILLEGAL",
"code": 68,
"description": "HookParameters contained something other than a HookParameter "
},
{
"identifier": "PARAMETERS_NAME",
"code": 69,
"description": "HookParameters contained a HookParameter which lacked ParameterName field "
},
{
"identifier": "PARAM_HOOK_CBAK",
"code": 70,
"description": "hook and cbak must take exactly one u32 parameter "
},
{
"identifier": "RETURN_HOOK_CBAK",
"code": 71,
"description": "hook and cbak must retunr i64 "
},
{
"identifier": "SHORT_HOOK",
"code": 72,
"description": "web assembly byte code ended abruptly "
},
{
"identifier": "TYPE_INVALID",
"code": 73,
"description": "malformed and illegal wasm specifying an illegal local var type "
},
{
"identifier": "WASM_BAD_MAGIC",
"code": 74,
"description": "wasm magic number missing or not wasm "
},
{
"identifier": "WASM_INVALID",
"code": 75,
"description": "set hook operation would set invalid wasm "
},
{
"identifier": "WASM_PARSE_LOOP",
"code": 76,
"description": "wasm section parsing resulted in an infinite loop "
},
{
"identifier": "WASM_SMOKE_TEST",
"code": 77,
"description": "Informational: first attempt to load wasm into wasm runtime "
},
{
"identifier": "WASM_TEST_FAILURE",
"code": 78,
"description": "the smoke test failed "
},
{
"identifier": "WASM_TOO_BIG",
"code": 79,
"description": "set hook would exceed maximum hook size "
},
{
"identifier": "WASM_TOO_SMALL",
"code": 80
},
{
"identifier": "WASM_VALIDATION",
"code": 81,
"description": "a generic error while parsing wasm, usually leb128 overflow"
},
{
"identifier": "HOOK_CBAK_DIFF_TYPES",
"code": 82,
"description": "hook and cbak function definitions were different"
}
]
+301
View File
@@ -0,0 +1,301 @@
[
{
"TransactionType": "AccountDelete",
"Account": "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm",
"Destination": {
"$type": "account",
"$value": ""
},
"DestinationTag": 13,
"Fee": "2000000",
"Sequence": 2470665,
"Flags": "2147483648"
},
{
"TransactionType": "AccountSet",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"Sequence": 5,
"Domain": "6578616D706C652E636F6D",
"SetFlag": 5,
"MessageKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB"
},
{
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
"TransactionType": "CheckCancel",
"CheckID": "49647F0D748DC3FE26BDACBC57F251AADEFFF391403EC9BF87C97F67E9977FB0",
"Fee": "12"
},
{
"Account": "rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy",
"TransactionType": "CheckCash",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"DeliverMin": {
"$value": "",
"$type": "amount.xrp"
},
"CheckID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334",
"Fee": "12"
},
{
"TransactionType": "CheckCreate",
"Account": "rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo",
"Destination": {
"$type": "account",
"$value": ""
},
"SendMax": "100000000",
"Expiration": 570113521,
"InvoiceID": "6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B",
"DestinationTag": 1,
"Fee": "12"
},
{
"TransactionType": "DepositPreauth",
"Account": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
"Authorize": "rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de",
"Fee": "10",
"Flags": "2147483648",
"Sequence": 2
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowCancel",
"Owner": {
"$type": "account",
"$value": ""
},
"OfferSequence": 7,
"Fee": "10"
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowCreate",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Destination": {
"$type": "account",
"$value": ""
},
"CancelAfter": 533257958,
"FinishAfter": 533171558,
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
"DestinationTag": 23480,
"SourceTag": 11747,
"Fee": "10"
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "EscrowFinish",
"Owner": {
"$type": "account",
"$value": ""
},
"OfferSequence": 7,
"Condition": "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100",
"Fulfillment": "A0028000",
"Fee": "10"
},
{
"TransactionType": "NFTokenMint",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenTaxon": 0,
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
},
{
"TransactionType": "NFTokenBurn",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenID": "000B013A95F14B0044F78A264E41713C64B5F89242540EE208C3098E00000D65"
},
{
"TransactionType": "NFTokenAcceptOffer",
"Account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"Fee": "10",
"NFTokenSellOffer": "A2FA1A9911FE2AEF83DAB05F437768E26A301EF899BD31EB85E704B3D528FF18",
"NFTokenBuyOffer": "4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862",
"NFTokenBrokerFee": "10"
},
{
"TransactionType": "NFTokenCancelOffer",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "10",
"NFTokenOffers": {
"$type": "json",
"$value": [
"4AAAEEA76E3C8148473CB3840CE637676E561FB02BD4CA22CA59729EA815B862"
]
}
},
{
"TransactionType": "NFTokenCreateOffer",
"Account": "rs8jBmmfpwgmrSPgwMsh7CvKRmRt1JTVSX",
"NFTokenID": "000100001E962F495F07A990F4ED55ACCFEEF365DBAA76B6A048C0A200000007",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Flags": "1",
"Destination": {
"$type": "account",
"$value": ""
},
"Owner": {
"$type": "account",
"$value": ""
},
"Fee": "10"
},
{
"TransactionType": "OfferCancel",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": "0",
"LastLedgerSequence": 7108629,
"OfferSequence": 6,
"Sequence": 7
},
{
"TransactionType": "OfferCreate",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": "0",
"LastLedgerSequence": 7108682,
"Sequence": 8,
"TakerGets": {
"$type": "amount.xrp",
"$value": "6000000"
},
"TakerPays": {
"$type": "amount.token",
"$value": {
"currency": "GKO",
"issuer": "ruazs5h1qEsqpke88pcqnaseXdm6od2xc",
"value": "2"
}
}
},
{
"TransactionType": "Payment",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Destination": {
"$type": "account",
"$value": ""
},
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Fee": "12",
"Flags": "2147483648",
"Sequence": 2
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "PaymentChannelCreate",
"Amount": {
"$value": "100",
"$type": "amount.xrp"
},
"Destination": {
"$type": "account",
"$value": ""
},
"SettleDelay": 86400,
"PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A",
"CancelAfter": 533171558,
"DestinationTag": 23480,
"SourceTag": 11747,
"Fee": "10"
},
{
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"TransactionType": "PaymentChannelFund",
"Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198",
"Amount": {
"$value": "200",
"$type": "amount.xrp"
},
"Expiration": 543171558,
"Fee": "10"
},
{
"Flags": "0",
"TransactionType": "SetRegularKey",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"RegularKey": "rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD"
},
{
"Flags": "0",
"TransactionType": "SignerListSet",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "12",
"SignerQuorum": 3,
"SignerEntries": {
"$type": "json",
"$value": [
{
"SignerEntry": {
"Account": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"SignerWeight": 2
}
},
{
"SignerEntry": {
"Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v",
"SignerWeight": 1
}
},
{
"SignerEntry": {
"Account": "raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n",
"SignerWeight": 1
}
}
]
}
},
{
"TransactionType": "TicketCreate",
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "10",
"Sequence": 381,
"TicketCount": 10
},
{
"TransactionType": "TrustSet",
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"Fee": "12",
"Flags": "262144",
"LastLedgerSequence": 8007750,
"LimitAmount": {
"$type": "amount.token",
"$value": {
"currency": "USD",
"issuer": "rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc",
"value": "100"
}
},
"Sequence": 12
},
{
"TransactionType": "Invoke",
"Destination": {
"$type": "account",
"$value": ""
},
"Fee": "12"
},
{
"TransactionType": "UriToken",
"Fee": "12",
"URI": "697066733A2F2F516D614374444B5A4656767666756676626479346573745A626851483744586831364354707631686F776D424779"
}
]
+13 -13
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { useEffect, useState } from 'react'
// Define general type for useWindowSize hook, which includes width and height
interface Size {
width: number | undefined;
height: number | undefined;
width: number | undefined
height: number | undefined
}
// Hook
@@ -12,25 +12,25 @@ function useWindowSize(): Size {
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState<Size>({
width: undefined,
height: undefined,
});
height: undefined
})
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
height: window.innerHeight
})
}
// Add event listener
window.addEventListener("resize", handleResize);
window.addEventListener('resize', handleResize)
// Call handler right away so state gets updated with initial window size
handleResize();
handleResize()
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
return () => window.removeEventListener('resize', handleResize)
}, []) // Empty array ensures that effect is only run on mount
return windowSize
}
export default useWindowSize;
export default useWindowSize
-1
View File
@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
+14 -1
View File
@@ -2,6 +2,19 @@
module.exports = {
reactStrictMode: true,
images: {
domains: ['avatars.githubusercontent.com'],
domains: ['avatars.githubusercontent.com']
},
webpack(config, { isServer }) {
config.resolve.alias['vscode'] = require.resolve(
'@codingame/monaco-languageclient/lib/vscode-compatibility'
)
if (!isServer) {
config.resolve.fallback.fs = false
}
config.module.rules.push({
test: [/\.md$/, /hook-bundle\.js$/],
use: 'raw-loader'
})
return config
}
}
+61 -8
View File
@@ -6,30 +6,83 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"format": "prettier --write .",
"postinstall": "patch-package && yarn run postinstall-postinstall",
"postinstall-postinstall": "./node_modules/.bin/browserify -r ripple-binary-codec -r ripple-keypairs -r ripple-address-codec -r ripple-secret-codec -r ./node_modules/xrpl-accountlib/dist/index.js:xrpl-accountlib -o node_modules/xrpl-accountlib/dist/browser.hook-bundle.js"
},
"dependencies": {
"@monaco-editor/react": "^4.3.1",
"@codingame/monaco-jsonrpc": "^0.3.1",
"@codingame/monaco-languageclient": "^0.17.0",
"@monaco-editor/react": "^4.4.5",
"@octokit/core": "^3.5.1",
"@radix-ui/colors": "^0.1.7",
"@radix-ui/react-alert-dialog": "^0.1.1",
"@radix-ui/react-context-menu": "^0.1.6",
"@radix-ui/react-dialog": "^0.1.1",
"@radix-ui/react-dropdown-menu": "^0.1.1",
"@stitches/react": "^1.2.5",
"monaco-editor": "^0.29.1",
"@radix-ui/react-dropdown-menu": "^0.1.6",
"@radix-ui/react-id": "^0.1.1",
"@radix-ui/react-label": "^0.1.5",
"@radix-ui/react-popover": "^0.1.6",
"@radix-ui/react-switch": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7",
"@stitches/react": "^1.2.8",
"base64-js": "^1.5.1",
"comment-parser": "^1.3.1",
"dinero.js": "^1.9.1",
"file-saver": "^2.0.5",
"filesize": "^8.0.7",
"javascript-time-ago": "^2.3.11",
"jszip": "^3.7.1",
"lodash.uniqby": "^4.7.0",
"lodash.xor": "^4.5.0",
"monaco-editor": "^0.33.0",
"next": "^12.0.4",
"next-auth": "^4.0.0-beta.5",
"next-themes": "^0.0.15",
"next-auth": "^4.10.3",
"next-plausible": "^3.2.0",
"next-themes": "^0.1.1",
"normalize-url": "^7.0.2",
"octokit": "^1.7.0",
"pako": "^2.0.4",
"patch-package": "^6.4.7",
"phosphor-react": "^1.3.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1",
"re-resizable": "^6.9.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hook-form": "^7.28.0",
"react-hot-keys": "^2.7.1",
"react-hot-toast": "^2.1.1",
"valtio": "^1.2.5"
"react-markdown": "^8.0.3",
"react-new-window": "^0.2.1",
"react-select": "^5.2.1",
"react-split": "^2.0.14",
"react-stay-scrolled": "^7.4.0",
"react-time-ago": "^7.1.9",
"reconnecting-websocket": "^4.4.0",
"regexify-string": "^1.0.17",
"valtio": "^1.2.5",
"vscode-languageserver": "^7.0.0",
"vscode-uri": "^3.0.2",
"wabt": "^1.0.30",
"xrpl-accountlib": "^1.6.1",
"xrpl-client": "^2.0.2"
},
"devDependencies": {
"@types/dinero.js": "^1.9.0",
"@types/file-saver": "^2.0.4",
"@types/lodash.uniqby": "^4.7.6",
"@types/lodash.xor": "^4.5.6",
"@types/pako": "^1.0.2",
"@types/react": "17.0.31",
"browserify": "^17.0.0",
"eslint": "7.32.0",
"eslint-config-next": "11.1.2",
"raw-loader": "^4.0.2",
"typescript": "4.4.4"
},
"resolutions": {
"ripple-binary-codec": "=1.4.2"
}
}
+134 -24
View File
@@ -1,31 +1,141 @@
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
import { ThemeProvider } from "next-themes";
import { Toaster } from "react-hot-toast";
import { useEffect } from 'react'
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Head from 'next/head'
import { SessionProvider } from 'next-auth/react'
import { ThemeProvider } from 'next-themes'
import { Toaster } from 'react-hot-toast'
import { useRouter } from 'next/router'
import { IdProvider } from '@radix-ui/react-id'
import PlausibleProvider from 'next-plausible'
import { darkTheme } from "../stitches.config";
import Navigation from "../components/Navigation";
import { darkTheme, css } from '../stitches.config'
import Navigation from '../components/Navigation'
import { fetchFiles } from '../state/actions'
import state from '../state'
import TimeAgo from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en.json'
import { useSnapshot } from 'valtio'
import Alert from '../components/AlertDialog'
import { Button, Flex } from '../components'
import { ChatCircleText } from 'phosphor-react'
TimeAgo.setDefaultLocale(en.locale)
TimeAgo.addLocale(en)
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
const router = useRouter()
const slug = router.query?.slug
const gistId = (Array.isArray(slug) && slug[0]) ?? null
const origin = 'https://xrpl-hooks-ide.vercel.app' // TODO: Change when site is deployed
const shareImg = '/share-image.png'
const snap = useSnapshot(state)
useEffect(() => {
if (gistId && router.isReady) {
fetchFiles(gistId)
} else {
if (
!gistId &&
router.isReady &&
router.pathname.includes('/develop') &&
!snap.files.length &&
!snap.mainModalShowed
) {
state.mainModalOpen = true
state.mainModalShowed = true
}
}
}, [gistId, router.isReady, router.pathname, snap.files, snap.mainModalShowed])
return (
<>
<SessionProvider session={session}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
value={{
light: "light",
dark: darkTheme.className,
}}
>
<Navigation />
<Component {...pageProps} />
<Toaster />
</ThemeProvider>
</SessionProvider>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="format-detection" content="telephone=no" />
<meta property="og:url" content={`${origin}${router.asPath}`} />
<title>XRPL Hooks Builder</title>
<meta property="og:title" content="XRPL Hooks Builder" />
<meta name="twitter:title" content="XRPL Hooks Builder" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@XRPLF" />
<meta
name="description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
/>
<meta
property="og:description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
/>
<meta
name="twitter:description"
content="Hooks Builder, add smart contract functionality to the XRP Ledger."
/>
<meta property="og:image" content={`${origin}${shareImg}`} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:image" content={`${origin}${shareImg}`} />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161618" />
<meta name="application-name" content="XRPL Hooks Builder" />
<meta name="msapplication-TileColor" content="#c10ad0" />
<meta name="theme-color" content="#161618" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FDFCFD" media="(prefers-color-scheme: light)" />
</Head>
<IdProvider>
<SessionProvider session={session}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
value={{
light: 'light',
dark: darkTheme.className
}}
>
<PlausibleProvider domain="hooks-builder.xrpl.org" trackOutboundLinks>
<Navigation />
<Component {...pageProps} />
<Toaster
toastOptions={{
className: css({
backgroundColor: '$mauve1',
color: '$mauve10',
fontSize: '$sm',
zIndex: 9999,
'.dark &': {
backgroundColor: '$mauve4',
color: '$mauve12'
}
})()
}}
/>
<Alert />
<Flex
as="a"
href="https://github.com/XRPLF/Hooks/discussions"
target="_blank"
rel="noopener noreferrer"
css={{ position: 'fixed', right: '$4', bottom: '$4' }}
>
<Button size="sm" variant="primary" outline>
<ChatCircleText size={14} style={{ marginRight: '0px' }} />
Bugs & Discussions
</Button>
</Flex>
</PlausibleProvider>
</ThemeProvider>
</SessionProvider>
</IdProvider>
</>
);
)
}
export default MyApp;
export default MyApp
+10 -24
View File
@@ -1,48 +1,34 @@
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from "next/document";
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { globalStyles, getCssText } from "../stitches.config";
import { globalStyles, getCssText } from '../stitches.config'
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
const initialProps = await Document.getInitialProps(ctx)
return initialProps;
return initialProps
}
render() {
globalStyles();
globalStyles()
return (
<Html>
<Head>
<meta name="description" content="Playground for XRPL Hooks" />
<link rel="icon" href="/favicon.ico" />
<style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&family=Work+Sans:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<style
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
)
}
}
export default MyDocument;
export default MyDocument
+7 -8
View File
@@ -1,11 +1,10 @@
import type { NextRequest, NextFetchEvent } from 'next/server';
import { NextResponse as Response } from 'next/server';
import { getToken } from "next-auth/jwt"
import type { NextRequest, NextFetchEvent } from 'next/server'
import { NextResponse as Response } from 'next/server'
export default function middleware(req: NextRequest, ev: NextFetchEvent) {
if (req.nextUrl.pathname === "/") {
return Response.redirect("/develop");
if (req.nextUrl.pathname === '/') {
const url = req.nextUrl.clone()
url.pathname = '/develop'
return Response.redirect(url)
}
}
}
+15 -18
View File
@@ -1,5 +1,4 @@
import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import NextAuth from 'next-auth'
export default NextAuth({
// Configure one or more authentication providers
@@ -11,40 +10,38 @@ export default NextAuth({
// scope: 'user,gist'
// }),
{
id: "github",
name: "GitHub",
type: "oauth",
id: 'github',
name: 'GitHub',
type: 'oauth',
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
authorization: "https://github.com/login/oauth/authorize?scope=read:user+user:email+gist",
token: "https://github.com/login/oauth/access_token",
userinfo: "https://api.github.com/user",
authorization: 'https://github.com/login/oauth/authorize?scope=read:user+user:email+gist',
token: 'https://github.com/login/oauth/access_token',
userinfo: 'https://api.github.com/user',
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name || profile.login,
username: profile.login,
email: profile.email,
image: profile.avatar_url,
image: profile.avatar_url
}
},
}
}
// ...add more providers here
],
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
if (account && account.access_token) {
token.accessToken = account.access_token;
token.username = user?.username || '';
token.accessToken = account.access_token
token.username = user?.username || ''
}
return token
},
async session({ session, token }) {
session.accessToken = token.accessToken as string;
const user = { ...session.user, username: token.username };
session['user']['username'] = token.username as string;
session.accessToken = token.accessToken as string
session['user']['username'] = token.username as string
return session
}
},
})
}
})
+47
View File
@@ -0,0 +1,47 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
interface ErrorResponse {
error: string
}
export interface Faucet {
address: string
secret: string
xrp: number
hash: string
code: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Faucet | ErrorResponse>
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed!' })
}
const { account } = req.query
const ip = Array.isArray(req?.headers?.['x-real-ip'])
? req?.headers?.['x-real-ip'][0]
: req?.headers?.['x-real-ip']
try {
const response = await fetch(
`https://${process.env.NEXT_PUBLIC_TESTNET_URL}/newcreds?account=${account ? account : ''}`,
{
method: 'POST',
headers: {
'x-forwarded-for': ip || ''
}
}
)
const json: Faucet | ErrorResponse = await response.json()
if ('error' in json) {
return res.status(429).json(json)
}
return res.status(200).json(json)
} catch (err) {
console.log(err)
return res.status(500).json({ error: 'Server error' })
}
return res.status(500).json({ error: 'Not able to create faucet, try again' })
}
+1 -4
View File
@@ -5,9 +5,6 @@ type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
res.status(200).json({ name: 'John Doe' })
}
+15
View File
@@ -0,0 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { url, opts } = req.body
const r = await fetch(url, opts)
if (!r.ok) throw r.statusText
const data = await r.json()
return res.json(data)
} catch (error) {
console.warn(error)
return res.status(500).json({ message: 'Something went wrong!' })
}
}
+59
View File
@@ -0,0 +1,59 @@
import dynamic from 'next/dynamic'
import React from 'react'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import state from '../../state'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
const DeployEditor = dynamic(() => import('../../components/DeployEditor'), {
ssr: false
})
const Accounts = dynamic(() => import('../../components/Accounts'), {
ssr: false
})
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const Deploy = () => {
const { deployLogs } = useSnapshot(state)
return (
<Split
direction="vertical"
gutterSize={4}
gutterAlign="center"
sizes={getSplit('deployVertical') || [40, 60]}
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('deployVertical', e)}
>
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
<DeployEditor />
</main>
<Split
direction="horizontal"
sizes={getSplit('deployHorizontal') || [50, 50]}
minSize={[320, 160]}
gutterSize={4}
gutterAlign="center"
style={{
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
onDragEnd={e => saveSplit('deployHorizontal', e)}
>
<div style={{ alignItems: 'stretch', display: 'flex' }}>
<Accounts />
</div>
<div>
<LogBox title="Deploy Log" logs={deployLogs} clearLog={() => (state.deployLogs = [])} />
</div>
</Split>
</Split>
)
}
export default Deploy
-9
View File
@@ -1,9 +0,0 @@
import Container from "../../components/Container";
const Deploy = () => {
return (
<Container css={{ py: "$10" }}>This will be the deploy page</Container>
);
};
export default Deploy;
+251
View File
@@ -0,0 +1,251 @@
import { Label } from '@radix-ui/react-label'
import type { NextPage } from 'next'
import dynamic from 'next/dynamic'
import { FileJs, Gear, Play } from 'phosphor-react'
import Hotkeys from 'react-hot-keys'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import { ButtonGroup, Flex } from '../../components'
import Box from '../../components/Box'
import Button from '../../components/Button'
import Popover from '../../components/Popover'
import RunScript from '../../components/RunScript'
import state, { IFile } from '../../state'
import { compileCode } from '../../state/actions'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
import { styled } from '../../stitches.config'
import { getFileExtention } from '../../utils/helpers'
const HooksEditor = dynamic(() => import('../../components/HooksEditor'), {
ssr: false
})
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const OptimizationText = () => (
<span>
Specify which optimization level to use for compiling. For example -O0 means “no optimization”:
this level compiles the fastest and generates the most debuggable code. -O2 means moderate level
of optimization which enables most optimizations. Read more about the options from{' '}
<a
className="link"
rel="noopener noreferrer"
target="_blank"
href="https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-o0"
>
clang documentation
</a>
.
</span>
)
const StyledOptimizationText = styled(OptimizationText, {
color: '$mauve12 !important',
fontSize: '200px',
'span a.link': {
color: 'red'
}
})
const CompilerSettings = () => {
const snap = useSnapshot(state)
return (
<Flex css={{ minWidth: 200, flexDirection: 'column', gap: '$5' }}>
<Box>
<Label
style={{
flexDirection: 'row',
display: 'flex'
}}
>
Optimization level{' '}
<Popover
css={{
maxWidth: '240px',
lineHeight: '1.3',
a: {
color: '$purple11'
},
'.dark &': {
backgroundColor: '$black !important',
'.arrow': {
fill: '$colors$black'
}
}
}}
content={<StyledOptimizationText />}
>
<Flex
css={{
position: 'relative',
top: '-1px',
ml: '$1',
backgroundColor: '$mauve8',
borderRadius: '$full',
cursor: 'pointer',
width: '16px',
height: '16px',
alignItems: 'center',
justifyContent: 'center'
}}
>
?
</Flex>
</Popover>
</Label>
<ButtonGroup css={{ mt: '$2', fontFamily: '$monospace' }}>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O0'}
onClick={() => (state.compileOptions.optimizationLevel = '-O0')}
>
-O0
</Button>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O1'}
onClick={() => (state.compileOptions.optimizationLevel = '-O1')}
>
-O1
</Button>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O2'}
onClick={() => (state.compileOptions.optimizationLevel = '-O2')}
>
-O2
</Button>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O3'}
onClick={() => (state.compileOptions.optimizationLevel = '-O3')}
>
-O3
</Button>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-O4'}
onClick={() => (state.compileOptions.optimizationLevel = '-O4')}
>
-O4
</Button>
<Button
css={{ fontFamily: '$monospace' }}
outline={snap.compileOptions.optimizationLevel !== '-Os'}
onClick={() => (state.compileOptions.optimizationLevel = '-Os')}
>
-Os
</Button>
</ButtonGroup>
</Box>
</Flex>
)
}
const Home: NextPage = () => {
const snap = useSnapshot(state)
const activeFile = snap.files[snap.active] as IFile | undefined
const activeFileExt = getFileExtention(activeFile?.name)
const canCompile = activeFileExt === 'c' || activeFileExt === 'wat'
return (
<Split
direction="vertical"
sizes={getSplit('developVertical') || [70, 30]}
minSize={[100, 100]}
gutterAlign="center"
gutterSize={4}
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('developVertical', e)}
>
<main style={{ display: 'flex', flex: 1, position: 'relative' }}>
<HooksEditor />
{canCompile && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
>
<Flex
css={{
position: 'absolute',
bottom: '$4',
left: '$4',
alignItems: 'center',
display: 'flex',
cursor: 'pointer',
gap: '$2'
}}
>
<Button
variant="primary"
uppercase
disabled={!snap.files.length}
isLoading={snap.compiling}
onClick={() => compileCode(snap.active)}
>
<Play weight="bold" size="16px" />
Compile to Wasm
</Button>
<Popover content={<CompilerSettings />}>
<Button variant="primary" css={{ px: '10px' }}>
<Gear size="16px" />
</Button>
</Popover>
</Flex>
</Hotkeys>
)}
{activeFileExt === 'js' && (
<Hotkeys
keyName="command+b,ctrl+b"
onKeyDown={() => !snap.compiling && snap.files.length && compileCode(snap.active)}
>
<Flex
css={{
position: 'absolute',
bottom: '$4',
left: '$4',
alignItems: 'center',
display: 'flex',
cursor: 'pointer',
gap: '$2'
}}
>
<RunScript file={activeFile as IFile} />
</Flex>
</Hotkeys>
)}
</main>
<Flex css={{ width: '100%' }}>
<Flex
css={{
flex: 1,
background: '$mauve1',
position: 'relative',
borderRight: '1px solid $mauve8'
}}
>
<LogBox title="Development Log" clearLog={() => (state.logs = [])} logs={snap.logs} />
</Flex>
{activeFileExt === 'js' && (
<Flex
css={{
flex: 1
}}
>
<LogBox
Icon={FileJs}
title="Script Log"
logs={snap.scriptLogs}
clearLog={() => (state.scriptLogs = [])}
/>
</Flex>
)}
</Flex>
</Split>
)
}
export default Home
-27
View File
@@ -1,27 +0,0 @@
import type { NextPage } from "next";
import Head from "next/head";
import dynamic from "next/dynamic";
const HooksEditor = dynamic(() => import("../../components/HooksEditor"), {
ssr: false,
});
const Footer = dynamic(() => import("../../components/Footer"), {
ssr: false,
});
const Home: NextPage = () => {
return (
<>
<Head>
<title>XRPL Hooks Playground</title>
</Head>
<main style={{ display: "flex", flex: 1 }}>
<HooksEditor />
</main>
<Footer />
</>
);
};
export default Home;
+3 -3
View File
@@ -1,5 +1,5 @@
const Home = () => {
return <p>homepage</p>;
};
return <p>homepage</p>
}
export default Home;
export default Home
+37
View File
@@ -0,0 +1,37 @@
import { useEffect } from 'react'
import { signIn, useSession } from 'next-auth/react'
import Box from '../components/Box'
import Spinner from '../components/Spinner'
const SignInPage = () => {
const { data: session, status } = useSession()
useEffect(() => {
if (status !== 'loading' && !session) void signIn('github', { redirect: false })
if (status !== 'loading' && session) window.close()
}, [session, status])
return (
<Box
css={{
display: 'flex',
backgroundColor: '$mauve1',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
zIndex: 9999,
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center',
gap: '$2'
}}
>
Logging in <Spinner />
</Box>
)
}
export default SignInPage
+167
View File
@@ -0,0 +1,167 @@
import dynamic from 'next/dynamic'
import Split from 'react-split'
import { useSnapshot } from 'valtio'
import { Box, Container, Flex, Tab, Tabs } from '../../components'
import Transaction from '../../components/Transaction'
import state, { renameTxState } from '../../state'
import { getSplit, saveSplit } from '../../state/actions/persistSplits'
import { transactionsState, modifyTxState } from '../../state'
import { useEffect, useState } from 'react'
import { FileJs } from 'phosphor-react'
import RunScript from '../../components/RunScript'
const DebugStream = dynamic(() => import('../../components/DebugStream'), {
ssr: false
})
const LogBox = dynamic(() => import('../../components/LogBox'), {
ssr: false
})
const Accounts = dynamic(() => import('../../components/Accounts'), {
ssr: false
})
const Test = () => {
// This and useEffect is here to prevent useLayoutEffect warnings from react-split
const [showComponent, setShowComponent] = useState(false)
const { transactionLogs } = useSnapshot(state)
const { transactions, activeHeader } = useSnapshot(transactionsState)
const snap = useSnapshot(state)
useEffect(() => {
setShowComponent(true)
}, [])
if (!showComponent) {
return null
}
const hasScripts = Boolean(snap.files.filter(f => f.name.toLowerCase()?.endsWith('.js')).length)
const renderNav = () => (
<Flex css={{ gap: '$3' }}>
{snap.files
.filter(f => f.name.endsWith('.js'))
.map(file => (
<RunScript file={file} key={file.name} />
))}
</Flex>
)
return (
<Container css={{ px: 0 }}>
<Split
direction="vertical"
sizes={
hasScripts && getSplit('testVertical')?.length === 2
? [50, 20, 30]
: hasScripts
? [50, 20, 50]
: [50, 50]
}
gutterSize={4}
gutterAlign="center"
style={{ height: 'calc(100vh - 60px)' }}
onDragEnd={e => saveSplit('testVertical', e)}
>
<Flex
row
fluid
css={{
justifyContent: 'center',
p: '$3 $2'
}}
>
<Split
direction="horizontal"
sizes={getSplit('testHorizontal') || [50, 50]}
minSize={[180, 320]}
gutterSize={4}
gutterAlign="center"
style={{
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
onDragEnd={e => saveSplit('testHorizontal', e)}
>
<Box css={{ width: '55%', px: '$2' }}>
<Tabs
label="Transaction"
activeHeader={activeHeader}
// TODO make header a required field
onChangeActive={(idx, header) => {
if (header) transactionsState.activeHeader = header
}}
keepAllAlive
defaultExtension="json"
allowedExtensions={['json']}
onCreateNewTab={header => modifyTxState(header, {})}
onRenameTab={(idx, nwName, oldName = '') => renameTxState(oldName, nwName)}
onCloseTab={(idx, header) => header && modifyTxState(header, undefined)}
>
{transactions.map(({ header, state }) => (
<Tab key={header} header={header}>
<Transaction state={state} header={header} />
</Tab>
))}
</Tabs>
</Box>
<Box css={{ width: '45%', mx: '$2', height: '100%' }}>
<Accounts card hideDeployBtn showHookStats />
</Box>
</Split>
</Flex>
{hasScripts ? (
<Flex
as="div"
css={{
borderTop: '1px solid $mauve6',
background: '$mauve1',
flexDirection: 'column'
}}
>
<LogBox
Icon={FileJs}
title="Helper scripts"
logs={snap.scriptLogs}
clearLog={() => (state.scriptLogs = [])}
renderNav={renderNav}
/>
</Flex>
) : null}
<Flex>
<Split
direction="horizontal"
sizes={[50, 50]}
minSize={[320, 160]}
gutterSize={4}
gutterAlign="center"
style={{
display: 'flex',
flexDirection: 'row',
width: '100%',
height: '100%'
}}
>
<Box
css={{
borderRight: '1px solid $mauve8',
height: '100%'
}}
>
<LogBox
title="Development Log"
logs={transactionLogs}
clearLog={() => (state.transactionLogs = [])}
/>
</Box>
<Box css={{ height: '100%' }}>
<DebugStream />
</Box>
</Split>
</Flex>
</Split>
</Container>
)
}
export default Test
-7
View File
@@ -1,7 +0,0 @@
import Container from "../../components/Container";
const Test = () => {
return <Container css={{ py: "$10" }}>This will be the test page</Container>;
};
export default Test;
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#161618</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="137" height="306" viewBox="0 0 137 306" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect y="-64" width="146" height="370" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_133_2138" transform="translate(0 -0.000887603) scale(0.00369004 0.00145607)"/>
</pattern>
<image id="image0_133_2138" width="271" height="688" xlink:href="data:image/png;base64,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 truncated
</defs>
</svg>

After

Width:  |  Height:  |  Size: 138 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="72" height="162" viewBox="0 0 72 162" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect opacity="0.5" y="-63" width="89" height="225" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_133_6595" transform="translate(0 -0.00210742) scale(0.00369004 0.00145961)"/>
</pattern>
<image id="image0_133_6595" width="271" height="688" xlink:href="data:image/png;base64,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 truncated
</defs>
</svg>

After

Width:  |  Height:  |  Size: 153 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="49" height="197" viewBox="0 0 49 197" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect opacity="0.5" x="49" y="225" width="89" height="225" transform="rotate(-180 49 225)" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_133_6596" transform="translate(0 -0.00210742) scale(0.00369004 0.00145961)"/>
</pattern>
<image id="image0_133_6596" width="271" height="688" xlink:href="data:image/png;base64,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 truncated
</defs>
</svg>

After

Width:  |  Height:  |  Size: 153 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="141" height="232" viewBox="0 0 141 232" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="141" height="357" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_133_2136" transform="translate(0 -0.00134888) scale(0.00369004 0.00145741)"/>
</pattern>
<image id="image0_133_2136" width="271" height="688" xlink:href="data:image/png;base64,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 truncated
</defs>
</svg>

After

Width:  |  Height:  |  Size: 134 KiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="588.000000pt" height="588.000000pt" viewBox="0 0 588.000000 588.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,588.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3843 4097 c-381 -380 -737 -736 -791 -790 l-99 -99 -940 1 -940 0
-204 -211 c-112 -116 -228 -235 -257 -265 -29 -30 -51 -57 -48 -60 2 -3 542
-5 1198 -5 l1193 0 799 -799 799 -799 380 0 c209 0 378 2 376 5 -2 2 -422 423
-933 934 l-928 929 921 920 c507 507 921 923 921 927 0 3 -170 5 -377 5 l-378
0 -692 -693z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"name": "Hooks Builder",
"short_name": "Hooks Builder",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#161618",
"background_color": "#161618",
"display": "standalone"
}
-4
View File
@@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
declare module '*.md' {
const content: string
export default content
}
declare module '*.hook-bundle.js' {
const content: string
export default content
}
-172
View File
@@ -1,172 +0,0 @@
import { proxy, subscribe } from 'valtio';
import { devtools } from 'valtio/utils';
import { Octokit } from '@octokit/core';
import type monaco from 'monaco-editor';
import toast from 'react-hot-toast';
const octokit = new Octokit();
interface File {
name: string;
language: string;
content: string;
}
interface IState {
files: File[],
active: number;
loading: boolean;
compiling: boolean;
logs: {
type: 'error' | 'warning' | 'log',
message: string;
}[];
editorCtx?: typeof monaco.editor;
editorSettings: {
tabSize: number;
}
}
let localStorageState: null | string = null;
let initialState = {
files: [],
active: 0,
loading: false,
compiling: false,
logs: [],
editorCtx: undefined,
editorSettings: {
tabSize: 2
}
}
// Check if there's a persited state in localStorage
if (typeof window !== 'undefined') {
try {
localStorageState = localStorage.getItem('hooksIdeState');
} catch (err) {
console.log(`localStorage state broken`);
localStorage.removeItem('hooksIdeState');
}
}
if (localStorageState) {
initialState = JSON.parse(localStorageState);
}
// Initialize state
export const state = proxy<IState>(initialState);
// Fetch content from Githug Gists
export const fetchFiles = (gistId: string) => {
if (gistId) {
state.logs.push({ type: 'log', message: `Fetching Gist with id: ${gistId}` });
octokit.request("GET /gists/{gist_id}", { gist_id: gistId }).then(res => {
if (res.data.files && Object.keys(res.data.files).length > 0) {
const files = Object.keys(res.data.files).map(filename => ({
name: res.data.files?.[filename]?.filename || 'noname.c',
language: res.data.files?.[filename]?.language?.toLowerCase() || '',
content: res.data.files?.[filename]?.content || ''
}))
state.loading = false;
if (files.length > 0) {
state.logs.push({ type: 'log', message: 'Fetched successfully ✅' })
state.files = files;
return
}
return
}
}).catch(err => {
state.loading = false;
state.logs.push({ type: 'error', message: `Couldn't find Gist with id: ${gistId}` })
return
})
return
}
state.loading = false;
// return state.files = initFiles
}
export const updateEditorSettings = (editorSettings: IState['editorSettings']) => {
state.editorCtx?.getModels().forEach(model => {
console.log(model.uri)
model.updateOptions({
...editorSettings
})
});
return state.editorSettings = editorSettings;
}
export const saveFile = (value: string) => {
const editorModels = state.editorCtx?.getModels();
const currentModel = editorModels?.find(editorModel => editorModel.uri.path === `/${state.files[state.active].name}`);
console.log(currentModel?.getValue())
if (state.files.length > 0) {
console.log('häää')
state.files[state.active].content = currentModel?.getValue() || '';
}
console.log(state.files[state.active])
toast.success('Saved successfully', { position: 'bottom-center' })
}
export const createNewFile = (name: string) => {
const emptyFile: File = { name, language: 'c', content: "" };
state.files.push(emptyFile)
state.active = state.files.length - 1;
}
export const compileCode = async (activeId: number) => {
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
throw Error('Missing env!')
};
if (state.compiling) {
return;
}
state.compiling = true;
try {
const res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"output": "wasm",
"compress": true,
"files": [
{
"type": "c",
"name": state.files[activeId].name,
"options": "-g -O3",
"src": state.files[activeId].content
}
]
})
});
const json = await res.json();
state.compiling = false;
if (!json.success) {
state.logs.push({ type: 'error', message: json.message })
if (json.tasks && json.tasks.length > 0) {
json.tasks.forEach((task: any) => {
if (!task.success) {
state.logs.push({ type: 'error', message: task?.console })
}
})
}
return toast.error(`Couldn't compile!`, { position: 'bottom-center' });
}
state.logs.push({ type: 'log', message: 'Compiled successfully ✅' })
toast.success('Compiled successfully!', { position: 'bottom-center' });
} catch (err) {
console.log(err)
state.logs.push({ type: 'error', message: 'Error occured while compiling!' })
state.compiling = false;
}
}
const unsub = devtools(state, 'Files State');
subscribe(state, () => {
const { editorCtx, ...storedState } = state;
localStorage.setItem('hooksIdeState', JSON.stringify(storedState))
});
+86
View File
@@ -0,0 +1,86 @@
import toast from 'react-hot-toast'
import state, { FaucetAccountRes } from '../index'
import fetchAccountInfo from '../../utils/accountInfo';
export const names = [
'Alice',
'Bob',
'Carol',
'Carlos',
'Charlie',
'Dan',
'Dave',
'David',
'Faythe',
'Frank',
'Grace',
'Heidi',
'Judy',
'Olive',
'Peggy',
'Walter'
]
/* This function adds faucet account to application global state.
* It calls the /api/faucet endpoint which in send a HTTP POST to
* https://hooks-testnet.xrpl-labs.com/newcreds and it returns
* new account with 10 000 XRP. Hooks Testnet /newcreds endpoint
* is protected with CORS so that's why we did our own endpoint
*/
export const addFaucetAccount = async (name?: string, showToast: boolean = false) => {
if (typeof window === undefined) return
const toastId = showToast ? toast.loading('Creating account') : ''
const res = await fetch(`${window.location.origin}/api/faucet`, {
method: 'POST'
})
const json: FaucetAccountRes | { error: string } = await res.json()
if ('error' in json) {
if (!showToast) return;
return toast.error(json.error, { id: toastId })
}
const currNames = state.accounts.map(acc => acc.name)
const info = await fetchAccountInfo(json.address, { silent: true })
state.accounts.push({
name: name || names.filter(name => !currNames.includes(name))[0],
xrp: (json.xrp || 0 * 1000000).toString(),
address: json.address,
secret: json.secret,
sequence: info?.Sequence || 1,
hooks: [],
isLoading: false,
version: '2'
})
if (showToast) {
toast.success('New account created', { id: toastId })
}
}
// fetch initial faucets
; (async function fetchFaucets() {
if (typeof window !== 'undefined') {
if (state.accounts.length === 0) {
await addFaucetAccount()
// setTimeout(() => {
// addFaucetAccount();
// }, 10000);
}
}
})()
export const addFunds = async (address: string) => {
const toastId = toast.loading('Requesting funds')
const res = await fetch(`${window.location.origin}/api/faucet?account=${address}`, {
method: 'POST'
})
const json: FaucetAccountRes | { error: string } = await res.json()
if ('error' in json) {
return toast.error(json.error, { id: toastId })
} else {
toast.success(`Funds added (${json.xrp} XRP)`, { id: toastId })
const currAccount = state.accounts.find(acc => acc.address === address)
if (currAccount) {
currAccount.xrp = (Number(currAccount.xrp) + json.xrp * 1000000).toString()
}
}
}
+171
View File
@@ -0,0 +1,171 @@
import toast from 'react-hot-toast'
import Router from 'next/router'
import state from '../index'
import { saveFile } from './saveFile'
import { decodeBinary } from '../../utils/decodeBinary'
import { ref } from 'valtio'
/* compileCode sends the code of the active file to compile endpoint
* If all goes well you will get base64 encoded wasm file back with
* some extra logging information if we can provide it. This function
* also decodes the returned wasm and creates human readable WAT file
* out of it and store both in global state.
*/
export const compileCode = async (activeId: number) => {
// Save the file to global state
saveFile(false, activeId)
const file = state.files[activeId]
if (file.name.endsWith('.wat')) {
return compileWat(activeId)
}
if (!process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT) {
throw Error('Missing env!')
}
// Bail out if we're already compiling
if (state.compiling) {
// if compiling is ongoing return // TODO Inform user about it.
return
}
// Set loading state to true
state.compiling = true
state.logs = []
try {
file.containsErrors = false
let res: Response
try {
res = await fetch(process.env.NEXT_PUBLIC_COMPILE_API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
output: 'wasm',
compress: true,
strip: state.compileOptions.strip,
files: [
{
type: 'c',
options: state.compileOptions.optimizationLevel || '-O2',
name: file.name,
src: file.content
}
]
})
})
} catch (error) {
throw Error('Something went wrong, check your network connection and try again!')
}
const json = await res.json()
state.compiling = false
if (!json.success) {
const errors = [json.message]
if (json.tasks && json.tasks.length > 0) {
json.tasks.forEach((task: any) => {
if (!task.success) {
errors.push(task?.console)
}
})
}
throw errors
}
try {
// Decode base64 encoded wasm that is coming back from the endpoint
const bufferData = await decodeBinary(json.output)
// Import wabt from and create human readable version of wasm file and
// put it into state
const ww = await (await import('wabt')).default()
const myModule = ww.readWasm(new Uint8Array(bufferData), {
readDebugNames: true
})
myModule.applyNames()
const wast = myModule.toText({ foldExprs: false, inlineExport: false })
file.compiledContent = ref(bufferData)
file.lastCompiled = new Date()
file.compiledValueSnapshot = file.content
file.compiledWatContent = wast
} catch (error) {
throw Error('Invalid compilation result produced, check your code for errors and try again!')
}
toast.success('Compiled successfully!', { position: 'bottom-center' })
state.logs.push({
type: 'success',
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
link: Router.asPath.replace('develop', 'deploy'),
linkText: 'Go to deploy'
})
} catch (err) {
console.log(err)
if (err instanceof Array && typeof err[0] === 'string') {
err.forEach(message => {
state.logs.push({
type: 'error',
message
})
})
} else if (err instanceof Error) {
state.logs.push({
type: 'error',
message: err.message
})
} else {
state.logs.push({
type: 'error',
message: 'Something went wrong, come back later!'
})
}
state.compiling = false
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
file.containsErrors = true
}
}
export const compileWat = async (activeId: number) => {
if (state.compiling) return;
const file = state.files[activeId]
state.compiling = true
state.logs = []
try {
const wabt = await (await import('wabt')).default()
const module = wabt.parseWat(file.name, file.content);
module.resolveNames();
module.validate();
const { buffer } = module.toBinary({
log: false,
write_debug_names: true,
});
file.compiledContent = ref(buffer)
file.lastCompiled = new Date()
file.compiledValueSnapshot = file.content
file.compiledWatContent = file.content
toast.success('Compiled successfully!', { position: 'bottom-center' })
state.logs.push({
type: 'success',
message: `File ${state.files?.[activeId]?.name} compiled successfully. Ready to deploy.`,
link: Router.asPath.replace('develop', 'deploy'),
linkText: 'Go to deploy'
})
} catch (err) {
console.log(err)
let message = "Error compiling WAT file!"
if (err instanceof Error) {
message = err.message
}
state.logs.push({
type: 'error',
message
})
toast.error(`Error occurred while compiling!`, { position: 'bottom-center' })
file.containsErrors = true
}
state.compiling = false
}
Loaded 100 of 193 files, more files were not shown because too many files have changed in this diff. Show more