diff --git a/.github/workflows/update_browserslist.yaml b/.github/workflows/update_browserslist.yaml index eaabe6add..99d695561 100644 --- a/.github/workflows/update_browserslist.yaml +++ b/.github/workflows/update_browserslist.yaml @@ -1,7 +1,7 @@ name: Update Browserslist DB on: schedule: - - cron: '0 0 * * 0' # Runs every week at 00:00 on Sunday + - cron: '0 0 1 * *' # Runs every month at 00:00 on the 1st workflow_dispatch: # Allows manual triggering of the workflow jobs: update-browserslist-db: diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png index c95fddb9f..5933b7f7d 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-input-price.png b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-input-price.png index 364deb3c8..ce04c9329 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-input-price.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-input-price.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-animation.png b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-animation.png index e611d06ba..daec1d7b3 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-animation.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-animation.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-summary.png b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-summary.png index 2b9333dd9..c2c0dcc0e 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-summary.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/simulator-results-summary.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-input-price.png b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-input-price.png index b24e4fba6..428cc683c 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-input-price.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-input-price.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-animation.png b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-animation.png index 1f3ce319c..00f741c09 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-animation.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-animation.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-summary.png b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-summary.png index d9d1be05e..49a3d7362 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-summary.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_range/simulator-results-summary.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png index 47b0cbbc6..89d0fece3 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png and b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-animation.png b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-animation.png index 60bf7f65c..00c63813e 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-animation.png and b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-animation.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-summary.png b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-summary.png index 06cc53eef..8666dc49d 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-summary.png and b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-results-summary.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png b/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png index 46cd73b76..9ddf9407d 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png and b/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-input-price.png b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-input-price.png index 0fcc759bf..87012439b 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-input-price.png and b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-input-price.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-animation.png b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-animation.png index 1d2316b1f..dcd423e6f 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-animation.png and b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-animation.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-summary.png b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-summary.png index 4137f932b..c36216281 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-summary.png and b/e2e/screenshots/simulator/recurring/Recurring_range_range/simulator-results-summary.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png index d281484aa..aba9d4f96 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png index 3cb6ee5b3..eae377338 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png index 1f97f3cae..158fd6ccd 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/editPrices/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/editPrices/form.png index ee6b7bb78..a0de7647a 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/editPrices/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png index d0df004a0..639eb9a2d 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/withdraw/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/withdraw/form.png index dc39ea584..a58a28b65 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/withdraw/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png index 842f66c2b..6c2356a6b 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png index dc6d8a227..985fda28e 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png index 8caafcbec..2c96c5e0a 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/duplicate/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/duplicate/form.png index 23d10b5df..50c8ac677 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/duplicate/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/editPrices/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/editPrices/form.png index 54fc4a4fa..116afdf2e 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/editPrices/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png index 9d93046d0..4bcc2cd3d 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/withdraw/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/withdraw/form.png index 8ce908e29..49c5bbcf8 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/withdraw/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png index 75ca0ea36..6490a1c0a 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/deposit/form.png index a2f1f093b..99111b75c 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/duplicate/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/duplicate/form.png index 18c2e0540..2a1badf32 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/duplicate/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/editPrices/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/editPrices/form.png index 7cbcb8d19..9a2127cb3 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/editPrices/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/undercut/form.png index 26277ca99..dac15a148 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/withdraw/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/withdraw/form.png index e84aa6be9..9ffc71a29 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/withdraw/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/create/form.png index baefcfb63..9e403a7ac 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/deposit/form.png index 92032a791..9384bdd4a 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/duplicate/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/duplicate/form.png index 03c259622..7e1359c37 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/duplicate/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/editPrices/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/editPrices/form.png index c25ff601c..71a4365ad 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/editPrices/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/undercut/form.png index 42eb6e9cd..43c9976e5 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_range/withdraw/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_range/withdraw/form.png index b0730eeab..fde7133e1 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_range/withdraw/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_range/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png index 4281699c8..54ec6d79b 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/create/my-strategy.png b/e2e/screenshots/strategy/overlapping/Overlapping/create/my-strategy.png index c96ea4abf..062f93a24 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/create/my-strategy.png and b/e2e/screenshots/strategy/overlapping/Overlapping/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/deposit/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/deposit/form.png index b53300488..f12e2acff 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/deposit/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/deposit/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/duplicate/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/duplicate/form.png index 4aaf9c762..0158813dd 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/duplicate/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png index ca6146ce9..e62c7b947 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/undercut/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/undercut/form.png index 67e3dff4b..c20c4d9e3 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/undercut/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/undercut/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/withdraw/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/withdraw/form.png index 80b8538e9..2cb1e20c5 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/withdraw/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/form.png index 3e3246390..d187dd9ef 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png index 42f5544f5..7421bdc3e 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png index a251bedbe..437f81eaa 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/editPrices/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/editPrices/form.png index d24b064fe..2893ec018 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/editPrices/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/renew/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/renew/form.png index 330ff2f4f..405f6ac6a 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/renew/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/renew/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/undercut/form.png index 8dac4bd85..a37523db1 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/withdraw/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/withdraw/form.png index 747d10e85..a55d3a67f 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/withdraw/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/create/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/create/form.png index 41a14c4a3..ec7d7e85f 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/create/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/create/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png index 15653ae05..70edeab14 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png index e6a4ffda7..9a5bc5c3c 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/editPrices/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/editPrices/form.png index 5200433a2..dcb1b98ee 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/editPrices/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/renew/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/renew/form.png index 3595a60de..142c492a1 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/renew/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/renew/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/undercut/form.png index 00c5a3f01..d665e13da 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/undercut/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/withdraw/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/withdraw/form.png index d6706368e..cb0297e19 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/withdraw/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png index 69e526fc5..bdd4978ba 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/my-strategy.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/my-strategy.png index 6605513ce..4a8e344f2 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/my-strategy.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png index c5d01bcd5..df22fedba 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png index cd6d2eece..8ae573e37 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/editPrices/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/editPrices/form.png index ec3166aec..731751208 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/editPrices/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/renew/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/renew/form.png index 439866d9a..42b8c6b3f 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/renew/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/renew/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png index 121f4df02..24bb73f6d 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/withdraw/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/withdraw/form.png index 51bbc6a67..b1692224e 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/withdraw/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/withdraw/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/create/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/create/form.png index 8b82ce91e..9d68a67ec 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/create/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/create/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png index ad302bab1..581db6783 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/duplicate/form.png index b4f323e99..344cda456 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/editPrices/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/editPrices/form.png index cda5b6a71..8d9f0c8f0 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/editPrices/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/renew/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/renew/form.png index 468f2c8fc..76e709084 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/renew/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/renew/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png index 7b2c2f5e3..ef94d0860 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/withdraw/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/withdraw/form.png index 0961447d3..fd3e4a651 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/withdraw/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/withdraw/form.png differ diff --git a/e2e/utils/simulator/CreateSimulationDriver.ts b/e2e/utils/simulator/CreateSimulationDriver.ts index ec964a766..f515f28a1 100644 --- a/e2e/utils/simulator/CreateSimulationDriver.ts +++ b/e2e/utils/simulator/CreateSimulationDriver.ts @@ -97,7 +97,6 @@ export class CreateSimulationDriver { async fillDates(start: string, end: string) { const [from, to] = [dayjs(start).unix(), dayjs(end).unix()].sort(); - await this.page.getByTestId('date-picker-button').click(); // Select date twice to force range to be 1 day long await this.selectDate(to); diff --git a/package.json b/package.json index 0801da846..2a849fc1c 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "lint-staged": "^14.0.1", "lodash": "^4.17.21", "lz-string": "^1.5.0", - "mipd": "^0.0.7", "msw": "^2.3.1", "postcss": "^8.4.31", "prettier": "^2.7.1", @@ -87,7 +86,7 @@ "vite-plugin-svgr": "^2.4.0", "vite-tsconfig-paths": "^4.2.1", "vitest": "^1.6.0", - "wagmi": "2.12.12", + "wagmi": "2.12.32", "web-vitals": "^2.1.0" }, "scripts": { diff --git a/src/assets/icons/draw-channel.svg b/src/assets/icons/draw-channel.svg new file mode 100644 index 000000000..50975f0d1 --- /dev/null +++ b/src/assets/icons/draw-channel.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/icons/draw-extended-line.svg b/src/assets/icons/draw-extended-line.svg new file mode 100644 index 000000000..e1a41a3f2 --- /dev/null +++ b/src/assets/icons/draw-extended-line.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/icons/draw-indicator.svg b/src/assets/icons/draw-indicator.svg new file mode 100644 index 000000000..1262b3435 --- /dev/null +++ b/src/assets/icons/draw-indicator.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/icons/draw-line.svg b/src/assets/icons/draw-line.svg new file mode 100644 index 000000000..2ce1f8f9b --- /dev/null +++ b/src/assets/icons/draw-line.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/icons/draw-rectangle.svg b/src/assets/icons/draw-rectangle.svg new file mode 100644 index 000000000..68c14bd61 --- /dev/null +++ b/src/assets/icons/draw-rectangle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/icons/draw-triangle.svg b/src/assets/icons/draw-triangle.svg new file mode 100644 index 000000000..d0ab5f834 --- /dev/null +++ b/src/assets/icons/draw-triangle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/common/datePicker/DateRangePicker.tsx b/src/components/common/datePicker/DateRangePicker.tsx index 0bf553a77..a11d95589 100644 --- a/src/components/common/datePicker/DateRangePicker.tsx +++ b/src/components/common/datePicker/DateRangePicker.tsx @@ -39,11 +39,12 @@ interface Props { end?: Date; onConfirm: (props: { start?: Date; end?: Date }) => void; setIsOpen: Dispatch; - presets: DatePickerPreset[]; + presets?: DatePickerPreset[]; options?: Omit; required?: boolean; form?: string; disabled?: boolean; + className?: string; } const displayRange = (start?: Date, end?: Date) => { @@ -70,7 +71,8 @@ export const DateRangePicker = memo(function DateRangePicker( ? 'border-white/60 active:border-white/80' : 'border-background-800 hover:border-background-700 active:border-background-600', props.disabled && - 'border-background-800 hover:border-background-800 active:border-background-800 cursor-not-allowed hover:bg-transparent' + 'border-background-800 hover:border-background-800 active:border-background-800 cursor-not-allowed hover:bg-transparent', + props.className )} data-testid="date-picker-button" disabled={props.disabled} @@ -133,7 +135,7 @@ const Content = (props: Props) => { ); const [date, setDate] = useState(baseDate); const hasDates = !!(date?.from && date?.to); - const selectedPreset = props.presets.find((p) => { + const selectedPreset = props.presets?.find((p) => { if (!hasDates) return false; const from = subDays(now, p.days); return isSameDay(from, date?.from!) && isSameDay(date?.to!, now); @@ -167,25 +169,27 @@ const Content = (props: Props) => { return (
-
- {props.presets.map(({ label, days }) => ( - - ))} -
+ {!!props.presets && ( +
+ {props.presets.map(({ label, days }) => ( + + ))} +
+ )}
); diff --git a/src/components/debug/DebugFeatureFlag.tsx b/src/components/debug/DebugFeatureFlag.tsx new file mode 100644 index 000000000..c99c4b1c3 --- /dev/null +++ b/src/components/debug/DebugFeatureFlag.tsx @@ -0,0 +1,78 @@ +import { Button } from 'components/common/button'; +import { FormEvent } from 'react'; +import { lsService } from 'services/localeStorage'; +import { useStore } from 'store'; +import { featureFlags } from 'utils/featureFlags'; + +export const DebugFeatureFlag = () => { + const { toaster } = useStore(); + + const setFeatureFlags = (e: FormEvent) => { + e.preventDefault(); + const data = new FormData(e.currentTarget); + const values = data.getAll('flags'); + lsService.setItem('featureFlags', values as string[]); + if (values.length) toaster.addToast('Enjoy your new superpowers 🔥'); + else toaster.addToast('Features disabled'); + }; + return ( +
+

🧪 Feature Flags

+ {!!featureFlags.length ? : } + + + ); +}; + +const EmptyFlags = () => ( +

There is no feature flags for now. Thanks for your interest.

+); + +const FeatureRadioGroup = () => { + const currentFlags = lsService.getItem('featureFlags') ?? []; + return ( + <> +
+ + Select the feature flags you want to test + + {featureFlags.map(({ value, label, description }) => ( +
+ +
+ +

+ {description} +

+
+
+ ))} +
+

Disclaimer: beware of the dragons 🐲

+ + ); +}; diff --git a/src/components/simulator/input/SimInputChart.tsx b/src/components/simulator/input/SimInputChart.tsx index f8652f944..33d32f87d 100644 --- a/src/components/simulator/input/SimInputChart.tsx +++ b/src/components/simulator/input/SimInputChart.tsx @@ -1,12 +1,7 @@ import { Link } from '@tanstack/react-router'; import { buttonStyles } from 'components/common/button/buttonStyles'; import { CarbonLogoLoading } from 'components/common/CarbonLogoLoading'; -import { - DateRangePicker, - datePickerPresets, -} from 'components/common/datePicker/DateRangePicker'; import { IconTitleText } from 'components/common/iconTitleText/IconTitleText'; -import { datePickerDisabledDays } from 'components/simulator/result/SimResultChartHeader'; import { SimulatorInputOverlappingValues, SimulatorOverlappingInputDispatch, @@ -14,17 +9,15 @@ import { import { StrategyInputValues } from 'hooks/useStrategyInput'; import { ChartPrices, - D3ChartCandlesticks, OnPriceUpdates, } from 'components/strategies/common/d3Chart'; import { SimulatorType } from 'libs/routing/routes/sim'; import { useCallback } from 'react'; import { ReactComponent as IconPlus } from 'assets/icons/plus.svg'; -import { CandlestickData, D3ChartSettingsProps, D3ChartWrapper } from 'libs/d3'; -import { fromUnixUTC, toUnixUTC } from '../utils'; -import { startOfDay, sub } from 'date-fns'; +import { CandlestickData } from 'libs/d3'; import { formatNumber } from 'utils/helpers'; import { useMarketPrice } from 'hooks/useMarketPrice'; +import { D3PriceHistory } from 'components/strategies/common/d3Chart/D3PriceHistory'; interface Props { state: StrategyInputValues | SimulatorInputOverlappingValues; @@ -38,15 +31,6 @@ interface Props { simulationType: SimulatorType; } -const chartSettings: D3ChartSettingsProps = { - width: 0, - height: 0, - marginTop: 0, - marginBottom: 40, - marginLeft: 0, - marginRight: 80, -}; - export const SimInputChart = ({ state, dispatch, @@ -95,31 +79,19 @@ export const SimInputChart = ({ ); const onDatePickerConfirm = useCallback( - (props: { start?: Date; end?: Date }) => { + (props: { start?: string; end?: string }) => { if (!props.start || !props.end) return; - dispatch('start', toUnixUTC(props.start)); - dispatch('end', toUnixUTC(props.end)); + dispatch('start', props.start); + dispatch('end', props.end); }, [dispatch] ); return (
-
+

Price Chart

- -
+ {isError && ( - {(dms) => ( - - )} - + )}
); diff --git a/src/components/strategies/common/StrategyChartHistory.tsx b/src/components/strategies/common/StrategyChartHistory.tsx index ac7c8fe71..581afcbcc 100644 --- a/src/components/strategies/common/StrategyChartHistory.tsx +++ b/src/components/strategies/common/StrategyChartHistory.tsx @@ -1,9 +1,7 @@ import { ChartPrices, - D3ChartCandlesticks, OnPriceUpdates, } from 'components/strategies/common/d3Chart'; -import { D3ChartSettingsProps, D3ChartWrapper } from 'libs/d3'; import { useSearch } from '@tanstack/react-router'; import { useGetTokenPriceHistory } from 'libs/queries/extApi/tokenPrice'; import { TradeSearch } from 'libs/routing'; @@ -17,17 +15,9 @@ import { NotFound } from 'components/common/NotFound'; import { TradingviewChart } from 'components/tradingviewChart'; import { Token } from 'libs/tokens'; import { Activity } from 'libs/queries/extApi/activity'; -import config from 'config'; import { SafeDecimal } from 'libs/safedecimal'; - -const chartSettings: D3ChartSettingsProps = { - width: 0, - height: 0, - marginTop: 0, - marginBottom: 40, - marginLeft: 0, - marginRight: 80, -}; +import { D3PriceHistory } from './d3Chart/D3PriceHistory'; +import config from 'config'; const getBounds = ( order0: BaseOrder, @@ -151,28 +141,19 @@ export const StrategyChartHistory: FC = (props) => { ); } return ( - - {(dms) => ( - - )} - + ); }; diff --git a/src/components/strategies/common/StrategyChartOverlapping.tsx b/src/components/strategies/common/StrategyChartOverlapping.tsx index b1de42097..bf37ebf1d 100644 --- a/src/components/strategies/common/StrategyChartOverlapping.tsx +++ b/src/components/strategies/common/StrategyChartOverlapping.tsx @@ -12,20 +12,8 @@ import { OverlappingMarketPrice } from 'components/strategies/overlapping/Overla import { OverlappingChart } from 'components/strategies/overlapping/OverlappingChart'; import { StrategyChartHistory } from './StrategyChartHistory'; import { OnPriceUpdates } from 'components/strategies/common/d3Chart'; -import { - DateRangePicker, - datePickerPresets, -} from 'components/common/datePicker/DateRangePicker'; -import { - defaultEndDate, - defaultStartDate, -} from 'components/strategies/common/utils'; -import { fromUnixUTC, toUnixUTC } from 'components/simulator/utils'; -import { datePickerDisabledDays } from 'components/simulator/result/SimResultChartHeader'; -import { useNavigate } from '@tanstack/react-router'; import { Token } from 'libs/tokens'; import { StrategyChartLegend } from './StrategyChartLegend'; -import config from 'config'; interface Props { marketPrice?: string; @@ -37,27 +25,9 @@ interface Props { set: SetOverlapping; } -const url = '/trade/overlapping'; export const StrategyChartOverlapping: FC = (props) => { const { base, quote, marketPrice, set } = props; const search = useSearch({ strict: false }) as OverlappingSearch; - const navigate = useNavigate({ from: url }); - - const onDatePickerConfirm = (props: { start?: Date; end?: Date }) => { - const { start, end } = props; - if (!start || !end) return; - navigate({ - search: (previous) => ({ - ...previous, - priceStart: toUnixUTC(start), - priceEnd: toUnixUTC(end), - }), - resetScroll: false, - replace: true, - }); - }; - const isNativeChart = config.ui.priceChart === 'native'; - return (
= (props) => { History - {isNativeChart && search.chartType === 'history' && ( - - )} + = ({ children }) => { - const navigate = useNavigate({ from: '/trade' }); - const { priceStart, priceEnd } = useSearch({ strict: false }) as TradeSearch; - const onDatePickerConfirm = (props: { start?: Date; end?: Date }) => { - const { start, end } = props; - if (!start || !end) return; - navigate({ - search: (previous) => ({ - ...previous, - priceStart: toUnixUTC(start), - priceEnd: toUnixUTC(end), - }), - resetScroll: false, - replace: true, - }); - }; - const isNativeChart = config.ui.priceChart === 'native'; return (
= ({ children }) => {

Price Chart

- {isNativeChart && ( - - )} {children}
diff --git a/src/components/strategies/common/d3Chart/Candlesticks.tsx b/src/components/strategies/common/d3Chart/Candlesticks.tsx index 6ca3d28bd..7fa807496 100644 --- a/src/components/strategies/common/d3Chart/Candlesticks.tsx +++ b/src/components/strategies/common/d3Chart/Candlesticks.tsx @@ -1,12 +1,12 @@ -import { CandlestickData, ScaleBand, ScaleLinear } from 'libs/d3'; +import { CandlestickData } from 'libs/d3'; +import { useD3ChartCtx } from './D3ChartContext'; type CandlesticksProps = { - xScale: ScaleBand; - yScale: ScaleLinear; data: CandlestickData[]; }; -export function Candlesticks({ xScale, yScale, data }: CandlesticksProps) { +export function Candlesticks({ data }: CandlesticksProps) { + const { xScale, yScale } = useD3ChartCtx(); return ( <> {data.map((d) => { diff --git a/src/components/strategies/common/d3Chart/D3ChartCandlesticks.tsx b/src/components/strategies/common/d3Chart/D3ChartCandlesticks.tsx index 54af9b47c..8b9f5157d 100644 --- a/src/components/strategies/common/d3Chart/D3ChartCandlesticks.tsx +++ b/src/components/strategies/common/d3Chart/D3ChartCandlesticks.tsx @@ -1,24 +1,19 @@ import { D3ChartHandleLine } from 'components/strategies/common/d3Chart/D3ChartHandleLine'; import { D3ChartRecurring } from 'components/strategies/common/d3Chart/recurring/D3ChartRecurring'; import { D3ChartOverlapping } from 'components/strategies/common/d3Chart/overlapping/D3ChartOverlapping'; -import { getDomain } from 'components/strategies/common/d3Chart/utils'; import { XAxis } from 'components/strategies/common/d3Chart/xAxis'; -import { - D3YAxisRight, - useLinearScale, - CandlestickData, - scaleBand, - D3ChartSettings, -} from 'libs/d3'; -import { useMemo, useState } from 'react'; +import { D3YAxisRight, CandlestickData, D3AxisTick } from 'libs/d3'; import { prettifyNumber } from 'utils/helpers'; import { Candlesticks } from 'components/strategies/common/d3Chart/Candlesticks'; import { D3ChartDisposable } from './disposable/D3ChartDisposable'; import { TradeTypes } from 'libs/routing/routes/trade'; import { Activity } from 'libs/queries/extApi/activity'; import { D3ChartIndicators } from './D3ChartIndicators'; -import { D3ZoomEvent, ZoomTransform, select, zoom } from 'd3'; import { D3Pointer } from './D3Pointer'; +import { D3Drawings } from './drawing/D3Drawings'; +import { useD3ChartCtx } from './D3ChartContext'; +import { D3AllDrawingRanges } from './drawing/D3DrawingRanges'; +import { D3PricesAxis } from './D3PriceAxis'; export type ChartPrices = { buy: { min: T; max: T }; @@ -33,102 +28,38 @@ export interface D3ChartCandlesticksProps { prices: ChartPrices; onPriceUpdates: OnPriceUpdates; marketPrice?: number; - bounds: ChartPrices; onDragEnd?: OnPriceUpdates; isLimit?: { buy: boolean; sell: boolean }; - dms: D3ChartSettings; type: TradeTypes; overlappingSpread?: string; overlappingMarketPrice?: number; readonly?: boolean; activities?: Activity[]; + yTicks: D3AxisTick[]; } -const useZoom = (dms: D3ChartSettings, data: CandlestickData[]) => { - const [transform, setTransform] = useState(); - let k = 0; - const selection = select('#interactive-chart'); - const chartArea = select('.chart-area'); - const zoomHandler = zoom() - .scaleExtent([0.5, Math.ceil(data.length / 10)]) - .translateExtent([ - [-0.5 * dms.width, 0], - [1.5 * dms.width, 0], - ]) - .on('start', (e: D3ZoomEvent) => (k = e.transform.k)) - .on('zoom', (e: D3ZoomEvent) => { - if (e.transform.k === k) chartArea.style('cursor', 'grab'); - setTransform(e.transform); - }) - .on('end', () => chartArea.style('cursor', '')); - selection.call(zoomHandler); - return transform; -}; - -const getDateRange = (range: number[]) => { - if (!range.length) return []; - const points: string[] = []; - const first = range[0]; - const step = range[1] - first; - const start = Math.floor(-0.5 * range.length); - const end = Math.ceil(range.length * 1.5); - for (let i = start; i < end; i++) { - points.push((first + i * step).toString()); - } - return points; -}; - export const D3ChartCandlesticks = (props: D3ChartCandlesticksProps) => { const { data, prices, onPriceUpdates, marketPrice, - bounds, onDragEnd, isLimit, - dms, type, overlappingSpread, overlappingMarketPrice, readonly, activities, + yTicks, } = props; - const zoomTransform = useZoom(dms, data); - - const xScale = useMemo(() => { - const zoomX = (d: number) => (zoomTransform ? zoomTransform.applyX(d) : d); - return scaleBand() - .domain(getDateRange(data.map((d) => d.date))) - .range([dms.boundedWidth * -0.5, dms.boundedWidth * 1.5].map(zoomX)) - .paddingInner(0.5); - }, [data, dms.boundedWidth, zoomTransform]); - - const xTicks = useMemo(() => { - const length = xScale.domain().length; - const ratio = Math.ceil(zoomTransform?.k ?? 1); - const target = Math.floor((dms.boundedWidth * ratio) / 80); - const numberOfTicks = Math.max(1, target); - const m = Math.ceil(length / numberOfTicks); - return xScale.domain().filter((_, i) => i % m === m - 1); - }, [dms.boundedWidth, xScale, zoomTransform]); - - const yDomain = useMemo(() => { - const candles = data.filter((point) => xScale(point.date.toString())! > 0); - return getDomain(candles, bounds, marketPrice); - }, [bounds, data, marketPrice, xScale]); - - const y = useLinearScale({ - domain: yDomain, - range: [dms.boundedHeight, 0], - domainTolerance: 0.1, - }); + const { dms, yScale } = useD3ChartCtx(); if (!dms.width || !dms.height) return null; return ( <> - + { height={dms.boundedHeight} fillOpacity="0" /> - {activities?.length && ( - - )} - - { - return prettifyNumber(value, { abbreviate: true }); - }} - /> - {marketPrice && ( - - )} + {activities?.length && } + {type === 'disposable' && isLimit && ( { {type === 'recurring' && isLimit && ( { {type === 'overlapping' && overlappingSpread !== undefined && ( { spread={Number(overlappingSpread)} /> )} - + + + { + return prettifyNumber(value, { abbreviate: true }); + }} + /> + {marketPrice && ( + + )} + + + ); }; diff --git a/src/components/strategies/common/d3Chart/D3ChartContext.tsx b/src/components/strategies/common/d3Chart/D3ChartContext.tsx new file mode 100644 index 000000000..4c11d225c --- /dev/null +++ b/src/components/strategies/common/d3Chart/D3ChartContext.tsx @@ -0,0 +1,44 @@ +import { D3ChartSettings, ScaleBand, ScaleLinear } from 'libs/d3'; +import { DrawingMode } from './drawing/DrawingMenu'; +import { + createContext, + Dispatch, + FC, + ReactNode, + SetStateAction, + useContext, +} from 'react'; +import { ZoomTransform } from 'd3'; + +export interface ChartPoint { + x: string; + y: number; +} + +export interface Drawing { + id: number; + mode: DrawingMode; + points: ChartPoint[]; +} + +interface D3ChartContext { + dms: D3ChartSettings; + xScale: ScaleBand; + yScale: ScaleLinear; + drawingMode?: DrawingMode; + setDrawingMode: Dispatch>; + drawings: Drawing[]; + setDrawings: Dispatch>; + zoom?: ZoomTransform; +} + +const D3ChartCtx = createContext({} as any); + +interface Props extends D3ChartContext { + children: ReactNode; +} +export const D3ChartProvider: FC = ({ children, ...ctx }) => { + return {children}; +}; + +export const useD3ChartCtx = () => useContext(D3ChartCtx); diff --git a/src/components/strategies/common/d3Chart/D3ChartHandleLine.tsx b/src/components/strategies/common/d3Chart/D3ChartHandleLine.tsx index 217ff2ee5..9a50f23a4 100644 --- a/src/components/strategies/common/d3Chart/D3ChartHandleLine.tsx +++ b/src/components/strategies/common/d3Chart/D3ChartHandleLine.tsx @@ -1,15 +1,10 @@ -import { D3ChartSettings } from 'libs/d3'; import { SVGProps } from 'react'; import { cn } from 'utils/helpers'; - -const handleDms = { - width: 64, - height: 16, -}; +import { useD3ChartCtx } from './D3ChartContext'; +import { handleDms } from './utils'; interface Props { selector?: string; - dms: D3ChartSettings; y?: number; color: string; label?: string; @@ -17,23 +12,31 @@ interface Props { handleClassName?: string; readonly?: boolean; isDraggable?: boolean; + className?: string; } export const D3ChartHandleLine = ({ lineProps, ...props }: Props) => { const { selector, - dms, color, y = 0, label, readonly, isDraggable, handleClassName, + className, } = props; + const { dms } = useD3ChartCtx(); const lineWidth = dms.boundedWidth + 5; return ( - + { }; export interface D3ChartIndicatorsProps { - xScale: ScaleBand; - yScale: ScaleLinear; - boundHeight: number; activities: Activity[]; } -export const D3ChartIndicators = (props: D3ChartIndicatorsProps) => { - const { xScale, yScale, boundHeight: height, activities } = props; +export const D3ChartIndicators = ({ activities }: D3ChartIndicatorsProps) => { + const { xScale, yScale, dms } = useD3ChartCtx(); + const height = dms.boundedHeight; const { operations, trades } = groupByIndicators(activities, xScale.domain()); return ( <> diff --git a/src/components/strategies/common/d3Chart/D3ChartPriceOutOfScale.tsx b/src/components/strategies/common/d3Chart/D3ChartPriceOutOfScale.tsx index a2325051c..3f308fcf6 100644 --- a/src/components/strategies/common/d3Chart/D3ChartPriceOutOfScale.tsx +++ b/src/components/strategies/common/d3Chart/D3ChartPriceOutOfScale.tsx @@ -1,12 +1,11 @@ -import { D3ChartSettings } from 'libs/d3/types'; import { cn } from 'utils/helpers'; +import { useD3ChartCtx } from './D3ChartContext'; interface Props { type: 'buy' | 'sell'; minOutOfScale: boolean; maxOutOfScale: boolean; color: string; - dms: D3ChartSettings; } export const D3ChartPriceOutOfScale = ({ @@ -14,8 +13,8 @@ export const D3ChartPriceOutOfScale = ({ minOutOfScale, maxOutOfScale, color, - dms, }: Props) => { + const { dms } = useD3ChartCtx(); const msg = type === 'buy' ? 'BUY' : 'SELL'; const stopColor1 = minOutOfScale ? 'black' : color; const stopColor2 = minOutOfScale ? color : 'black'; diff --git a/src/components/strategies/common/d3Chart/D3Pointer.tsx b/src/components/strategies/common/d3Chart/D3Pointer.tsx index df35f706f..d8b1ff94e 100644 --- a/src/components/strategies/common/d3Chart/D3Pointer.tsx +++ b/src/components/strategies/common/d3Chart/D3Pointer.tsx @@ -1,36 +1,26 @@ import { fromUnixUTC, xAxisFormatter } from 'components/simulator/utils'; import { ScaleBand, ScaleLinear } from 'd3'; -import { D3ChartSettings } from 'libs/d3'; -import { FC, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { prettifyNumber } from 'utils/helpers'; - -const rect = { - w: 72, - h: 24, -}; +import { handleDms, scaleBandInvert } from './utils'; +import { useD3ChartCtx } from './D3ChartContext'; +import { getAreaBox } from './drawing/utils'; const usePointerPosition = ( xScale: ScaleBand, yScale: ScaleLinear ) => { const [position, setPosition] = useState<{ x: string; y: number }>(); - function scaleBandInvert(scale: ScaleBand) { - var domain = scale.domain(); - var paddingOuter = scale(domain[0]) ?? 0; - var eachBand = scale.step(); - return function (value: number) { - var index = Math.floor((value - paddingOuter) / eachBand); - return domain[Math.max(0, Math.min(index, domain.length - 1))]; - }; - } useEffect(() => { const invert = scaleBandInvert(xScale); - const chart = document.querySelector('.chart-area'); + const chart = document.getElementById('interactive-chart'); + const box = getAreaBox(); const move = (event: MouseEvent) => { - const rect = chart!.getBoundingClientRect(); + if (event.clientX > box.x + box.width) return; + if (event.clientY > box.y + box.height) return; const position = { - x: event.clientX - rect.x, - y: event.clientY - rect.y, + x: event.clientX - box.x, + y: event.clientY - box.y, }; setPosition({ x: invert(position.x), @@ -48,23 +38,21 @@ const usePointerPosition = ( return position; }; -interface Props { - dms: D3ChartSettings; - xScale: ScaleBand; - yScale: ScaleLinear; -} - -export const D3Pointer: FC = ({ dms, xScale, yScale }) => { +export const D3Pointer = () => { + const { dms, xScale, yScale } = useD3ChartCtx(); const position = usePointerPosition(xScale, yScale); if (!position) return; + const x = xScale(position.x)!; + const y = yScale(position.y); + const bandwidth = xScale.bandwidth(); return ( <> {/* X axis */} = ({ dms, xScale, yScale }) => { className="pointer-events-none" /> {xAxisFormatter.format(fromUnixUTC(position.x))} @@ -93,9 +81,9 @@ export const D3Pointer: FC = ({ dms, xScale, yScale }) => { {/* Y axis */} = ({ dms, xScale, yScale }) => { className="pointer-events-none" /> {prettifyNumber(position.y)} diff --git a/src/components/strategies/common/d3Chart/D3PriceAxis.tsx b/src/components/strategies/common/d3Chart/D3PriceAxis.tsx new file mode 100644 index 000000000..877992b8b --- /dev/null +++ b/src/components/strategies/common/d3Chart/D3PriceAxis.tsx @@ -0,0 +1,45 @@ +import { FC } from 'react'; +import { useD3ChartCtx } from './D3ChartContext'; +import { prettifyNumber } from 'utils/helpers'; +import { ChartPrices } from './D3ChartCandlesticks'; +import { handleDms } from './utils'; + +export const D3PricesAxis = ({ prices }: { prices: ChartPrices }) => { + const buyMin = Number(prices.buy.min); + const buyMax = Number(prices.buy.max); + const showBuyMax = buyMax && buyMin !== buyMax; + const sellMin = Number(prices.sell.min); + const sellMax = Number(prices.sell.max); + const showSellMax = sellMax && sellMin !== sellMax; + + return ( + <> + {!!buyMin && } + {showBuyMax && } + {!!sellMin && } + {showSellMax && } + + ); +}; + +interface Props { + price: number; + color: string; +} + +const D3PriceAxis: FC = ({ price, color }) => { + const { dms, yScale } = useD3ChartCtx(); + const lineWidth = dms.boundedWidth + 5; + const y = yScale(price); + return ( + + + + {prettifyNumber(price, { abbreviate: true })} + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/D3PriceHistory.module.css b/src/components/strategies/common/d3Chart/D3PriceHistory.module.css new file mode 100644 index 000000000..67c0b3799 --- /dev/null +++ b/src/components/strategies/common/d3Chart/D3PriceHistory.module.css @@ -0,0 +1,4 @@ +.history-chart:has(:global(.draggable):focus) text { + -webkit-user-select: none; + user-select: none; +} \ No newline at end of file diff --git a/src/components/strategies/common/d3Chart/D3PriceHistory.tsx b/src/components/strategies/common/d3Chart/D3PriceHistory.tsx new file mode 100644 index 000000000..f48a7dc7e --- /dev/null +++ b/src/components/strategies/common/d3Chart/D3PriceHistory.tsx @@ -0,0 +1,321 @@ +import { + CandlestickData, + D3ChartSettings, + D3ChartSettingsProps, + useChartDimensions, + useLinearScale, +} from 'libs/d3'; +import { D3ChartProvider } from './D3ChartContext'; +import { DrawingMenu, DrawingMode } from './drawing/DrawingMenu'; +import { + ChartPrices, + D3ChartCandlesticks, + OnPriceUpdates, +} from './D3ChartCandlesticks'; +import { FC, useEffect, useMemo, useRef, useState } from 'react'; +import { + D3ZoomEvent, + scaleBand, + select, + zoom, + ZoomBehavior, + zoomIdentity, + ZoomTransform, +} from 'd3'; +import { TradeTypes } from 'libs/routing/routes/trade'; +import { Activity } from 'libs/queries/extApi/activity'; +import { getDomain, scaleBandInvert } from './utils'; +import { cn } from 'utils/helpers'; +import { DateRangePicker } from 'components/common/datePicker/DateRangePicker'; +import { defaultEndDate, defaultStartDate } from '../utils'; +import { differenceInDays, startOfDay } from 'date-fns'; +import { fromUnixUTC, toUnixUTC } from 'components/simulator/utils'; +import style from './D3PriceHistory.module.css'; + +export interface RangeUpdate { + start?: string; + end?: string; +} + +const chartSettings: D3ChartSettingsProps = { + width: 0, + height: 0, + marginTop: 0, + marginBottom: 40, + marginLeft: 0, + marginRight: 80, +}; + +type TranslateExtent = [[number, number], [number, number]]; +type TransformBehavior = 'normal' | 'extended'; +const getExtentConfig = ( + behavior: TransformBehavior, + datasize: number, + width: number +) => { + if (behavior === 'normal') { + return { + // eslint-disable-next-line prettier/prettier + translate: [ + [0, 0], + [width, 0], + ] as TranslateExtent, + zoom: [1, Math.ceil(datasize / 7)] as [number, number], + }; + } else { + return { + // eslint-disable-next-line prettier/prettier + translate: [ + [-0.5 * width, 0], + [1.5 * width, 0], + ] as TranslateExtent, + zoom: [0.5, Math.ceil(datasize / 7)] as [number, number], + }; + } +}; + +const useZoom = ( + dms: D3ChartSettings, + data: CandlestickData[], + behavior: TransformBehavior +) => { + const zoomHandler = useRef>(); + const [transform, setTransform] = useState(); + + const selection = select('#interactive-chart'); + const chartArea = select('.chart-area'); + const extent = getExtentConfig(behavior, data.length, dms.width); + + useEffect(() => { + let k = 0; + zoomHandler.current = zoom() + .scaleExtent(extent.zoom) + .translateExtent(extent.translate) + .filter((e: Event) => { + return !(e.target as Element).classList.contains('draggable'); + }) + .on('start', (e: D3ZoomEvent) => { + k = e.transform.k; + }) + .on('zoom', (e: D3ZoomEvent) => { + if (e.transform.k === k) chartArea.style('cursor', 'grab'); + setTransform(e.transform); + }) + .on('end', () => chartArea.style('cursor', '')); + selection.call(zoomHandler.current); + }, [chartArea, extent.translate, extent.zoom, selection]); + + const zoomRange = (from: string, days: number) => { + const baseXScale = (() => { + if (behavior === 'normal') { + return scaleBand() + .domain(data.map((d) => d.date.toString())) + .range([0, dms.boundedWidth]) + .paddingInner(0.5); + } else { + return scaleBand() + .domain(getExtendedRange(data.map((d) => d.date))) + .range([dms.boundedWidth * -0.5, dms.boundedWidth * 1.5]) + .paddingInner(0.5); + } + })(); + + const scale = data.length / days; + const translateX = baseXScale(from)!; + const transition = selection.transition().duration(500); + const transform = zoomIdentity.scale(scale).translate(-1 * translateX, 0); + zoomHandler.current?.transform(transition, transform); + }; + + return { transform, zoomRange }; +}; + +const getExtendedRange = (range: number[]) => { + if (!range.length) return []; + const points: string[] = []; + const first = range[0]; + const step = range[1] - first; + const start = Math.floor(-0.5 * range.length); + const end = Math.ceil(range.length * 1.5); + for (let i = start; i < end; i++) { + points.push((first + i * step).toString()); + } + return points; +}; + +interface Props { + className?: string; + data: CandlestickData[]; + prices: ChartPrices; + onPriceUpdates: OnPriceUpdates; + onDragEnd: OnPriceUpdates; + onRangeUpdates?: (params: RangeUpdate) => void; + marketPrice?: number; + bounds: ChartPrices; + isLimit?: { buy: boolean; sell: boolean }; + type: TradeTypes; + overlappingSpread?: string; + readonly?: boolean; + activities?: Activity[]; + zoomBehavior?: TransformBehavior; +} + +const presetDays = [ + { days: 7, label: '7D' }, + { days: 30, label: '1M' }, + { days: 90, label: '3M' }, + { days: 365, label: '1Y' }, +]; + +export const D3PriceHistory: FC = (props) => { + const { + className, + data, + marketPrice, + bounds, + zoomBehavior = 'normal', + onRangeUpdates, + } = props; + const [drawingMode, setDrawingMode] = useState(); + const [drawings, setDrawings] = useState([]); + const [ref, dms] = useChartDimensions(chartSettings); + const { transform: zoomTransform, zoomRange } = useZoom( + dms, + data, + zoomBehavior + ); + + const xScale = useMemo(() => { + const zoomX = (d: number) => (zoomTransform ? zoomTransform.applyX(d) : d); + if (zoomBehavior === 'normal') { + return scaleBand() + .domain(data.map((d) => d.date.toString())) + .range([0, dms.boundedWidth].map(zoomX)) + .paddingInner(0.5); + } else { + return scaleBand() + .domain(getExtendedRange(data.map((d) => d.date))) + .range([dms.boundedWidth * -0.5, dms.boundedWidth * 1.5].map(zoomX)) + .paddingInner(0.5); + } + }, [data, dms.boundedWidth, zoomBehavior, zoomTransform]); + + const yDomain = useMemo(() => { + const candles = data.filter((point) => xScale(point.date.toString())! > 0); + return getDomain(candles, bounds, marketPrice); + }, [bounds, data, marketPrice, xScale]); + + const y = useLinearScale({ + domain: yDomain, + range: [dms.boundedHeight, 0], + domainTolerance: 0.1, + }); + + const invertX = scaleBandInvert(xScale); + + const start = invertX(xScale.bandwidth() / 2) ?? xScale.domain()[0]; + const end = invertX(dms.boundedWidth) ?? xScale.domain().at(-1); + + const disabledDates = [ + { + before: new Date(Number(xScale.domain()[0]) * 1000), + after: new Date(Number(xScale.domain().at(-1)!) * 1000), + }, + ]; + + useEffect(() => { + const id = setTimeout(() => { + if (onRangeUpdates) onRangeUpdates({ start, end }); + }, 100); + return () => clearTimeout(id); + }, [onRangeUpdates, start, end]); + + const zoomFromTo = ({ start, end }: { start?: Date; end?: Date }) => { + if (!start || !end) return; + zoomRange(toUnixUTC(startOfDay(start)), differenceInDays(end, start) + 1); + }; + + const zoomIn = (days: number) => { + zoomRange(data.at(days * -1)!.date.toString(), days); + }; + + return ( + +
+ setDrawings([])} /> +
+ + + + + + + + + + + + +
+ {presetDays.map(({ days, label }) => ( + + ))} +
+ +
+
+
+
+ ); +}; diff --git a/src/components/strategies/common/d3Chart/disposable/D3ChartDisposable.tsx b/src/components/strategies/common/d3Chart/disposable/D3ChartDisposable.tsx index 371fb0c51..5c517fdcf 100644 --- a/src/components/strategies/common/d3Chart/disposable/D3ChartDisposable.tsx +++ b/src/components/strategies/common/d3Chart/disposable/D3ChartDisposable.tsx @@ -2,15 +2,14 @@ import { D3ChartCandlesticksProps } from 'components/strategies/common/d3Chart/D import { DragablePriceRange } from 'components/strategies/common/d3Chart/recurring/DragablePriceRange'; import { handleStateChange } from 'components/strategies/common/d3Chart/recurring/utils'; import { isZero } from 'components/strategies/common/utils'; -import { ScaleLinear } from 'd3'; import { useCallback, useEffect, useRef } from 'react'; import { prettifyNumber } from 'utils/helpers'; +import { useD3ChartCtx } from '../D3ChartContext'; type Props = Pick< D3ChartCandlesticksProps, - 'prices' | 'onPriceUpdates' | 'dms' | 'onDragEnd' + 'prices' | 'onPriceUpdates' | 'onDragEnd' > & { - yScale: ScaleLinear; isLimit: { buy: boolean; sell: boolean }; readonly?: boolean; }; @@ -18,12 +17,11 @@ type Props = Pick< export const D3ChartDisposable = ({ prices, onPriceUpdates, - dms, - yScale, isLimit, readonly, onDragEnd, }: Props) => { + const { yScale } = useD3ChartCtx(); const onMinMaxChange = useCallback( (type: 'buy' | 'sell', min: number, max: number) => { const minInverted = yScale.invert(min).toString(); @@ -134,7 +132,6 @@ export const D3ChartDisposable = ({ onMinMaxChange={onMinMaxChange} labels={labels[type]} yPos={yPos[type]} - dms={dms} onDragEnd={onMinMaxChangeEnd} isLimit={isLimit[type]} readonly={readonly} diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawChannel.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawChannel.tsx new file mode 100644 index 000000000..72312ef46 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawChannel.tsx @@ -0,0 +1,372 @@ +import { ScaleBand, ScaleLinear } from 'libs/d3'; +import { + FC, + KeyboardEvent, + MouseEvent as ReactMouseEvent, + useEffect, + useRef, + useState, +} from 'react'; +import { scaleBandInvert } from '../utils'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getAreaBox, getDelta, getEdges, getInitialPoints } from './utils'; + +interface Props { + xScale: ScaleBand; + yScale: ScaleLinear; + onChange: (points: ChartPoint[]) => any; +} + +/** Transform the values of the polygon into two lines in D3 space */ +const fromPolygonPoints = ( + polygon: SVGPolygonElement, + invertX: (value: number) => string, + invertY: (value: number) => number +) => { + return [ + polygon.points.getItem(0), // line1 x1,y1 + polygon.points.getItem(1), // line1 x2,y2 + polygon.points.getItem(3), // line2 x1,y1 + polygon.points.getItem(2), // line2 x2,y2 + ].map(({ x, y }) => ({ + x: invertX(x), + y: invertY(y), + })); +}; + +/** Transform points into D3 polygon points */ +const toPolygonPoints = ( + points: ChartPoint[], + xScale: ScaleBand, + yScale: ScaleLinear +) => { + return [points[0], points[1], points[3], points[2]] + .map(({ x, y }) => `${xScale(x)},${yScale(y)}`) + .join(' '); +}; + +/** Get the polygon point out of a mouse delta & a base line */ +const getPolygonPoints = (line: SVGLineElement, deltaY: number) => { + const x1 = line.x1.baseVal.value; + const x2 = line.x2.baseVal.value; + const y1 = line.y1.baseVal.value; + const y2 = line.y2.baseVal.value; + const points = [ + [x1, y1], + [x2, y2], + [x2, y2 + deltaY], + [x1, y1 + deltaY], + ]; + return points.map(([x, y]) => `${x},${y}`).join(' '); +}; + +const getLineCenter = ( + points: ChartPoint[], + xScale: ScaleBand, + yScale: ScaleLinear +) => { + const [a, b] = points; + return { + cx: (xScale(a.x)! + xScale(b.x)!) / 2, + cy: (yScale(a.y)! + yScale(b.y)!) / 2, + }; +}; + +export const D3DrawChannel: FC = ({ xScale, yScale, onChange }) => { + const lineRef = useRef(null); + const secondLineRef = useRef(null); + const polygonRef = useRef(null); + const { dms } = useD3ChartCtx(); + const [points, setPoints] = useState([]); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + const addPoint = (event: ReactMouseEvent) => { + const svg = document.getElementById('interactive-chart')!; + const root = svg.getBoundingClientRect(); + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + if (!points.length) { + setPoints([{ x, y }]); + const handler = (e: MouseEvent) => { + if (!lineRef.current) return; + lineRef.current.setAttribute('x2', `${e.clientX - root.x}`); + lineRef.current.setAttribute('y2', `${e.clientY - root.y}`); + }; + document.addEventListener('mousemove', handler); + // wait after click event bubble up to document + requestAnimationFrame(() => { + document.addEventListener( + 'click', + () => document.removeEventListener('mousemove', handler), + { once: true } + ); + }); + } else if (points.length === 1) { + setPoints((p) => [p[0], { x, y }]); + const handler = (e: MouseEvent) => { + if (!secondLineRef.current) return; + const secondLine = secondLineRef.current; + const deltaY = e.clientY - event.clientY; + const translate = `translate(0, ${deltaY})`; + secondLine.setAttribute('transform', translate); + const polygonPoint = getPolygonPoints(secondLine, deltaY); + polygonRef.current?.setAttribute('points', polygonPoint); + }; + document.addEventListener('mousemove', handler); + requestAnimationFrame(() => { + document.addEventListener( + 'click', + () => document.removeEventListener('mousemove', handler), + { once: true } + ); + }); + } else { + const polygon = polygonRef.current!; + onChange(fromPolygonPoints(polygon, invertX, invertY)); + } + }; + + return ( + <> + + {!!points.length && ( + + )} + {points.length === 2 && ( + + )} + {points.map(({ x, y }) => ( + + ))} + + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +export const D3EditChannel: FC = ({ drawing, onChange }) => { + const ref = useRef(null); + const [editing, setEditing] = useState(false); + const { dms, xScale, yScale } = useD3ChartCtx(); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + const points = drawing.points; + + useEffect(() => { + document.getElementById(`shape-${drawing.id}`)?.focus(); + }, [drawing.id]); + + const onKeyDown = (event: KeyboardEvent) => { + const deleteKeys = ['Delete', 'Backspace', 'Clear']; + if (deleteKeys.includes(event.key)) onChange([]); + }; + + const dragShape = (event: ReactMouseEvent) => { + setEditing(true); + const shape = event.currentTarget; + const allCircles = getEdges(drawing.id); + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, allCircles); + shape.style.setProperty('cursor', 'grabbing'); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const points = initialPoints.map(({ x, y }) => ({ + x: invertX(x + delta.x), + y: invertY(y + delta.y), + })); + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + shape.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragPoint = ( + event: ReactMouseEvent, + index: number + ) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const allCircles = getEdges(drawing.id); + const circle = allCircles[index]; + const matchingIndex = (index + 2) % 4; + circle.style.setProperty('cursor', 'grabbing'); + + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, allCircles); + const newPoints = structuredClone(points); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + newPoints[index] = { + x: invertX(initialPoints[index].x + delta.x), + y: invertY(initialPoints[index].y + delta.y), + }; + newPoints[matchingIndex] = { + x: invertX(initialPoints[matchingIndex].x + delta.x), + y: invertY(initialPoints[matchingIndex].y + delta.y), + }; + onChange(newPoints); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + circle.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragCenter = (event: ReactMouseEvent, index: number) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const allCircles = getEdges(drawing.id); + const circleIndexes = index === 0 ? [0, 1] : [2, 3]; + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, allCircles); + const newPoints = structuredClone(points); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + for (const i of circleIndexes) { + newPoints[i].y = invertY(initialPoints[i].y + delta.y); + } + onChange(newPoints); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const circles = points.map(({ x, y }, i) => ( + dragPoint(e, i)} + /> + )); + + const showIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.setProperty('display', 'inline'); + }; + const hideIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.removeProperty('display'); + }; + + return ( + <> + {editing && ( + + )} + + + + + {circles} + dragCenter(e, 0)} + /> + dragCenter(e, 1)} + /> + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawExtendedLine.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawExtendedLine.tsx new file mode 100644 index 000000000..cbb598e26 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawExtendedLine.tsx @@ -0,0 +1,253 @@ +import { ScaleBand, ScaleLinear } from 'libs/d3'; +import { + FC, + KeyboardEvent, + MouseEvent as ReactMouseEvent, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { scaleBandInvert } from '../utils'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getMax, getMin } from 'utils/helpers/operators'; +import { getAreaBox, getDelta, getEdges, getInitialPoints } from './utils'; + +const getLineProps = ( + points: ChartPoint[], + xScale: ScaleBand, + yScale: ScaleLinear +) => { + const [a, b] = points; + if (a.x === b.x) { + return { x1: xScale(a.x), x2: xScale(b.x), y1: 0, y2: 1000 }; + } else { + const xDomain = xScale.domain(); + const minX = getMin(a.x, b.x); + const maxX = getMax(a.x, b.x); + const minY = minX === +a.x ? a.y : b.y; + const maxY = maxX === +a.x ? a.y : b.y; + const coef = (maxY - minY) / (maxX - minX); + const fn = (x: string) => (+x - minX) * coef + minY; + return { + x1: xScale(xDomain[0]), + x2: xScale(xDomain.at(-1)!), + y1: yScale(fn(xDomain[0])), + y2: yScale(fn(xDomain.at(-1)!)), + }; + } +}; + +interface Props { + xScale: ScaleBand; + yScale: ScaleLinear; + onChange: (points: ChartPoint[]) => any; +} + +export const D3DrawExtendedLine: FC = ({ xScale, yScale, onChange }) => { + const ref = useRef(null); + const { dms } = useD3ChartCtx(); + const [points, setPoints] = useState([]); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + if (points.length !== 1) return; + const area = document.querySelector('.chart-drawing-canvas')!; + const root = area!.getBoundingClientRect(); + const handler = (event: MouseEvent) => { + if (!ref.current) return; + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const newPoints = [points[0], { x, y }]; + const props = getLineProps(newPoints, xScale, yScale); + for (const [key, value] of Object.entries(props)) { + ref.current.setAttribute(key, value?.toString() ?? ''); + } + }; + area.addEventListener('mousemove', handler as any); + return () => area.removeEventListener('mousemove', handler as any); + }, [invertX, invertY, points, xScale, yScale]); + + const addPoint = (event: ReactMouseEvent) => { + const svg = document.getElementById('interactive-chart')!; + const root = svg.getBoundingClientRect(); + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const line = [...points, { x, y }]; + if (line.length === 2) onChange(line); + setPoints(line); + }; + + return ( + <> + {!!points.length && ( + + )} + {points.map(({ x, y }) => ( + + ))} + + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +export const D3EditExtendedLine: FC = ({ drawing, onChange }) => { + const lineRef = useRef(null); + const [editing, setEditing] = useState(false); + const { dms, xScale, yScale } = useD3ChartCtx(); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + document.getElementById(`shape-${drawing.id}`)?.focus(); + }, [drawing.id]); + + const lineProps = useMemo( + () => getLineProps(drawing.points, xScale, yScale), + [drawing.points, xScale, yScale] + ); + + const onKeyDown = (event: KeyboardEvent) => { + const deleteKeys = ['Delete', 'Backspace', 'Clear']; + if (deleteKeys.includes(event.key)) onChange([]); + }; + + const dragShape = (event: ReactMouseEvent) => { + setEditing(true); + const shape = event.currentTarget; + const circles = getEdges(drawing.id); + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, circles); + shape.style.setProperty('cursor', 'grabbing'); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const points = initialPoints.map(({ x, y }) => ({ + x: invertX(x + delta.x), + y: invertY(y + delta.y), + })); + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + shape.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragPoint = ( + event: ReactMouseEvent, + index: number + ) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const circle = event.currentTarget; + circle.style.setProperty('cursor', 'grabbing'); + const box = circle.getBoundingClientRect(); + const root = getAreaBox(); + const initialX = box.x - root.x + box.width / 2; + const initialY = box.y - root.y + box.width / 2; + const points = structuredClone(drawing.points); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + points[index] = { + x: invertX(initialX + delta.x), + y: invertY(initialY + delta.y), + }; + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + circle.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + const circles = drawing.points.map(({ x, y }, i) => ( + dragPoint(e, i)} + /> + )); + + const showIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.setProperty('display', 'inline'); + }; + const hideIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.removeProperty('display'); + }; + + return ( + <> + {editing && ( + + )} + + + {circles} + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawLine.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawLine.tsx new file mode 100644 index 000000000..cd55f07e1 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawLine.tsx @@ -0,0 +1,219 @@ +import { ScaleBand, ScaleLinear } from 'libs/d3'; +import { + FC, + KeyboardEvent, + MouseEvent as ReactMouseEvent, + useEffect, + useRef, + useState, +} from 'react'; +import { scaleBandInvert } from '../utils'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getAreaBox, getDelta, getEdges, getInitialPoints } from './utils'; + +interface Props { + xScale: ScaleBand; + yScale: ScaleLinear; + onChange: (points: ChartPoint[]) => any; +} + +export const D3DrawLine: FC = ({ xScale, yScale, onChange }) => { + const lineRef = useRef(null); + const { dms } = useD3ChartCtx(); + const [points, setPoints] = useState([]); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + if (points.length !== 1) return; + const area = document.querySelector('.chart-drawing-canvas')!; + const root = area!.getBoundingClientRect(); + const handler = (event: MouseEvent) => { + if (!lineRef.current) return; + lineRef.current.setAttribute('x2', `${event.clientX - root.x}`); + lineRef.current.setAttribute('y2', `${event.clientY - root.y}`); + }; + area.addEventListener('mousemove', handler as any); + return () => area.removeEventListener('mousemove', handler as any); + }, [points]); + + const addPoint = (event: ReactMouseEvent) => { + const svg = document.getElementById('interactive-chart')!; + const root = svg.getBoundingClientRect(); + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const line = [...points, { x, y }]; + if (line.length === 2) onChange(line); + setPoints(line); + }; + + return ( + <> + {!!points.length && ( + + )} + {points.map(({ x, y }) => ( + + ))} + + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +export const D3EditLine: FC = ({ drawing, onChange }) => { + const ref = useRef(null); + const [editing, setEditing] = useState(false); + const { dms, xScale, yScale } = useD3ChartCtx(); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + document.getElementById(`shape-${drawing.id}`)?.focus(); + }, [drawing.id]); + + const onKeyDown = (event: KeyboardEvent) => { + const deleteKeys = ['Delete', 'Backspace', 'Clear']; + if (deleteKeys.includes(event.key)) onChange([]); + }; + + const dragShape = (event: ReactMouseEvent) => { + setEditing(true); + const shape = event.currentTarget; + const circles = getEdges(drawing.id); + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, circles); + shape.style.setProperty('cursor', 'grabbing'); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const points = initialPoints.map(({ x, y }) => ({ + x: invertX(x + delta.x), + y: invertY(y + delta.y), + })); + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + shape.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragPoint = ( + event: ReactMouseEvent, + index: number + ) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const circle = event.currentTarget; + circle.style.setProperty('cursor', 'grabbing'); + const box = circle.getBoundingClientRect(); + const root = getAreaBox(); + const initialX = box.x - root.x + box.width / 2; + const initialY = box.y - root.y + box.width / 2; + const points = structuredClone(drawing.points); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + points[index] = { + x: invertX(initialX + delta.x), + y: invertY(initialY + delta.y), + }; + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + circle.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + const circles = drawing.points.map(({ x, y }, i) => ( + dragPoint(e, i)} + /> + )); + + const showIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.setProperty('display', 'inline'); + }; + const hideIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.removeProperty('display'); + }; + + return ( + <> + {editing && ( + + )} + + + {circles} + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawRect.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawRect.tsx new file mode 100644 index 000000000..dc19b5254 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawRect.tsx @@ -0,0 +1,260 @@ +import { ScaleBand, ScaleLinear } from 'libs/d3'; +import { + FC, + KeyboardEvent, + MouseEvent as ReactMouseEvent, + useEffect, + useRef, + useState, +} from 'react'; +import { scaleBandInvert } from '../utils'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getMax, getMin } from 'utils/helpers/operators'; +import { getAreaBox, getDelta, getEdges, getInitialPoints } from './utils'; + +const getRectPoints = (points: ChartPoint[]) => { + const minX = getMin(...points.map((p) => p.x)).toString(); + const minY = getMin(...points.map((p) => p.y)); + const maxX = getMax(...points.map((p) => p.x)).toString(); + const maxY = getMax(...points.map((p) => p.y)); + // yScale will invert Y, so we need to put maxY first + return [ + { x: minX, y: maxY }, + { x: minX, y: minY }, + { x: maxX, y: maxY }, + { x: maxX, y: minY }, + ]; +}; +const getRectProps = ( + points: ChartPoint[], + xScale: ScaleBand, + yScale: ScaleLinear +) => ({ + x: xScale(points[0].x)!, + y: yScale(points[0].y), + width: xScale(points[3].x)! - xScale(points[0].x)!, + height: yScale(points[3].y) - yScale(points[0].y), +}); + +interface Props { + xScale: ScaleBand; + yScale: ScaleLinear; + onChange: (points: ChartPoint[]) => any; +} + +export const D3DrawRect: FC = ({ xScale, yScale, onChange }) => { + const ref = useRef(null); + const { dms } = useD3ChartCtx(); + const [points, setPoints] = useState([]); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + if (points.length !== 1) return; + const area = document.querySelector('.chart-drawing-canvas')!; + const root = area!.getBoundingClientRect(); + const handler = (event: MouseEvent) => { + if (!ref.current) return; + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const newPoints = getRectPoints([points[0], { x, y }]); + const props = getRectProps(newPoints, xScale, yScale); + for (const [key, value] of Object.entries(props)) { + ref.current.setAttribute(key, value.toString()); + } + }; + area.addEventListener('mousemove', handler as any); + return () => area.removeEventListener('mousemove', handler as any); + }, [invertX, invertY, points, xScale, yScale]); + + const addPoint = (event: ReactMouseEvent) => { + const svg = document.getElementById('interactive-chart')!; + const root = svg.getBoundingClientRect(); + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const rect = [...points, { x, y }]; + setPoints(rect); + if (points.length === 1) { + const [min, , , max] = getRectPoints(rect); + onChange([min, max]); + } + }; + + return ( + <> + {!!points.length && ( + + )} + {points.map(({ x, y }) => ( + + ))} + + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +export const D3EditRect: FC = ({ drawing, onChange }) => { + const ref = useRef(null); + const [editing, setEditing] = useState(false); + const { dms, xScale, yScale } = useD3ChartCtx(); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + const rectPoints = getRectPoints(drawing.points); + const rectProps = getRectProps(rectPoints, xScale, yScale); + + useEffect(() => { + document.getElementById(`shape-${drawing.id}`)?.focus(); + }, [drawing.id]); + + const onKeyDown = (event: KeyboardEvent) => { + const deleteKeys = ['Delete', 'Backspace', 'Clear']; + if (deleteKeys.includes(event.key)) onChange([]); + }; + + const dragShape = (event: ReactMouseEvent) => { + setEditing(true); + const shape = event.currentTarget; + const circles = getEdges(drawing.id); + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, circles); + shape.style.setProperty('cursor', 'grabbing'); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const points = initialPoints.map(({ x, y }) => ({ + x: invertX(x + delta.x), + y: invertY(y + delta.y), + })); + const [min, , , max] = getRectPoints(points); + onChange([min, max]); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + shape.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragPoint = ( + event: ReactMouseEvent, + index: number + ) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const circle = event.currentTarget; + circle.style.setProperty('cursor', 'grabbing'); + const box = circle.getBoundingClientRect(); + const root = getAreaBox(); + const initialX = box.x - root.x + box.width / 2; + const initialY = box.y - root.y + box.width / 2; + const points = structuredClone(rectPoints); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const opposite = points.find((p) => { + return p.x !== points[index].x && p.y !== points[index].y; + }); + if (!opposite) return; + const newPoint = { + x: invertX(initialX + delta.x), + y: invertY(initialY + delta.y), + }; + onChange([opposite, newPoint]); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + circle.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + const circles = rectPoints.map(({ x, y }, i) => ( + dragPoint(e, i)} + /> + )); + + const showIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.setProperty('display', 'inline'); + }; + const hideIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.removeProperty('display'); + }; + + return ( + <> + {editing && ( + + )} + + + {circles} + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawTriangle.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawTriangle.tsx new file mode 100644 index 000000000..1992a7773 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawTriangle.tsx @@ -0,0 +1,231 @@ +import { ScaleBand, ScaleLinear } from 'libs/d3'; +import { + FC, + KeyboardEvent, + MouseEvent as ReactMouseEvent, + useEffect, + useRef, + useState, +} from 'react'; +import { scaleBandInvert } from '../utils'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getAreaBox, getDelta, getEdges, getInitialPoints } from './utils'; + +interface Props { + xScale: ScaleBand; + yScale: ScaleLinear; + onChange: (points: ChartPoint[]) => any; +} + +const polygonPoints = ( + points: ChartPoint[], + xScale: ScaleBand, + yScale: ScaleLinear +) => { + return points.map(({ x, y }) => `${xScale(x)},${yScale(y)}`).join(' '); +}; + +export const D3DrawTriangle: FC = ({ xScale, yScale, onChange }) => { + const ref = useRef(null); + const { dms } = useD3ChartCtx(); + const [points, setPoints] = useState([]); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + if (!points.length) return; + const area = document.querySelector('.chart-drawing-canvas')!; + const root = area!.getBoundingClientRect(); + const handler = (event: MouseEvent) => { + if (!ref.current) return; + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const newPoints = polygonPoints([...points, { x, y }], xScale, yScale); + ref.current.setAttribute('points', newPoints); + }; + area.addEventListener('mousemove', handler as any); + return () => area.removeEventListener('mousemove', handler as any); + }, [invertX, invertY, points, xScale, yScale]); + + const addPoint = (event: ReactMouseEvent) => { + const svg = document.getElementById('interactive-chart')!; + const root = svg.getBoundingClientRect(); + const x = invertX(event.clientX - root.x); + const y = invertY(event.clientY - root.y); + const line = [...points, { x, y }]; + if (line.length === 3) onChange(line); + setPoints(line); + }; + + const polygon = polygonPoints(points, xScale, yScale); + + return ( + <> + {!!points.length && ( + + )} + {points.map(({ x, y }) => ( + + ))} + + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +export const D3EditTriangle: FC = ({ drawing, onChange }) => { + const ref = useRef(null); + const [editing, setEditing] = useState(false); + const { dms, xScale, yScale } = useD3ChartCtx(); + const invertX = scaleBandInvert(xScale); + const invertY = yScale.invert; + + useEffect(() => { + document.getElementById(`shape-${drawing.id}`)?.focus(); + }, [drawing.id]); + + const onKeyDown = (event: KeyboardEvent) => { + const deleteKeys = ['Delete', 'Backspace', 'Clear']; + if (deleteKeys.includes(event.key)) onChange([]); + }; + + const dragShape = (event: ReactMouseEvent) => { + setEditing(true); + const shape = event.currentTarget; + const circles = getEdges(drawing.id); + const root = getAreaBox(); + const initialPoints = getInitialPoints(root, circles); + shape.style.setProperty('cursor', 'grabbing'); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + const points = initialPoints.map(({ x, y }) => ({ + x: invertX(x + delta.x), + y: invertY(y + delta.y), + })); + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + shape.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + + const dragPoint = ( + event: ReactMouseEvent, + index: number + ) => { + event.stopPropagation(); // Prevent mousedown on g + setEditing(true); + const circle = event.currentTarget; + circle.style.setProperty('cursor', 'grabbing'); + const box = circle.getBoundingClientRect(); + const root = getAreaBox(); + const initialX = box.x - root.x + box.width / 2; + const initialY = box.y - root.y + box.width / 2; + const points = structuredClone(drawing.points); + const move = (e: MouseEvent) => { + const delta = getDelta(root, event, e); + if (!delta) return; + points[index] = { + x: invertX(initialX + delta.x), + y: invertY(initialY + delta.y), + }; + onChange(points); + }; + const dragEnd = () => { + document.removeEventListener('mousemove', move); + circle.style.removeProperty('cursor'); + setEditing(false); + }; + document.addEventListener('mousemove', move); + document.addEventListener('mouseup', dragEnd, { once: true }); + }; + const circles = drawing.points.map(({ x, y }, i) => ( + dragPoint(e, i)} + /> + )); + + const showIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.setProperty('display', 'inline'); + }; + const hideIndicator = () => { + const ranges = document.getElementById(`ranges-${drawing.id}`); + ranges?.style.removeProperty('display'); + }; + + const polygon = polygonPoints(drawing.points, xScale, yScale); + + return ( + <> + {editing && ( + + )} + + + {circles} + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3DrawingRanges.tsx b/src/components/strategies/common/d3Chart/drawing/D3DrawingRanges.tsx new file mode 100644 index 000000000..b349ff4f6 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3DrawingRanges.tsx @@ -0,0 +1,158 @@ +import { FC } from 'react'; +import { Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { getMax, getMin } from 'utils/helpers/operators'; +import { prettifyNumber } from 'utils/helpers'; +import { fromUnixUTC, xAxisFormatter } from 'components/simulator/utils'; +import { handleDms } from '../utils'; + +export const D3AllDrawingRanges = () => { + const { drawings } = useD3ChartCtx(); + return drawings.map((d, i) => ); +}; + +interface Props { + drawing: Drawing; +} +export const D3DrawingRanges: FC = ({ drawing }) => { + const xMin = getMin(...drawing.points.map((v) => v.x)); + const xMax = getMax(...drawing.points.map((v) => v.x)); + const yMin = getMin(...drawing.points.map((v) => v.y)); + const yMax = getMax(...drawing.points.map((v) => v.y)); + return ( + + + + + ); +}; + +interface XRangeProps { + min: number; + max: number; +} +export const D3DrawingXRange: FC = (props) => { + const { dms, xScale } = useD3ChartCtx(); + const min = xScale(props.min.toString())!; + const max = xScale(props.max.toString())!; + const y = dms.boundedHeight + dms.marginBottom / 2 - handleDms.height / 2; + return ( + + + + + + {xAxisFormatter.format(fromUnixUTC(props.min))} + + + + + + {xAxisFormatter.format(fromUnixUTC(props.max))} + + + + ); +}; + +interface YRangeProps { + min: number; + max: number; +} +export const D3DrawingYRange: FC = (props) => { + const { dms, yScale } = useD3ChartCtx(); + const min = yScale(props.min); + const max = yScale(props.max); + const x = dms.boundedWidth; + const padding = (dms.marginRight - handleDms.width) / 2; + return ( + + + + + + {prettifyNumber(props.min, { abbreviate: true })} + + + + + + {prettifyNumber(props.max, { abbreviate: true })} + + + + ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/D3Drawings.tsx b/src/components/strategies/common/d3Chart/drawing/D3Drawings.tsx new file mode 100644 index 000000000..1c99fc0fc --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/D3Drawings.tsx @@ -0,0 +1,56 @@ +import { FC } from 'react'; +import { D3DrawLine, D3EditLine } from './D3DrawLine'; +import { ChartPoint, Drawing, useD3ChartCtx } from '../D3ChartContext'; +import { D3DrawExtendedLine, D3EditExtendedLine } from './D3DrawExtendedLine'; +import { D3DrawTriangle, D3EditTriangle } from './D3DrawTriangle'; +import { D3DrawRect, D3EditRect } from './D3DrawRect'; +import { D3DrawChannel, D3EditChannel } from './D3DrawChannel'; + +export const D3Drawings = () => { + const { drawings, setDrawings, drawingMode, setDrawingMode, xScale, yScale } = + useD3ChartCtx(); + const onChange = (points: ChartPoint[]) => { + setDrawings((list) => [ + ...list, + { id: Date.now(), mode: drawingMode, points }, + ]); + setDrawingMode(undefined); + }; + const drawProps = { drawingMode, xScale, yScale, onChange }; + + const editShape = (index: number, points: ChartPoint[]) => { + setDrawings((list) => { + const copy = structuredClone(list); + if (!points.length) copy.splice(index, 1); + else copy[index].points = points; + return copy; + }); + }; + + return ( + <> + {drawings.map((drawing, i) => ( + editShape(i, p)} /> + ))} + {drawingMode === 'line' && } + {drawingMode === 'channel' && } + {drawingMode === 'triangle' && } + {drawingMode === 'rectangle' && } + {drawingMode === 'extended-line' && } + + ); +}; + +interface D3ShapeProps { + drawing: Drawing; + onChange: (points: ChartPoint[]) => any; +} + +const D3Shape: FC = (props) => { + const mode = props.drawing.mode; + if (mode === 'line') return ; + if (mode === 'channel') return ; + if (mode === 'triangle') return ; + if (mode === 'rectangle') return ; + if (mode === 'extended-line') return ; +}; diff --git a/src/components/strategies/common/d3Chart/drawing/DrawingMenu.tsx b/src/components/strategies/common/d3Chart/drawing/DrawingMenu.tsx new file mode 100644 index 000000000..716bc963a --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/DrawingMenu.tsx @@ -0,0 +1,98 @@ +import { FC } from 'react'; +import { ReactComponent as IconIndicator } from 'assets/icons/draw-indicator.svg'; +import { ReactComponent as IconLine } from 'assets/icons/draw-line.svg'; +import { ReactComponent as IconExtendedLine } from 'assets/icons/draw-extended-line.svg'; +import { ReactComponent as IconChannel } from 'assets/icons/draw-channel.svg'; +import { ReactComponent as IconTriangle } from 'assets/icons/draw-triangle.svg'; +import { ReactComponent as IconRectangle } from 'assets/icons/draw-rectangle.svg'; +import { ReactComponent as IconTrash } from 'assets/icons/trash.svg'; +import { useD3ChartCtx } from '../D3ChartContext'; +import { + FloatTooltip, + FloatTooltipContent, + FloatTooltipTrigger, +} from 'components/common/tooltip/FloatTooltip'; + +export type DrawingMode = (typeof drawings)[number]['mode']; + +const drawings = [ + { + mode: undefined, + icon: , + label: 'Cross', + }, + { + mode: 'line' as const, + icon: , + label: 'Trending Line', + }, + { + mode: 'extended-line' as const, + icon: , + label: 'Extended Line', + }, + { + mode: 'channel' as const, + icon: , + label: 'Parallel Channel', + }, + { + mode: 'triangle' as const, + icon: , + label: 'Triangle', + }, + { + mode: 'rectangle' as const, + icon: , + label: 'Rectangle', + }, +]; + +interface Props { + clearDrawings: () => void; +} + +export const DrawingMenu: FC = ({ clearDrawings }) => { + const { drawingMode, setDrawingMode } = useD3ChartCtx(); + return ( +
+ {drawings.map(({ mode, label, icon }) => ( + + + + + + {label} + + + ))} +
+ + + + + + Delete all + + +
+ ); +}; diff --git a/src/components/strategies/common/d3Chart/drawing/utils.ts b/src/components/strategies/common/d3Chart/drawing/utils.ts new file mode 100644 index 000000000..b9df7df66 --- /dev/null +++ b/src/components/strategies/common/d3Chart/drawing/utils.ts @@ -0,0 +1,39 @@ +import { MouseEvent as ReactMouseEvent } from 'react'; +export const getEdges = (id: number) => { + const shape = document.getElementById(`shape-${id}`)!; + const circles = shape.querySelectorAll('circle.edge'); + return Array.from(circles); +}; + +export const getAreaBox = () => { + const area = document.querySelector('.chart-area')!; + return area.getBoundingClientRect(); +}; + +export const getInitialPoints = ( + root: DOMRect, + circles: SVGCircleElement[] +) => { + return circles.map((c) => { + const { x, y, width } = c.getBoundingClientRect(); + return { + x: x - root.x + width / 2, + y: y - root.y + width / 2, + }; + }); +}; + +export const getDelta = ( + root: DOMRect, + mouseDown: ReactMouseEvent, + moveMove: MouseEvent +) => { + if (moveMove.clientX < root.x || moveMove.clientX > root.x + root.width) + return; + if (moveMove.clientY < root.y || moveMove.clientY > root.y + root.height) + return; + return { + x: moveMove.clientX - mouseDown.clientX, + y: moveMove.clientY - mouseDown.clientY, + }; +}; diff --git a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlapping.tsx b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlapping.tsx index 46b636110..122c8477c 100644 --- a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlapping.tsx +++ b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlapping.tsx @@ -17,33 +17,24 @@ import { getMaxBuyMin, getMinSellMax, } from 'components/strategies/overlapping/utils'; -import { ScaleLinear } from 'd3'; import { useCallback, useEffect, useRef } from 'react'; import { prettifyNumber } from 'utils/helpers'; import { useD3OverlappingChart } from './useD3OverlappingChart'; +import { useD3ChartCtx } from '../D3ChartContext'; type Props = Pick< D3ChartCandlesticksProps, - 'prices' | 'onPriceUpdates' | 'dms' | 'onDragEnd' + 'prices' | 'onPriceUpdates' | 'onDragEnd' > & { - yScale: ScaleLinear; marketPrice?: number; spread: number; readonly?: boolean; }; export const D3ChartOverlapping = (props: Props) => { - const { - dms, - yScale, - prices, - marketPrice, - readonly, - onPriceUpdates, - onDragEnd, - spread, - } = props; - + const { prices, marketPrice, readonly, onPriceUpdates, onDragEnd, spread } = + props; + const { dms, yScale } = useD3ChartCtx(); const isDragging = useRef(false); const selectorHandleBuyMin = getHandleSelector('buy', 'line1'); @@ -207,7 +198,6 @@ export const D3ChartOverlapping = (props: Props) => { { { /> { /> { readonly={readonly} /> { minOutOfScale={minIsOutOfScale} maxOutOfScale={false} color="var(--buy)" - dms={dms} /> ); diff --git a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingHandle.tsx b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingHandle.tsx index 25f91a9ea..4d8172f07 100644 --- a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingHandle.tsx +++ b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingHandle.tsx @@ -1,4 +1,4 @@ -import { D3ChartSettings, drag, Selection } from 'libs/d3'; +import { drag, Selection } from 'libs/d3'; import { D3ChartHandleLine } from 'components/strategies/common/d3Chart/D3ChartHandleLine'; import { getSelector, @@ -8,7 +8,6 @@ import { useEffect } from 'react'; interface Props { selector: string; - dms: D3ChartSettings; onDragStart?: (y: number) => void; onDrag?: (y: number) => void; onDragEnd?: (y: number) => void; diff --git a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingRangeGroup.tsx b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingRangeGroup.tsx index 5c9be8b33..b315309c5 100644 --- a/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingRangeGroup.tsx +++ b/src/components/strategies/common/d3Chart/overlapping/D3ChartOverlappingRangeGroup.tsx @@ -5,12 +5,11 @@ import { getSelector, useSelectable, } from 'components/strategies/common/d3Chart/utils'; -import { D3ChartSettings } from 'libs/d3/types'; import { useEffect, useState } from 'react'; +import { useD3ChartCtx } from '../D3ChartContext'; interface Props { readonly?: boolean; - dms: D3ChartSettings; onDragStart?: (y: number, y2: number) => void; onDrag?: (y: number, y2: number) => void; onDragEnd?: (y: number, y2: number) => void; @@ -18,11 +17,11 @@ interface Props { export const D3ChartOverlappingRangeGroup = ({ readonly, - dms, onDragStart, onDrag, onDragEnd, }: Props) => { + const { dms } = useD3ChartCtx(); const selector = 'overlapping-range-group'; const selection = getSelector(selector); const selectorRectBuy = getRectSelector('buy'); diff --git a/src/components/strategies/common/d3Chart/recurring/D3ChartHandle.tsx b/src/components/strategies/common/d3Chart/recurring/D3ChartHandle.tsx index 8acc392ad..a93b81a65 100644 --- a/src/components/strategies/common/d3Chart/recurring/D3ChartHandle.tsx +++ b/src/components/strategies/common/d3Chart/recurring/D3ChartHandle.tsx @@ -1,4 +1,4 @@ -import { D3ChartSettings, drag, Selection } from 'libs/d3'; +import { drag, Selection } from 'libs/d3'; import { D3ChartHandleLine } from 'components/strategies/common/d3Chart/D3ChartHandleLine'; import { getSelector, @@ -9,7 +9,6 @@ import { useEffect } from 'react'; interface Props { selector: string; selectorOpposite: string; - dms: D3ChartSettings; onDragStart?: (y: number) => void; onDrag: (y: number) => void; onDragEnd?: (y?: number, y2?: number) => void; diff --git a/src/components/strategies/common/d3Chart/recurring/D3ChartRecurring.tsx b/src/components/strategies/common/d3Chart/recurring/D3ChartRecurring.tsx index c1a100887..2a3f95108 100644 --- a/src/components/strategies/common/d3Chart/recurring/D3ChartRecurring.tsx +++ b/src/components/strategies/common/d3Chart/recurring/D3ChartRecurring.tsx @@ -1,15 +1,14 @@ import { D3ChartCandlesticksProps } from 'components/strategies/common/d3Chart/D3ChartCandlesticks'; import { DragablePriceRange } from 'components/strategies/common/d3Chart/recurring/DragablePriceRange'; import { handleStateChange } from 'components/strategies/common/d3Chart/recurring/utils'; -import { ScaleLinear } from 'd3'; import { useCallback, useEffect, useRef } from 'react'; import { prettifyNumber } from 'utils/helpers'; +import { useD3ChartCtx } from '../D3ChartContext'; type Props = Pick< D3ChartCandlesticksProps, - 'prices' | 'onPriceUpdates' | 'dms' | 'onDragEnd' + 'prices' | 'onPriceUpdates' | 'onDragEnd' > & { - yScale: ScaleLinear; isLimit: { buy: boolean; sell: boolean }; readonly?: boolean; }; @@ -17,12 +16,11 @@ type Props = Pick< export const D3ChartRecurring = ({ prices, onPriceUpdates, - dms, - yScale, isLimit, readonly, onDragEnd, }: Props) => { + const { yScale } = useD3ChartCtx(); const onMinMaxChange = useCallback( (type: 'buy' | 'sell', min: number, max: number) => { const minInverted = yScale.invert(min).toString(); @@ -154,7 +152,6 @@ export const D3ChartRecurring = ({ onMinMaxChange={onMinMaxChange} labels={labels.buy} yPos={yPos.buy} - dms={dms} onDragEnd={onMinMaxChangeEnd} isLimit={isLimit.buy} readonly={readonly} @@ -164,7 +161,6 @@ export const D3ChartRecurring = ({ onMinMaxChange={onMinMaxChange} labels={labels.sell} yPos={yPos.sell} - dms={dms} isLimit={isLimit.sell} onDragEnd={onMinMaxChangeEnd} readonly={readonly} diff --git a/src/components/strategies/common/d3Chart/recurring/DragablePriceRange.tsx b/src/components/strategies/common/d3Chart/recurring/DragablePriceRange.tsx index 8159db624..7fa841116 100644 --- a/src/components/strategies/common/d3Chart/recurring/DragablePriceRange.tsx +++ b/src/components/strategies/common/d3Chart/recurring/DragablePriceRange.tsx @@ -10,8 +10,8 @@ import { getHandleSelector, getRectSelector, } from 'components/strategies/common/d3Chart/utils'; -import { D3ChartSettings } from 'libs/d3/types'; import { useCallback, useEffect, useRef } from 'react'; +import { useD3ChartCtx } from '../D3ChartContext'; type OrderRangeProps = { type: 'buy' | 'sell'; @@ -19,7 +19,6 @@ type OrderRangeProps = { onDragEnd?: (type: 'buy' | 'sell', y?: number, y2?: number) => void; labels: { min: string; max: string }; yPos: { min: number; max: number }; - dms: D3ChartSettings; isLimit: boolean; readonly?: boolean; }; @@ -30,10 +29,10 @@ export const DragablePriceRange = ({ onDragEnd, labels, yPos, - dms, isLimit, readonly, }: OrderRangeProps) => { + const { dms } = useD3ChartCtx(); const isDragging = useRef(false); const color = type === 'buy' ? 'var(--buy)' : 'var(--sell)'; const maxIsOutOfScale = yPos.max <= 0; @@ -116,7 +115,6 @@ export const DragablePriceRange = ({ selector={selectorH1} selectorOpposite={selectorH2} label={isH1Max.current ? labels.max : labels.min} - dms={dms} onDragStart={onDragStartHandler} onDrag={onDragH1} onDragEnd={onDragEndHandler} @@ -129,7 +127,6 @@ export const DragablePriceRange = ({ selector={selectorH2} selectorOpposite={selectorH1} label={isH2Max.current ? labels.max : labels.min} - dms={dms} onDragStart={onDragStartHandler} onDrag={onDragH2} onDragEnd={onDragEndHandler} @@ -143,7 +140,6 @@ export const DragablePriceRange = ({ minOutOfScale={minIsOutOfScale} maxOutOfScale={maxIsOutOfScale} color={color} - dms={dms} /> ); diff --git a/src/components/strategies/common/d3Chart/utils.ts b/src/components/strategies/common/d3Chart/utils.ts index 603bef9ed..bf78cb65c 100644 --- a/src/components/strategies/common/d3Chart/utils.ts +++ b/src/components/strategies/common/d3Chart/utils.ts @@ -1,8 +1,13 @@ -import { max, min, select } from 'd3'; +import { max, min, ScaleBand, select } from 'd3'; import { ChartPrices } from 'components/strategies/common/d3Chart/D3ChartCandlesticks'; import { CandlestickData } from 'libs/d3/types'; import { useEffect, useState } from 'react'; +export const handleDms = { + width: 64, + height: 16, +}; + export const getSelector = (selector: string) => select(`.${selector}`); export const useSelectable = (selector: string) => { @@ -91,3 +96,13 @@ export const getDomain: GetDomainFn = (data, prices, marketPrice) => { return [domainMin, domainMax]; }; + +export const scaleBandInvert = (scale: ScaleBand) => { + const domain = scale.domain(); + const paddingOuter = scale(domain[0]) ?? 0; + const eachBand = scale.step(); + return (value: number) => { + const index = Math.floor((value - paddingOuter) / eachBand); + return domain[Math.max(0, Math.min(index, domain.length - 1))]; + }; +}; diff --git a/src/components/strategies/common/d3Chart/xAxis.tsx b/src/components/strategies/common/d3Chart/xAxis.tsx index d86876e90..39f4e5bc0 100644 --- a/src/components/strategies/common/d3Chart/xAxis.tsx +++ b/src/components/strategies/common/d3Chart/xAxis.tsx @@ -1,14 +1,18 @@ -import { ScaleBand } from 'd3'; -import { D3ChartSettings } from 'libs/d3'; import { fromUnixUTC, xAxisFormatter } from 'components/simulator/utils'; +import { useD3ChartCtx } from './D3ChartContext'; +import { useMemo } from 'react'; -type Props = { - dms: D3ChartSettings; - xScale: ScaleBand; - xTicks: string[]; -}; +export const XAxis = () => { + const { dms, xScale, zoom } = useD3ChartCtx(); + const xTicks = useMemo(() => { + const length = xScale.domain().length; + const ratio = Math.ceil(zoom?.k ?? 1); + const target = Math.floor((dms.boundedWidth * ratio) / 110); + const numberOfTicks = Math.max(1, target); + const m = Math.ceil(length / numberOfTicks); + return xScale.domain().filter((_, i) => i % m === m - 1); + }, [dms.boundedWidth, xScale, zoom]); -export const XAxis = ({ xScale, dms, xTicks }: Props) => { const ticks = xTicks.map((tickValue) => { const x = (xScale(tickValue) ?? 0) + xScale.bandwidth() / 2; return ( @@ -42,7 +46,7 @@ export const XAxis = ({ xScale, dms, xTicks }: Props) => { x={-bandwidthOffset} y={dms.boundedHeight} width={dms.width} - height="40" + height={dms.marginBottom} className="fill-background-black" /> ; @@ -31,12 +32,8 @@ const middle = 40; const bottom = 10; // Utils -const getMin = (...data: string[]) => SafeDecimal.min(...data).toNumber(); -const getMax = (...data: string[]) => SafeDecimal.max(...data).toNumber(); const round = (value: number) => Math.round(value * 100) / 100; -const clamp = (min: number, value: number, max: number) => { - return Math.min(max, Math.max(value, min)); -}; + const textWidth = (text: string = '') => { return text.length * (fontSize / 2) + 5; }; diff --git a/src/components/strategies/overview/StrategyFilterSort.tsx b/src/components/strategies/overview/StrategyFilterSort.tsx index ad1a885e5..a451e7139 100644 --- a/src/components/strategies/overview/StrategyFilterSort.tsx +++ b/src/components/strategies/overview/StrategyFilterSort.tsx @@ -29,7 +29,7 @@ export const strategySort = { pairAsc: 'Pair (A->Z)', pairDesc: 'Pair (Z->A)', totalBudgetDesc: 'Total Budget', - trades: 'Trades', + trades: 'Trade Count', }; export type StrategySort = keyof typeof strategySort; diff --git a/src/libs/d3/primitives/D3YAxisRight.tsx b/src/libs/d3/primitives/D3YAxisRight.tsx index cf0af0df9..58d09c721 100644 --- a/src/libs/d3/primitives/D3YAxisRight.tsx +++ b/src/libs/d3/primitives/D3YAxisRight.tsx @@ -3,11 +3,15 @@ import { uuid } from 'utils/helpers'; export const D3YAxisRight = ({ ticks, dms, formatter }: D3AxisProps) => { return ( - + diff --git a/src/libs/wagmi/connectors.ts b/src/libs/wagmi/connectors.ts index d37a2974a..d16ba4d46 100644 --- a/src/libs/wagmi/connectors.ts +++ b/src/libs/wagmi/connectors.ts @@ -1,7 +1,6 @@ import tailwindWalletLogo from 'assets/logos/tailwindWallet.svg'; import compassWalletLogo from 'assets/logos/compassWallet.svg'; import seifWalletLogo from 'assets/logos/seifWallet.svg'; -import { createStore } from 'mipd'; import { Connector, CreateConnectorFn, createConnector } from 'wagmi'; import { metaMask, @@ -34,10 +33,10 @@ const createPlaceholderConnector = ({ }) => { return createConnector(() => { return { - id: id, - name: name, + id, + name, type: id + PLACEHOLDER_TAG, - icon: icon, + icon, async setup() {}, async connect() { throw Error('Wallet not installed'); @@ -82,7 +81,6 @@ const getDefaultConnector = (connectorType: SelectableConnectionName) => { }); case 'MetaMask': return metaMask({ - extensionOnly: false, preferDesktop: true, dappMetadata: { name: config.appName, @@ -130,28 +128,11 @@ export const providerRdnsToName = ( ): string | undefined => providerMapRdnsToName[connectionName]; const getConfigConnectors = (): CreateConnectorFn[] => { - const store = createStore(); - - const initializedProvidersRdns = store - .getProviders() - .map((provider) => provider.info.rdns.toLowerCase()); - // Safe wallet always runs through an iFrame, the same check as the safe connector's getProvider is performed here. // The allowedDomains param in the safe connector is another way to check we're in a safe wallet - if (!isIframe()) initializedProvidersRdns.push('safe'); - - const initializedProvidersNames = initializedProvidersRdns - .map(providerRdnsToName) - .filter((c) => c) - .map((c) => c!.toLowerCase()); - - // Only initialize connectors that are not injected - const missingConnectors = selectedConnectors.filter( - (name) => !initializedProvidersNames.includes(name.toLowerCase()) - ); - - store.destroy(); - return missingConnectors.map(getDefaultConnector); + const configConnectors = new Set(selectedConnectors); + if (!isIframe()) configConnectors.delete('Safe'); + return Array.from(configConnectors).map(getDefaultConnector); }; export const configConnectors = getConfigConnectors(); diff --git a/src/pages/debug/index.tsx b/src/pages/debug/index.tsx index 66b3a3478..fb50bcd60 100644 --- a/src/pages/debug/index.tsx +++ b/src/pages/debug/index.tsx @@ -11,11 +11,13 @@ import { DebugFiatCurrency } from 'components/debug/DebugFiatCurrency'; import { DebugOrderBook } from 'components/debug/DebugOrderBook'; import { DebugSDKConfig } from 'components/debug/DebugSDKConfig'; import { DebugConfig } from 'components/debug/DebugConfig'; +import { DebugFeatureFlag } from 'components/debug/DebugFeatureFlag'; export const DebugPage = () => { return (
+ diff --git a/src/pages/simulator/overlapping/index.tsx b/src/pages/simulator/overlapping/index.tsx index 0af0cb78a..1dfb0f2d9 100644 --- a/src/pages/simulator/overlapping/index.tsx +++ b/src/pages/simulator/overlapping/index.tsx @@ -20,8 +20,8 @@ export const SimulatorInputOverlappingPage = () => { const { data, isPending, isError } = useGetTokenPriceHistory({ baseToken: searchState.baseToken, quoteToken: searchState.quoteToken, - start: searchState.start, - end: searchState.end, + start: defaultStart().toString(), + end: defaultEnd().toString(), }); useEffect(() => { diff --git a/src/pages/simulator/recurring/index.tsx b/src/pages/simulator/recurring/index.tsx index 27803162f..84ccc7b3d 100644 --- a/src/pages/simulator/recurring/index.tsx +++ b/src/pages/simulator/recurring/index.tsx @@ -23,8 +23,8 @@ export const SimulatorInputRecurringPage = () => { const { data, isPending, isError } = useGetTokenPriceHistory({ baseToken: searchState.baseToken, quoteToken: searchState.quoteToken, - start: searchState.start, - end: searchState.end, + start: defaultStart().toString(), + end: defaultEnd().toString(), }); const { marketPrice, isPending: marketPricePending } = useMarketPrice({ base: state.baseToken, diff --git a/src/pages/strategy/index.tsx b/src/pages/strategy/index.tsx index 1c7bdf952..5085937d4 100644 --- a/src/pages/strategy/index.tsx +++ b/src/pages/strategy/index.tsx @@ -27,12 +27,7 @@ import { defaultStartDate, emptyOrder, } from 'components/strategies/common/utils'; -import { fromUnixUTC, toUnixUTC } from 'components/simulator/utils'; -import { datePickerDisabledDays } from 'components/simulator/result/SimResultChartHeader'; -import { - DateRangePicker, - datePickerPresets, -} from 'components/common/datePicker/DateRangePicker'; +import { toUnixUTC } from 'components/simulator/utils'; import config from 'config'; import { StrategyBlockInfo } from 'components/strategies/overview/strategyBlock/StrategyBlockInfo'; import { useActivityQuery } from 'components/activity/useActivityQuery'; @@ -63,15 +58,6 @@ export const StrategyPage = () => { setSearch({ hideIndicators: shouldShow }); }; - const onDatePickerConfirm = (props: { start?: Date; end?: Date }) => { - const { start, end } = props; - if (!start || !end) return; - setSearch({ - priceStart: toUnixUTC(start), - priceEnd: toUnixUTC(end), - }); - }; - const { data: activities } = useActivityQuery({ strategyIds: id, start: priceStart?.toString() ?? toUnixUTC(defaultStartDate()), @@ -144,20 +130,6 @@ export const StrategyPage = () => {

Price Chart

- {isNativeChart && ( - - )} {isNativeChart && (

Indicators

diff --git a/src/services/localeStorage/index.ts b/src/services/localeStorage/index.ts index a71b662fa..44d2f200d 100644 --- a/src/services/localeStorage/index.ts +++ b/src/services/localeStorage/index.ts @@ -51,6 +51,7 @@ interface LocalStorageSchema { lastSdkCache: { timestamp: number; ttl: number }; notificationPreferences: NotificationPreference; configOverride: Partial; + featureFlags: string[]; } enum EnumStrategySort { diff --git a/src/utils/featureFlags.ts b/src/utils/featureFlags.ts new file mode 100644 index 000000000..a86d09d01 --- /dev/null +++ b/src/utils/featureFlags.ts @@ -0,0 +1,15 @@ +import { lsService } from 'services/localeStorage'; + +/** + * ⚠️ Deprecated flags ⚠️ + * To avoid possible overlap with deprecated flags do not use any of these deprecated flags: + * + */ + +export const featureFlags = []; + +type CurrentFlags = (typeof featureFlags)[number]['value']; + +/** Easily know if the user has an active flag */ +export const hasFlag = (flag: CurrentFlags) => + (lsService.getItem('featureFlags') ?? []).includes(flag); diff --git a/src/utils/helpers/operators.ts b/src/utils/helpers/operators.ts index be25f5228..b5d9c8397 100644 --- a/src/utils/helpers/operators.ts +++ b/src/utils/helpers/operators.ts @@ -1,3 +1,5 @@ +import { SafeDecimal } from 'libs/safedecimal'; + /** * Utils function used inside a filter for type safety * @example @@ -26,3 +28,10 @@ export const isEmpty = (value: any) => { (Array.isArray(value) && !value.length) ); }; + +export const clamp = (min: number, value: number, max: number) => + Math.min(max, Math.max(value, min)); +export const getMin = (...data: (string | number)[]) => + SafeDecimal.min(...data).toNumber(); +export const getMax = (...data: (string | number)[]) => + SafeDecimal.max(...data).toNumber(); diff --git a/tailwind.config.ts b/tailwind.config.ts index 1395ec9bc..26ddfaf17 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -63,7 +63,7 @@ export default { middle: lightDark(0.7292, 0.127, 214.3), // #10BBD8 last: lightDark(0.7617, 0.119, 184.67), // #3DCABB }, - primary: lightDark(0.68, 0.153, 160), // #00B578 + primary: lightDark(0.7985, 0.132, 159.5), // #67D79F error: lightDark(0.65, 0.147, 15), // #D86371 sell: lightDark(0.65, 0.147, 15), // #D86371 buy: lightDark(0.68, 0.153, 160), // #00B578 diff --git a/yarn.lock b/yarn.lock index bf46c265f..cfc606a09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1588,17 +1588,20 @@ resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20230801.0.tgz#d23ec9671a08d91ad027fb4c783e94dee275d279" integrity sha512-RzRUR+J/T3h58qbTZHYntYsnZXu3JnrlZIhqP2hhdyfoZAZ/+ko4wX0foAqlYHi+kXWaWtySHBuMcx6ec6TXlQ== -"@coinbase/wallet-sdk@4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-4.0.4.tgz#634cd89bac93eeaf381a1f026476794e53431ed6" - integrity sha512-74c040CRnGhfRjr3ArnkAgud86erIqdkPHNt5HR1k9u97uTIZCJww9eGYT67Qf7gHPpGS/xW8Be1D4dvRm63FA== +"@coinbase/wallet-sdk@4.2.3": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-4.2.3.tgz#a30fa0605b24bc42c37f52a62d2442bcbb7734af" + integrity sha512-BcyHZ/Ec84z0emORzqdXDv4P0oV+tV3a0OirfA8Ko1JGBIAVvB+hzLvZzCDvnuZx7MTK+Dd8Y9Tjlo446BpCIg== dependencies: - buffer "^6.0.3" + "@noble/hashes" "^1.4.0" clsx "^1.2.1" eventemitter3 "^5.0.1" - keccak "^3.0.3" - preact "^10.16.0" - sha.js "^2.4.11" + preact "^10.24.2" + +"@ecies/ciphers@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@ecies/ciphers/-/ciphers-0.2.1.tgz#a3119516fb55d27ed2d21c497b1c4988f0b4ca02" + integrity sha512-ezMihhjW24VNK/2qQR7lH8xCQY24nk0XHF/kwJ1OuiiY5iEwQXOcKVSy47fSoHPRG8gVGXcK5SgtONDk5xMwtQ== "@emotion/is-prop-valid@^0.8.2": version "0.8.8" @@ -2420,10 +2423,10 @@ resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.1.tgz#e89b840a7af8097a8ed4953d8dc8470d1302d3ef" integrity sha512-ihb3B0T/wJm1eUuArYP4lCTSEoZsClHhuWyfo/kMX3m/odpqNcPfsz5O2A3NT7dXCAgWPGDQGPqygCpgeniKMw== -"@metamask/sdk-communication-layer@0.28.2": - version "0.28.2" - resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.28.2.tgz#25d84a6af4dd79324e0d4c9d1f307711fbd4aa91" - integrity sha512-kGx6qgP482DecPILnIS38bgxIjNransR3/Jh5Lfg9BXJLaXpq/MEGrjHGnJHAqCyfRymnd5cgexHtXJvQtRWQA== +"@metamask/sdk-communication-layer@0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.30.0.tgz#2bd252cfce3ac4260a6c8c9359732ab5e839b75e" + integrity sha512-q5nbdYkAf76MsZxi1l5MJEAyd8sY9jLRapC8a7x1Q1BNV4rzQeFeux/d0mJ/jTR2LAwbnLZs2rL226AM75oK4w== dependencies: bufferutil "^4.0.8" date-fns "^2.29.3" @@ -2431,28 +2434,26 @@ utf-8-validate "^5.0.2" uuid "^8.3.2" -"@metamask/sdk-install-modal-web@0.28.1": - version "0.28.1" - resolved "https://registry.yarnpkg.com/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.28.1.tgz#3e7085c34eaec7f9974e4a928e7f5bea33a278c9" - integrity sha512-mHkIjWTpYQMPDMtLEEtTVXhae4pEjy7jDBfV7497L0U3VCPQrBl/giZBwA6AgKEX1emYcM2d1WRHWR9N4YhyJA== +"@metamask/sdk-install-modal-web@0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.30.0.tgz#9ec634201b1b47bb30064f42ae0efba7f204bb0a" + integrity sha512-1gT533Huja9tK3cmttvcpZirRAtWJ7vnYH+lnNRKEj2xIP335Df2cOwS+zqNC4GlRCZw7A3IsTjIzlKoxBY1uQ== dependencies: qr-code-styling "^1.6.0-rc.1" -"@metamask/sdk@0.28.4": - version "0.28.4" - resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.28.4.tgz#bb5f3849629403ec97c23e1a968c6b893ecf001c" - integrity sha512-RjWBKPNesjeua2SXIDF9IvYALOSsOQyqHv5DPPK0Voskytk7y+2n/33ocbC1BH5hTLI4hDPH+BuCpXJRWs3/Yg== +"@metamask/sdk@0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@metamask/sdk/-/sdk-0.30.1.tgz#63126ad769566098000cc3c2cd513d18808471f3" + integrity sha512-NelEjJZsF5wVpSQELpmvXtnS9+C6HdxGQ4GB9jMRzeejphmPyKqmrIGM6XtaPrJtlpX+40AcJ2dtBQcjJVzpbQ== dependencies: "@metamask/onboarding" "^1.0.1" "@metamask/providers" "16.1.0" - "@metamask/sdk-communication-layer" "0.28.2" - "@metamask/sdk-install-modal-web" "0.28.1" - "@types/dom-screen-wake-lock" "^1.0.0" - "@types/uuid" "^10.0.0" + "@metamask/sdk-communication-layer" "0.30.0" + "@metamask/sdk-install-modal-web" "0.30.0" bowser "^2.9.0" cross-fetch "^4.0.0" debug "^4.3.4" - eciesjs "^0.3.15" + eciesjs "^0.4.8" eth-rpc-errors "^4.0.3" eventemitter2 "^6.4.7" i18next "23.11.5" @@ -2462,7 +2463,6 @@ qrcode-terminal-nooctal "^0.12.1" react-native-webview "^11.26.0" readable-stream "^3.6.2" - rollup-plugin-visualizer "^5.9.2" socket.io-client "^4.5.1" util "^0.12.4" uuid "^8.3.2" @@ -2598,6 +2598,11 @@ dependencies: eslint-scope "5.1.1" +"@noble/ciphers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.0.0.tgz#34758a1cbfcd4126880f83e6b1cdeb88785b7970" + integrity sha512-wH5EHOmLi0rEazphPbecAzmjd12I6/Yv/SiHdkA9LSycsQk7RuuTp7am5/o62qYr0RScE7Pc9icXGBbsr6cesA== + "@noble/curves@1.2.0", "@noble/curves@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -2619,7 +2624,7 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.4.0": +"@noble/curves@^1.4.0", "@noble/curves@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== @@ -2648,7 +2653,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -2900,10 +2905,10 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf" integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw== -"@safe-global/safe-apps-provider@0.18.3": - version "0.18.3" - resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.3.tgz#805a42e24f5dde803cb96dac251a3c9e256de45b" - integrity sha512-f/0cNv3S4v7p8rowAjj0hDCg8Q8P/wBjp5twkNWeBdvd0RDr7BuRBPPk74LCqmjQ82P+1ltLlkmVFSmxTIT7XQ== +"@safe-global/safe-apps-provider@0.18.4": + version "0.18.4" + resolved "https://registry.yarnpkg.com/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.4.tgz#53df912aa20d933f6b14c5bcb0737a8cd47def57" + integrity sha512-SWYeG3gyTO6wGHMSokfHakZ9isByn2mHsM0VohIorYFFEyGGmJ89btnTm+DqDUSoQtvWAatZB7XNy6CaYMvqtg== dependencies: "@safe-global/safe-apps-sdk" "^9.1.0" events "^3.3.0" @@ -3824,11 +3829,6 @@ dependencies: "@types/ms" "*" -"@types/dom-screen-wake-lock@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@types/dom-screen-wake-lock/-/dom-screen-wake-lock-1.0.3.tgz#c3588a5f6f40fae957f9ce5be9bc4927a61bb9a0" - integrity sha512-3Iten7X3Zgwvk6kh6/NRdwN7WbZ760YgFCsF5AxDifltUQzW1RaW+WRmcVtgwFzLjaNu64H+0MPJ13yRa8g3Dw== - "@types/estree@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" @@ -3926,13 +3926,6 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== -"@types/secp256k1@^4.0.4": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" - integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== - dependencies: - "@types/node" "*" - "@types/semver@^7.3.12": version "7.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" @@ -3948,11 +3941,6 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311" integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== -"@types/uuid@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" - integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== - "@types/wrap-ansi@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" @@ -4123,32 +4111,31 @@ loupe "^2.3.7" pretty-format "^29.7.0" -"@wagmi/connectors@5.1.11": - version "5.1.11" - resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-5.1.11.tgz#828fd8764c4e632efe215d2b3b75415d8e601836" - integrity sha512-k6IfxYHG0MqJWt2KY6UhrNt4mPSmCLq0tQG3h+uB5em1oioX9V902geoik+KoF6Sa0oqAq5UTJVA1IT5lAjOkQ== +"@wagmi/connectors@5.3.10": + version "5.3.10" + resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-5.3.10.tgz#e28afaabe0cd0dd993de6460d0c137afcfcaf379" + integrity sha512-4nXfnycfDkw8uGuZVf19E8TWd1rtskFUZEg0rLeY/pc3Uu5KhWuzNu+ycLfBHpepkuWKXxL7yr0jqzPiNn+ggg== dependencies: - "@coinbase/wallet-sdk" "4.0.4" - "@metamask/sdk" "0.28.4" - "@safe-global/safe-apps-provider" "0.18.3" + "@coinbase/wallet-sdk" "4.2.3" + "@metamask/sdk" "0.30.1" + "@safe-global/safe-apps-provider" "0.18.4" "@safe-global/safe-apps-sdk" "9.1.0" - "@walletconnect/ethereum-provider" "2.16.1" - "@walletconnect/modal" "2.6.2" + "@walletconnect/ethereum-provider" "2.17.0" cbw-sdk "npm:@coinbase/wallet-sdk@3.9.3" -"@wagmi/core@2.13.5": - version "2.13.5" - resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-2.13.5.tgz#20764d88d36c31c4557511309eef7d23fa60c98e" - integrity sha512-lvX/hApJTSA/H2kOklokjIYiUpnT8CpBH80GeOiKxU0CGK1wNHTu20GRTCy0GF1t7jkNwPSG3m0SmnXmgYMmHw== +"@wagmi/core@2.14.6": + version "2.14.6" + resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-2.14.6.tgz#f08894b01ab896c20989ee6d12bf07376adc613e" + integrity sha512-YoDtMt/RofrB3geEXGzV/xJYsMMN3U6x6cyWrScHwLF32NtlfQAtOUvRpJ5Q0FmQteRLiupVAOu+WB2aDLzCiA== dependencies: eventemitter3 "5.0.1" mipd "0.0.7" - zustand "4.4.1" + zustand "5.0.0" -"@walletconnect/core@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.16.1.tgz#019b181387792e0d284e75074b961b48193d9b6a" - integrity sha512-UlsnEMT5wwFvmxEjX8s4oju7R3zadxNbZgsFeHEsjh7uknY2zgmUe1Lfc5XU6zyPb1Jx7Nqpdx1KN485ee8ogw== +"@walletconnect/core@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.17.0.tgz#bf490e85a4702eff0f7cf81ba0d3c1016dffff33" + integrity sha512-On+uSaCfWdsMIQsECwWHZBmUXfrnqmv6B8SXRRuTJgd8tUpEvBkLQH4X7XkSm3zW6ozEkQTCagZ2ox2YPn3kbw== dependencies: "@walletconnect/heartbeat" "1.2.2" "@walletconnect/jsonrpc-provider" "1.0.14" @@ -4161,8 +4148,8 @@ "@walletconnect/relay-auth" "1.0.4" "@walletconnect/safe-json" "1.0.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.1" - "@walletconnect/utils" "2.16.1" + "@walletconnect/types" "2.17.0" + "@walletconnect/utils" "2.17.0" events "3.3.0" lodash.isequal "4.5.0" uint8arrays "3.1.0" @@ -4174,20 +4161,20 @@ dependencies: tslib "1.14.1" -"@walletconnect/ethereum-provider@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.16.1.tgz#4fb8a1df39104ad3fbd02579233e796f432f6d35" - integrity sha512-oD7DNCssUX3plS5gGUZ9JQ63muQB/vxO68X6RzD2wd8gBsYtSPw4BqYFc7KTO6dUizD6gfPirw32yW2pTvy92w== +"@walletconnect/ethereum-provider@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.17.0.tgz#d74feaaed6180a6799e96760d7ee867ff3a083d2" + integrity sha512-b+KTAXOb6JjoxkwpgYQQKPUcTwENGmdEdZoIDLeRicUmZTn/IQKfkMoC2frClB4YxkyoVMtj1oMV2JAax+yu9A== dependencies: "@walletconnect/jsonrpc-http-connection" "1.0.8" "@walletconnect/jsonrpc-provider" "1.0.14" "@walletconnect/jsonrpc-types" "1.0.4" "@walletconnect/jsonrpc-utils" "1.0.8" - "@walletconnect/modal" "2.6.2" - "@walletconnect/sign-client" "2.16.1" - "@walletconnect/types" "2.16.1" - "@walletconnect/universal-provider" "2.16.1" - "@walletconnect/utils" "2.16.1" + "@walletconnect/modal" "2.7.0" + "@walletconnect/sign-client" "2.17.0" + "@walletconnect/types" "2.17.0" + "@walletconnect/universal-provider" "2.17.0" + "@walletconnect/utils" "2.17.0" events "3.3.0" "@walletconnect/events@1.0.1", "@walletconnect/events@^1.0.1": @@ -4278,30 +4265,30 @@ "@walletconnect/safe-json" "^1.0.2" pino "7.11.0" -"@walletconnect/modal-core@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.6.2.tgz#d73e45d96668764e0c8668ea07a45bb8b81119e9" - integrity sha512-cv8ibvdOJQv2B+nyxP9IIFdxvQznMz8OOr/oR/AaUZym4hjXNL/l1a2UlSQBXrVjo3xxbouMxLb3kBsHoYP2CA== +"@walletconnect/modal-core@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.7.0.tgz#73c13c3b7b0abf9ccdbac9b242254a86327ce0a4" + integrity sha512-oyMIfdlNdpyKF2kTJowTixZSo0PGlCJRdssUN/EZdA6H6v03hZnf09JnwpljZNfir2M65Dvjm/15nGrDQnlxSA== dependencies: valtio "1.11.2" -"@walletconnect/modal-ui@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.6.2.tgz#fa57c087c57b7f76aaae93deab0f84bb68b59cf9" - integrity sha512-rbdstM1HPGvr7jprQkyPggX7rP4XiCG85ZA+zWBEX0dVQg8PpAgRUqpeub4xQKDgY7pY/xLRXSiCVdWGqvG2HA== +"@walletconnect/modal-ui@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.7.0.tgz#dbbb7ee46a5a25f7d39db622706f2d197b268cbb" + integrity sha512-gERYvU7D7K1ANCN/8vUgsE0d2hnRemfAFZ2novm9aZBg7TEd/4EgB+AqbJ+1dc7GhOL6dazckVq78TgccHb7mQ== dependencies: - "@walletconnect/modal-core" "2.6.2" + "@walletconnect/modal-core" "2.7.0" lit "2.8.0" motion "10.16.2" qrcode "1.5.3" -"@walletconnect/modal@2.6.2": - version "2.6.2" - resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.6.2.tgz#4b534a836f5039eeb3268b80be7217a94dd12651" - integrity sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA== +"@walletconnect/modal@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.7.0.tgz#55f969796d104cce1205f5f844d8f8438b79723a" + integrity sha512-RQVt58oJ+rwqnPcIvRFeMGKuXb9qkgSmwz4noF8JZGUym3gUAzVs+uW2NQ1Owm9XOJAV+sANrtJ+VoVq1ftElw== dependencies: - "@walletconnect/modal-core" "2.6.2" - "@walletconnect/modal-ui" "2.6.2" + "@walletconnect/modal-core" "2.7.0" + "@walletconnect/modal-ui" "2.7.0" "@walletconnect/relay-api@1.0.11": version "1.0.11" @@ -4329,19 +4316,19 @@ dependencies: tslib "1.14.1" -"@walletconnect/sign-client@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.16.1.tgz#94a2f630ba741bd180f540c53576c5ceaace4857" - integrity sha512-s2Tx2n2duxt+sHtuWXrN9yZVaHaYqcEcjwlTD+55/vs5NUPlISf+fFmZLwSeX1kUlrSBrAuxPUcqQuRTKcjLOA== +"@walletconnect/sign-client@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.17.0.tgz#efe811b1bb10082d964e2f0378aaa1b40f424503" + integrity sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg== dependencies: - "@walletconnect/core" "2.16.1" + "@walletconnect/core" "2.17.0" "@walletconnect/events" "1.0.1" "@walletconnect/heartbeat" "1.2.2" "@walletconnect/jsonrpc-utils" "1.0.8" "@walletconnect/logger" "2.1.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.1" - "@walletconnect/utils" "2.16.1" + "@walletconnect/types" "2.17.0" + "@walletconnect/utils" "2.17.0" events "3.3.0" "@walletconnect/time@1.0.2", "@walletconnect/time@^1.0.2": @@ -4351,10 +4338,10 @@ dependencies: tslib "1.14.1" -"@walletconnect/types@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.16.1.tgz#6583d458d3f7b1919d482ba516ccb7878ec8c91f" - integrity sha512-9P4RG4VoDEF+yBF/n2TF12gsvT/aTaeZTVDb/AOayafqiPnmrQZMKmNCJJjq1sfdsDcHXFcZWMGsuCeSJCmrXA== +"@walletconnect/types@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.17.0.tgz#20eda5791e3172f8ab9146caa3f317701d4b3232" + integrity sha512-i1pn9URpvt9bcjRDkabuAmpA9K7mzyKoLJlbsAujRVX7pfaG7wur7u9Jz0bk1HxvuABL5LHNncTnVKSXKQ5jZA== dependencies: "@walletconnect/events" "1.0.1" "@walletconnect/heartbeat" "1.2.2" @@ -4363,25 +4350,25 @@ "@walletconnect/logger" "2.1.2" events "3.3.0" -"@walletconnect/universal-provider@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.16.1.tgz#6d52c41c7388e01f89007956a1117748ab9a11e4" - integrity sha512-q/tyWUVNenizuClEiaekx9FZj/STU1F3wpDK4PUIh3xh+OmUI5fw2dY3MaNDjyb5AyrS0M8BuQDeuoSuOR/Q7w== +"@walletconnect/universal-provider@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.17.0.tgz#c9d4bbd9b8f0e41b500b2488ccbc207dc5f7a170" + integrity sha512-d3V5Be7AqLrvzcdMZSBS8DmGDRdqnyLk1DWmRKAGgR6ieUWykhhUKlvfeoZtvJrIXrY7rUGYpH1X41UtFkW5Pw== dependencies: "@walletconnect/jsonrpc-http-connection" "1.0.8" "@walletconnect/jsonrpc-provider" "1.0.14" "@walletconnect/jsonrpc-types" "1.0.4" "@walletconnect/jsonrpc-utils" "1.0.8" "@walletconnect/logger" "2.1.2" - "@walletconnect/sign-client" "2.16.1" - "@walletconnect/types" "2.16.1" - "@walletconnect/utils" "2.16.1" + "@walletconnect/sign-client" "2.17.0" + "@walletconnect/types" "2.17.0" + "@walletconnect/utils" "2.17.0" events "3.3.0" -"@walletconnect/utils@2.16.1": - version "2.16.1" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.16.1.tgz#2099cc2bd16b0edc32022f64aa2c2c323b45d1d4" - integrity sha512-aoQirVoDoiiEtYeYDtNtQxFzwO/oCrz9zqeEEXYJaAwXlGVTS34KFe7W3/Rxd/pldTYKFOZsku2EzpISfH8Wsw== +"@walletconnect/utils@2.17.0": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.17.0.tgz#02b3af0b80d0c1a994d692d829d066271b04d071" + integrity sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ== dependencies: "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" @@ -4392,7 +4379,7 @@ "@walletconnect/relay-auth" "1.0.4" "@walletconnect/safe-json" "1.0.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.1" + "@walletconnect/types" "2.17.0" "@walletconnect/window-getters" "1.0.1" "@walletconnect/window-metadata" "1.0.1" detect-browser "5.3.0" @@ -5023,9 +5010,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001629, caniuse-lite@^1.0.30001639, caniuse-lite@^1.0.30001640: - version "1.0.30001677" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz" - integrity sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog== + version "1.0.30001679" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz" + integrity sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA== "cbw-sdk@npm:@coinbase/wallet-sdk@3.9.3": version "3.9.3" @@ -5790,11 +5777,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" @@ -5940,14 +5922,15 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -eciesjs@^0.3.15: - version "0.3.18" - resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.3.18.tgz#67b5d73a8466e40a45bbc2f2a3177e71e9c0643d" - integrity sha512-RQhegEtLSyIiGJmFTZfvCTHER/fymipXFVx6OwSRYD6hOuy+6Kjpk0dGvIfP9kxn/smBpxQy71uxpGO406ITCw== +eciesjs@^0.4.8: + version "0.4.11" + resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.4.11.tgz#cdd3b988df9c7be99049c765f5ff895efa16060d" + integrity sha512-SmUG449n1w1YGvJD9R30tBGvpxTxA0cnn0rfvpFIBvmezfIhagLjsH2JG8HBHOLS8slXsPh48II7IDUTH/J3Mg== dependencies: - "@types/secp256k1" "^4.0.4" - futoin-hkdf "^1.5.3" - secp256k1 "^5.0.0" + "@ecies/ciphers" "^0.2.1" + "@noble/ciphers" "^1.0.0" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" ejs@^3.1.5: version "3.1.10" @@ -5984,7 +5967,7 @@ elliptic@6.5.4, elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.4, elliptic@^6.5.5: +elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== @@ -6869,11 +6852,6 @@ fuse.js@^6.6.2: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111" integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA== -futoin-hkdf@^1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz#6c8024f2e1429da086d4e18289ef2239ad33ee35" - integrity sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -7379,7 +7357,7 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== @@ -7510,7 +7488,7 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -8126,7 +8104,7 @@ minipass@^4.2.4: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== -mipd@0.0.7, mipd@^0.0.7: +mipd@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.7.tgz#bb5559e21fa18dc3d9fe1c08902ef14b7ce32fd9" integrity sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg== @@ -8267,11 +8245,6 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== -node-addon-api@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" - integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== - node-addon-api@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" @@ -8463,15 +8436,6 @@ open@^7.3.1: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -8839,6 +8803,11 @@ preact@^10.16.0: resolved "https://registry.yarnpkg.com/preact/-/preact-10.22.0.tgz#a50f38006ae438d255e2631cbdaf7488e6dd4e16" integrity sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw== +preact@^10.24.2: + version "10.24.3" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64" + integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9287,16 +9256,6 @@ robust-predicates@^3.0.0: resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== -rollup-plugin-visualizer@^5.9.2: - version "5.12.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz#661542191ce78ee4f378995297260d0c1efb1302" - integrity sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ== - dependencies: - open "^8.4.0" - picomatch "^2.3.1" - source-map "^0.7.4" - yargs "^17.5.1" - rollup@^4.13.0: version "4.23.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.23.0.tgz#6eed667340a6e95407eebbbb65de1138fbce1a79" @@ -9397,15 +9356,6 @@ scrypt-js@3.0.1: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== -secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== - dependencies: - elliptic "^6.5.4" - node-addon-api "^5.0.0" - node-gyp-build "^4.2.0" - semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -10447,13 +10397,13 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -wagmi@2.12.12: - version "2.12.12" - resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-2.12.12.tgz#0780267ac473f7dfe25d887ae5186e1e3790c087" - integrity sha512-BgB8GprWJzWuq3V6vCr12kP9a+ta9AWEkoM/fjQWE90yD9YWEgYmpK/uqXNnZLymsuSfxyIFn7JhYIs+mwo/yA== +wagmi@2.12.32: + version "2.12.32" + resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-2.12.32.tgz#1f641c9626ef5389c2ac68a836c08aceb6607295" + integrity sha512-0svmcWEQOTZpt322CYGcX0KGSwW4DZ24vr3hpHTgqFmVZMfKSVuOWQoXXvnFcrkLWitTz0sDCy58yjf1F4TUng== dependencies: - "@wagmi/connectors" "5.1.11" - "@wagmi/core" "2.13.5" + "@wagmi/connectors" "5.3.10" + "@wagmi/core" "2.14.6" use-sync-external-store "1.2.0" web-vitals@^2.1.0: @@ -10746,7 +10696,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.5.1, yargs@^17.7.2: +yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -10774,9 +10724,7 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== -zustand@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.1.tgz#0cd3a3e4756f21811bd956418fdc686877e8b3b0" - integrity sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw== - dependencies: - use-sync-external-store "1.2.0" +zustand@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.0.tgz#71f8aaecf185592a3ba2743d7516607361899da9" + integrity sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==